Merge branch 'feature/zip-316-and-latest-upstream' into release/0.17.0-alpha.3

This commit is contained in:
Francisco Gindre 2022-11-01 15:09:39 -03:00
commit 59dc56c9b4
53 changed files with 1045 additions and 626 deletions

View File

@ -1,6 +1,6 @@
language: swift language: swift
os: osx os: osx
osx_image: xcode13.4 osx_image: xcode14
xcode_project: ./Example/ZcashLightClientSample/ZcashLightClientSample.xcodeproj xcode_project: ./Example/ZcashLightClientSample/ZcashLightClientSample.xcodeproj
xcode_scheme: ZcashLightClientSample xcode_scheme: ZcashLightClientSample
xcode_destination: platform=iOS Simulator,OS=15.5,name=iPhone 8 xcode_destination: platform=iOS Simulator,OS=15.5,name=iPhone 8

View File

@ -9,6 +9,34 @@
# 0.17.0-alpha.1 # 0.17.0-alpha.1
See MIGRATING.md See MIGRATING.md
# 0.16-13-beta
- [#597] SDK does not build with SQLite 0.14
# 0.16.12-beta
Checkpoints added:
Mainnet
````
Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1832500.json
Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1835000.json
Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1837500.json
Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1840000.json
Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1842500.json
Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1845000.json
Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1847500.json
Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1850000.json
````
# 0.16.11-beta
Checkpoints added:
Mainnet
````
Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1812500.json
Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1815000.json
Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1817500.json
Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1820000.json
Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1822500.json
Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1825000.json
Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1827500.json
Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1830000.json
````
# 0.16.10-beta # 0.16.10-beta
- [#532] [0.16.x-beta] Download does not stop correctly - [#532] [0.16.x-beta] Download does not stop correctly

View File

@ -1,149 +1,151 @@
{ {
"pins" : [ "object": {
{ "pins": [
"identity" : "grpc-swift", {
"kind" : "remoteSourceControl", "package": "grpc-swift",
"location" : "https://github.com/grpc/grpc-swift.git", "repositoryURL": "https://github.com/grpc/grpc-swift.git",
"state" : { "state": {
"revision" : "466cc881f1760ed8c0e685900ed62dab7846a571", "branch": null,
"version" : "1.8.0" "revision": "466cc881f1760ed8c0e685900ed62dab7846a571",
"version": "1.8.0"
}
},
{
"package": "KRActivityIndicatorView",
"repositoryURL": "https://github.com/krimpedance/KRActivityIndicatorView.git",
"state": {
"branch": null,
"revision": "bcb0e841d6de0cd343a32bd5056580a56d06c0bc",
"version": "3.0.7"
}
},
{
"package": "KRProgressHUD",
"repositoryURL": "https://github.com/krimpedance/KRProgressHUD.git",
"state": {
"branch": null,
"revision": "265142816d8f8ea93840accaf4ac7c49998e77c2",
"version": "3.4.7"
}
},
{
"package": "MnemonicSwift",
"repositoryURL": "https://github.com/zcash-hackworks/MnemonicSwift.git",
"state": {
"branch": null,
"revision": "27711179a75a1172d6f04ceb5d86419cf0cba401",
"version": "2.1.0"
}
},
{
"package": "NotificationBubbles",
"repositoryURL": "https://github.com/pacu/NotificationBubbles.git",
"state": {
"branch": null,
"revision": "ae6d47f3a415c9eec5daa8e04d040c0e68654f00",
"version": "1.0.0"
}
},
{
"package": "PaginatedTableView",
"repositoryURL": "https://github.com/dh-ecc/PaginatedTableView",
"state": {
"branch": "master",
"revision": "a3fd9f079d6c9ac3095ee3ef2369a68c29ba04db",
"version": null
}
},
{
"identity" : "sqlite.swift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/stephencelis/SQLite.swift.git",
"state" : {
"revision" : "60a65015f6402b7c34b9a924f755ca0a73afeeaa",
"version" : "0.13.1"
}
},
{
"package": "swift-crypto",
"repositoryURL": "https://github.com/apple/swift-crypto.git",
"state": {
"branch": null,
"revision": "a8911e0fadc25aef1071d582355bd1037a176060",
"version": "2.0.4"
}
},
{
"package": "swift-log",
"repositoryURL": "https://github.com/apple/swift-log.git",
"state": {
"branch": null,
"revision": "5d66f7ba25daf4f94100e7022febf3c75e37a6c7",
"version": "1.4.2"
}
},
{
"package": "swift-nio",
"repositoryURL": "https://github.com/apple/swift-nio.git",
"state": {
"branch": null,
"revision": "51c3fc2e4a0fcdf4a25089b288dd65b73df1b0ef",
"version": "2.37.0"
}
},
{
"package": "swift-nio-extras",
"repositoryURL": "https://github.com/apple/swift-nio-extras.git",
"state": {
"branch": null,
"revision": "f73ca5ee9c6806800243f1ac415fcf82de9a4c91",
"version": "1.10.2"
}
},
{
"package": "swift-nio-http2",
"repositoryURL": "https://github.com/apple/swift-nio-http2.git",
"state": {
"branch": null,
"revision": "108ac15087ea9b79abb6f6742699cf31de0e8772",
"version": "1.22.0"
}
},
{
"package": "swift-nio-ssl",
"repositoryURL": "https://github.com/apple/swift-nio-ssl.git",
"state": {
"branch": null,
"revision": "52a486ff6de9bc3e26bf634c5413c41c5fa89ca5",
"version": "2.17.2"
}
},
{
"package": "swift-nio-transport-services",
"repositoryURL": "https://github.com/apple/swift-nio-transport-services.git",
"state": {
"branch": null,
"revision": "8ab824b140d0ebcd87e9149266ddc353e3705a3e",
"version": "1.11.4"
}
},
{
"package": "SwiftProtobuf",
"repositoryURL": "https://github.com/apple/swift-protobuf.git",
"state": {
"branch": null,
"revision": "e1499bc69b9040b29184f7f2996f7bab467c1639",
"version": "1.19.0"
}
},
{
"identity" : "zcash-light-client-ffi",
"kind" : "remoteSourceControl",
"location" : "https://github.com/zcash-hackworks/zcash-light-client-ffi",
"state" : {
"revision" : "4043e011f4d6a39b7671d48ca4a54c0085603b76",
"version" : "0.1.0-beta.1"
}
} }
}, ]
{ },
"identity" : "kractivityindicatorview", "version": 1
"kind" : "remoteSourceControl",
"location" : "https://github.com/krimpedance/KRActivityIndicatorView.git",
"state" : {
"revision" : "bcb0e841d6de0cd343a32bd5056580a56d06c0bc",
"version" : "3.0.7"
}
},
{
"identity" : "krprogresshud",
"kind" : "remoteSourceControl",
"location" : "https://github.com/krimpedance/KRProgressHUD.git",
"state" : {
"revision" : "265142816d8f8ea93840accaf4ac7c49998e77c2",
"version" : "3.4.7"
}
},
{
"identity" : "mnemonicswift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/zcash-hackworks/MnemonicSwift.git",
"state" : {
"revision" : "716a2c32ac2bbd8a1499ac834077df42b75edc85",
"version" : "2.2.4"
}
},
{
"identity" : "notificationbubbles",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pacu/NotificationBubbles.git",
"state" : {
"revision" : "ae6d47f3a415c9eec5daa8e04d040c0e68654f00",
"version" : "1.0.0"
}
},
{
"identity" : "paginatedtableview",
"kind" : "remoteSourceControl",
"location" : "https://github.com/dh-ecc/PaginatedTableView",
"state" : {
"branch" : "master",
"revision" : "a3fd9f079d6c9ac3095ee3ef2369a68c29ba04db"
}
},
{
"identity" : "sqlite.swift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/stephencelis/SQLite.swift.git",
"state" : {
"revision" : "60a65015f6402b7c34b9a924f755ca0a73afeeaa",
"version" : "0.13.1"
}
},
{
"identity" : "swift-crypto",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-crypto.git",
"state" : {
"revision" : "d9825fa541df64b1a7b182178d61b9a82730d01f",
"version" : "2.1.0"
}
},
{
"identity" : "swift-log",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-log.git",
"state" : {
"revision" : "5d66f7ba25daf4f94100e7022febf3c75e37a6c7",
"version" : "1.4.2"
}
},
{
"identity" : "swift-nio",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio.git",
"state" : {
"revision" : "51c3fc2e4a0fcdf4a25089b288dd65b73df1b0ef",
"version" : "2.37.0"
}
},
{
"identity" : "swift-nio-extras",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio-extras.git",
"state" : {
"revision" : "f73ca5ee9c6806800243f1ac415fcf82de9a4c91",
"version" : "1.10.2"
}
},
{
"identity" : "swift-nio-http2",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio-http2.git",
"state" : {
"revision" : "108ac15087ea9b79abb6f6742699cf31de0e8772",
"version" : "1.22.0"
}
},
{
"identity" : "swift-nio-ssl",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio-ssl.git",
"state" : {
"revision" : "52a486ff6de9bc3e26bf634c5413c41c5fa89ca5",
"version" : "2.17.2"
}
},
{
"identity" : "swift-nio-transport-services",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio-transport-services.git",
"state" : {
"revision" : "8ab824b140d0ebcd87e9149266ddc353e3705a3e",
"version" : "1.11.4"
}
},
{
"identity" : "swift-protobuf",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-protobuf.git",
"state" : {
"revision" : "e1499bc69b9040b29184f7f2996f7bab467c1639",
"version" : "1.19.0"
}
},
{
"identity" : "zcash-light-client-ffi",
"kind" : "remoteSourceControl",
"location" : "https://github.com/zcash-hackworks/zcash-light-client-ffi",
"state" : {
"revision" : "4043e011f4d6a39b7671d48ca4a54c0085603b76",
"version" : "0.1.0-beta.1"
}
}
],
"version" : 2
} }

View File

@ -16,6 +16,7 @@ enum DemoAppConfig {
static var birthdayHeight: BlockHeight = ZcashSDK.isMainnet ? 935000 : 1386000 static var birthdayHeight: BlockHeight = ZcashSDK.isMainnet ? 935000 : 1386000
static var seed = 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 var seed = 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 var address: String { static var address: String {
"\(host):\(port)" "\(host):\(port)"
} }

View File

@ -28,11 +28,13 @@ class GetUTXOsViewController: UIViewController {
self.transparentAddressLabel.text = tAddress self.transparentAddressLabel.text = tAddress
// swiftlint:disable:next force_try Task { @MainActor in
let balance = try! synchronizer.getTransparentBalance(accountIndex: 0) // swiftlint:disable:next force_try
let balance = try! await AppDelegate.shared.sharedSynchronizer.getTransparentBalance(accountIndex: 0)
self.totalBalanceLabel.text = NumberFormatter.zcashNumberFormatter.string(from: NSNumber(value: balance.total.amount))
self.verifiedBalanceLabel.text = NumberFormatter.zcashNumberFormatter.string(from: NSNumber(value: balance.verified.amount)) self.totalBalanceLabel.text = NumberFormatter.zcashNumberFormatter.string(from: NSNumber(value: balance.total.amount))
self.verifiedBalanceLabel.text = NumberFormatter.zcashNumberFormatter.string(from: NSNumber(value: balance.verified.amount))
}
} }
@IBAction func shieldFunds(_ sender: Any) { @IBAction func shieldFunds(_ sender: Any) {

View File

@ -33,30 +33,27 @@ class SendViewController: UIViewController {
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
synchronizer = AppDelegate.shared.sharedSynchronizer synchronizer = AppDelegate.shared.sharedSynchronizer
// swiftlint:disable:next force_try
_ = try! synchronizer.prepare(with: DemoAppConfig.seed)
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(viewTapped(_:))) let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(viewTapped(_:)))
self.view.addGestureRecognizer(tapRecognizer) self.view.addGestureRecognizer(tapRecognizer)
setUp() setUp()
Task { @MainActor in
// swiftlint:disable:next force_try
try! await synchronizer.prepare()
}
} }
override func viewDidAppear(_ animated: Bool) { override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated) super.viewDidAppear(animated)
do { Task { @MainActor in
try synchronizer.start(retry: false) do {
self.synchronizerStatusLabel.text = SDKSynchronizer.textFor(state: synchronizer.status) try await synchronizer.start(retry: false)
} catch { self.synchronizerStatusLabel.text = SDKSynchronizer.textFor(state: synchronizer.status)
self.synchronizerStatusLabel.text = SDKSynchronizer.textFor(state: synchronizer.status) } catch {
fail(error) self.synchronizerStatusLabel.text = SDKSynchronizer.textFor(state: synchronizer.status)
fail(error)
}
} }
} }
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
// swiftlint:disable:next force_try
try! synchronizer.stop() // Fail on purpose if this throws.
}
@objc func viewTapped(_ recognizer: UITapGestureRecognizer) { @objc func viewTapped(_ recognizer: UITapGestureRecognizer) {
let point = recognizer.location(in: self.view) let point = recognizer.location(in: self.view)

View File

@ -31,7 +31,9 @@ class SyncBlocksViewController: UIViewController {
// swiftlint:disable:next force_try // swiftlint:disable:next force_try
_ = try! wallet.initialize(with: DemoAppConfig.seed) _ = try! wallet.initialize(with: DemoAppConfig.seed)
processor = CompactBlockProcessor(initializer: wallet) processor = CompactBlockProcessor(initializer: wallet)
statusLabel.text = textFor(state: processor?.state.getState() ?? .stopped) Task { @MainActor in
statusLabel.text = textFor(state: await processor?.state ?? .stopped)
}
progressBar.progress = 0 progressBar.progress = 0
NotificationCenter.default.addObserver( NotificationCenter.default.addObserver(
@ -47,14 +49,16 @@ class SyncBlocksViewController: UIViewController {
NotificationCenter.default.removeObserver(self) NotificationCenter.default.removeObserver(self)
guard let processor = self.processor else { return } guard let processor = self.processor else { return }
processor.stop() Task {
await processor.stop()
}
} }
@objc func processorNotification(_ notification: Notification) { @objc func processorNotification(_ notification: Notification) {
DispatchQueue.main.async { Task { @MainActor in
guard self.processor != nil else { return } guard self.processor != nil else { return }
self.updateUI() await self.updateUI()
switch notification.name { switch notification.name {
case let not where not == Notification.Name.blockProcessorUpdated: case let not where not == Notification.Name.blockProcessorUpdated:
@ -70,30 +74,28 @@ class SyncBlocksViewController: UIViewController {
@IBAction func startStop() { @IBAction func startStop() {
guard let processor = processor else { return } guard let processor = processor else { return }
switch processor.state.getState() { Task { @MainActor in
case .stopped: switch await processor.state {
startProcessor() case .stopped:
default: await startProcessor()
stopProcessor() default:
await stopProcessor()
}
} }
} }
func startProcessor() { func startProcessor() async {
guard let processor = processor else { return } guard let processor = processor else { return }
do { await processor.start()
try processor.start() await updateUI()
updateUI()
} catch {
fail(error: error)
}
} }
func stopProcessor() { func stopProcessor() async {
guard let processor = processor else { return } guard let processor = processor else { return }
processor.stop() await processor.stop()
updateUI() await updateUI()
} }
func fail(error: Error) { func fail(error: Error) {
@ -110,11 +112,13 @@ class SyncBlocksViewController: UIViewController {
) )
self.present(alert, animated: true, completion: nil) self.present(alert, animated: true, completion: nil)
updateUI() Task { @MainActor in
await updateUI()
}
} }
func updateUI() { func updateUI() async {
guard let state = processor?.state.getState() else { return } guard let state = await processor?.state else { return }
statusLabel.text = textFor(state: state) statusLabel.text = textFor(state: state)
startPause.setTitle(buttonText(for: state), for: .normal) startPause.setTitle(buttonText(for: state), for: .normal)

View File

@ -16,7 +16,7 @@ extension CompactBlockProcessor {
) async throws { ) async throws {
try Task.checkCancellation() try Task.checkCancellation()
setState(.downloading) state = .downloading
var buffer: [ZcashCompactBlock] = [] var buffer: [ZcashCompactBlock] = []
var targetHeightInternal: BlockHeight? var targetHeightInternal: BlockHeight?

View File

@ -56,8 +56,8 @@ extension CompactBlockProcessor {
try Task.checkCancellation() try Task.checkCancellation()
LoggerProxy.debug("Started Enhancing range: \(range)") LoggerProxy.debug("Started Enhancing range: \(range)")
setState(.enhancing) state = .enhancing
let blockRange = range.blockRange() let blockRange = range.blockRange()
var retries = 0 var retries = 0
let maxRetries = 5 let maxRetries = 5

View File

@ -103,7 +103,7 @@ public enum CompactBlockProgress {
} }
protocol EnhancementStreamDelegate: AnyObject { protocol EnhancementStreamDelegate: AnyObject {
func transactionEnhancementProgressUpdated(_ progress: EnhancementProgress) func transactionEnhancementProgressUpdated(_ progress: EnhancementProgress) async
} }
public protocol EnhancementProgress { public protocol EnhancementProgress {
@ -213,7 +213,7 @@ public extension Notification.Name {
/// The compact block processor is in charge of orchestrating the download and caching of compact blocks from a LightWalletEndpoint /// The compact block processor is in charge of orchestrating the download and caching of compact blocks from a LightWalletEndpoint
/// when started the processor downloads does a download - validate - scan cycle until it reaches latest height on the blockchain. /// when started the processor downloads does a download - validate - scan cycle until it reaches latest height on the blockchain.
public class CompactBlockProcessor { public actor CompactBlockProcessor {
/// Compact Block Processor configuration /// Compact Block Processor configuration
/// ///
@ -312,51 +312,13 @@ public class CompactBlockProcessor {
case synced case synced
} }
// TODO: this isn't an Actor even though it looks like a good candidate, the reason: public internal(set) var state: State = .stopped {
// `state` lives in both sync and async environments. An Actor is demanding async context only didSet {
// so we can't take the advantage unless we encapsulate all `state` reads/writes to async context. transitionState(from: oldValue, to: self.state)
// Therefore solution with class + lock works for us butr eventually will be replaced.
// The future of CompactBlockProcessor is an actor (we won't need to encapsulate the state separately), issue 523,
// https://github.com/zcash/ZcashLightClientKit/issues/523
public class ThreadSafeState {
private var state: State = .stopped
let lock = NSLock()
func setState(_ newState: State) {
lock.lock()
defer { lock.unlock() }
state = newState
}
public func getState() -> State {
lock.lock()
defer { lock.unlock() }
return state
} }
} }
public internal(set) var state = ThreadSafeState() private var needsToStartScanningWhenStopped = false
/// This will be gone when processor becomes actor.
public class ThreadSafeNeedsToStartScanning {
private var needsToStartScanning = false
let lock = NSLock()
func set(_ newValue: Bool) {
lock.lock()
defer { lock.unlock() }
needsToStartScanning = newValue
}
public func get() -> Bool {
lock.lock()
defer { lock.unlock() }
return needsToStartScanning
}
}
private let needsToStartScanningWhenStopped = ThreadSafeNeedsToStartScanning()
var config: Configuration { var config: Configuration {
willSet { willSet {
self.stop() self.stop()
@ -368,7 +330,7 @@ public class CompactBlockProcessor {
} }
var shouldStart: Bool { var shouldStart: Bool {
switch self.state.getState() { switch self.state {
case .stopped, .synced, .error: case .stopped, .synced, .error:
return !maxAttemptsReached return !maxAttemptsReached
default: default:
@ -406,7 +368,7 @@ public class CompactBlockProcessor {
/// - storage: concrete implementation of `CompactBlockStorage` protocol /// - storage: concrete implementation of `CompactBlockStorage` protocol
/// - backend: a class that complies to `ZcashRustBackendWelding` /// - backend: a class that complies to `ZcashRustBackendWelding`
/// - config: `Configuration` struct for this processor /// - config: `Configuration` struct for this processor
convenience init( init(
service: LightWalletService, service: LightWalletService,
storage: CompactBlockStorage, storage: CompactBlockStorage,
backend: ZcashRustBackendWelding.Type, backend: ZcashRustBackendWelding.Type,
@ -427,7 +389,7 @@ public class CompactBlockProcessor {
/// Initializes a CompactBlockProcessor instance from an Initialized object /// Initializes a CompactBlockProcessor instance from an Initialized object
/// - Parameters: /// - Parameters:
/// - initializer: an instance that complies to CompactBlockDownloading protocol /// - initializer: an instance that complies to CompactBlockDownloading protocol
public convenience init(initializer: Initializer) { public init(initializer: Initializer) {
self.init( self.init(
service: initializer.lightWalletService, service: initializer.lightWalletService,
storage: initializer.storage, storage: initializer.storage,
@ -468,12 +430,6 @@ public class CompactBlockProcessor {
cancelableTask?.cancel() cancelableTask?.cancel()
} }
func setState(_ newState: State) {
let oldValue = state.getState()
state.setState(newState)
transitionState(from: oldValue, to: newState)
}
static func validateServerInfo( static func validateServerInfo(
_ info: LightWalletdInfo, _ info: LightWalletdInfo,
saplingActivation: BlockHeight, saplingActivation: BlockHeight,
@ -520,7 +476,7 @@ public class CompactBlockProcessor {
/// triggers the blockProcessorStartedDownloading notification /// triggers the blockProcessorStartedDownloading notification
/// ///
/// - Important: subscribe to the notifications before calling this method /// - Important: subscribe to the notifications before calling this method
public func start(retry: Bool = false) throws { public func start(retry: Bool = false) async {
if retry { if retry {
self.retryAttempts = 0 self.retryAttempts = 0
self.processingError = nil self.processingError = nil
@ -529,12 +485,12 @@ public class CompactBlockProcessor {
} }
guard shouldStart else { guard shouldStart else {
switch self.state.getState() { switch self.state {
case .error(let e): case .error(let e):
// max attempts have been reached // max attempts have been reached
LoggerProxy.info("max retry attempts reached with error: \(e)") LoggerProxy.info("max retry attempts reached with error: \(e)")
notifyError(CompactBlockProcessorError.maxAttemptsReached(attempts: self.maxAttempts)) notifyError(CompactBlockProcessorError.maxAttemptsReached(attempts: self.maxAttempts))
setState(.stopped) state = .stopped
case .stopped: case .stopped:
// max attempts have been reached // max attempts have been reached
LoggerProxy.info("max retry attempts reached") LoggerProxy.info("max retry attempts reached")
@ -545,12 +501,12 @@ public class CompactBlockProcessor {
notifyError(CompactBlockProcessorError.maxAttemptsReached(attempts: self.maxAttempts)) notifyError(CompactBlockProcessorError.maxAttemptsReached(attempts: self.maxAttempts))
case .downloading, .validating, .scanning, .enhancing, .fetching: case .downloading, .validating, .scanning, .enhancing, .fetching:
LoggerProxy.debug("Warning: compact block processor was started while busy!!!!") LoggerProxy.debug("Warning: compact block processor was started while busy!!!!")
needsToStartScanningWhenStopped.set(true) self.`needsToStartScanningWhenStopped` = true
} }
return return
} }
self.nextBatch() await self.nextBatch()
} }
/** /**
@ -571,7 +527,7 @@ public class CompactBlockProcessor {
Rewinds to provided height. Rewinds to provided height.
If nil is provided, it will rescan to nearest height (quick rescan) If nil is provided, it will rescan to nearest height (quick rescan)
*/ */
public func rewindTo(_ height: BlockHeight?) throws -> BlockHeight { public func rewindTo(_ height: BlockHeight?) async throws -> BlockHeight {
self.stop() self.stop()
let lastDownloaded = try downloader.lastDownloadedBlockHeight() let lastDownloaded = try downloader.lastDownloadedBlockHeight()
@ -586,7 +542,7 @@ public class CompactBlockProcessor {
let error = rustBackend.lastError() ?? RustWeldingError.genericError( let error = rustBackend.lastError() ?? RustWeldingError.genericError(
message: "unknown error getting nearest rewind height for height: \(height)" message: "unknown error getting nearest rewind height for height: \(height)"
) )
fail(error) await fail(error)
throw error throw error
} }
@ -594,7 +550,7 @@ public class CompactBlockProcessor {
let rewindHeight = max(Int32(nearestHeight - 1), Int32(config.walletBirthday)) let rewindHeight = max(Int32(nearestHeight - 1), Int32(config.walletBirthday))
guard rustBackend.rewindToHeight(dbData: config.dataDb, height: rewindHeight, networkType: self.config.network.networkType) else { guard rustBackend.rewindToHeight(dbData: config.dataDb, height: rewindHeight, networkType: self.config.network.networkType) else {
let error = rustBackend.lastError() ?? RustWeldingError.genericError(message: "unknown error rewinding to height \(height)") let error = rustBackend.lastError() ?? RustWeldingError.genericError(message: "unknown error rewinding to height \(height)")
fail(error) await fail(error)
throw error throw error
} }
@ -611,7 +567,7 @@ public class CompactBlockProcessor {
- Throws CompactBlockProcessorError.invalidConfiguration if block height is invalid or if processor is already started - Throws CompactBlockProcessorError.invalidConfiguration if block height is invalid or if processor is already started
*/ */
func setStartHeight(_ startHeight: BlockHeight) throws { func setStartHeight(_ startHeight: BlockHeight) throws {
guard self.state.getState() == .stopped, startHeight >= config.network.constants.saplingActivationHeight else { guard self.state == .stopped, startHeight >= config.network.constants.saplingActivationHeight else {
throw CompactBlockProcessorError.invalidConfiguration throw CompactBlockProcessorError.invalidConfiguration
} }
@ -620,32 +576,29 @@ public class CompactBlockProcessor {
self.config = config self.config = config
} }
func validateServer(completionBlock: @escaping (() -> Void)) { func validateServer() async {
Task { @MainActor in do {
do { let info = try await self.service.getInfo()
let info = try await self.service.getInfo() try Self.validateServerInfo(
try Self.validateServerInfo( info,
info, saplingActivation: self.config.saplingActivation,
saplingActivation: self.config.saplingActivation, localNetwork: self.config.network,
localNetwork: self.config.network, rustBackend: self.rustBackend
rustBackend: self.rustBackend )
) } catch let error as LightWalletServiceError {
completionBlock() self.severeFailure(error.mapToProcessorError())
} catch let error as LightWalletServiceError { } catch {
self.severeFailure(error.mapToProcessorError()) self.severeFailure(error)
} catch {
self.severeFailure(error)
}
} }
} }
/// Processes new blocks on the given range based on the configuration set for this instance /// Processes new blocks on the given range based on the configuration set for this instance
func processNewBlocks(range: CompactBlockRange) { func processNewBlocks(range: CompactBlockRange) async {
self.foundBlocks = true self.foundBlocks = true
self.backoffTimer?.invalidate() self.backoffTimer?.invalidate()
self.backoffTimer = nil self.backoffTimer = nil
cancelableTask = Task(priority: .userInitiated) { [weak self] in cancelableTask = Task(priority: .userInitiated) {
do { do {
try await compactBlockStreamDownload( try await compactBlockStreamDownload(
blockBufferSize: config.downloadBufferSize, blockBufferSize: config.downloadBufferSize,
@ -657,13 +610,13 @@ public class CompactBlockProcessor {
try await compactBlockEnhancement(range: range) try await compactBlockEnhancement(range: range)
try await fetchUnspentTxOutputs(range: range) try await fetchUnspentTxOutputs(range: range)
} catch { } catch {
if Task.isCancelled { if !(Task.isCancelled) {
setState(.stopped) await fail(error)
if self?.needsToStartScanningWhenStopped.get() ?? false {
self?.nextBatch()
}
} else { } else {
fail(error) state = .stopped
if needsToStartScanningWhenStopped {
await nextBatch()
}
} }
} }
} }
@ -682,7 +635,7 @@ public class CompactBlockProcessor {
LoggerProxy.debug("progress: \(progress)") LoggerProxy.debug("progress: \(progress)")
NotificationCenter.default.post( NotificationCenter.default.mainThreadPost(
name: Notification.Name.blockProcessorUpdated, name: Notification.Name.blockProcessorUpdated,
object: self, object: self,
userInfo: userInfo userInfo: userInfo
@ -690,7 +643,7 @@ public class CompactBlockProcessor {
} }
func notifyTransactions(_ txs: [ConfirmedTransactionEntity], in range: BlockRange) { func notifyTransactions(_ txs: [ConfirmedTransactionEntity], in range: BlockRange) {
NotificationCenter.default.post( NotificationCenter.default.mainThreadPost(
name: .blockProcessorFoundTransactions, name: .blockProcessorFoundTransactions,
object: self, object: self,
userInfo: [ userInfo: [
@ -715,29 +668,29 @@ public class CompactBlockProcessor {
self.backoffTimer?.invalidate() self.backoffTimer?.invalidate()
self.retryAttempts = config.retries self.retryAttempts = config.retries
self.processingError = error self.processingError = error
setState(.error(error)) state = .error(error)
self.notifyError(error) self.notifyError(error)
} }
func fail(_ error: Error) { func fail(_ error: Error) async {
// todo specify: failure // todo specify: failure
LoggerProxy.error("\(error)") LoggerProxy.error("\(error)")
cancelableTask?.cancel() cancelableTask?.cancel()
self.retryAttempts += 1 self.retryAttempts += 1
self.processingError = error self.processingError = error
switch self.state.getState() { switch self.state {
case .error: case .error:
notifyError(error) notifyError(error)
default: default:
break break
} }
setState(.error(error)) state = .error(error)
guard self.maxAttemptsReached else { return } guard self.maxAttemptsReached else { return }
// don't set a new timer if there are no more attempts. // don't set a new timer if there are no more attempts.
self.setTimer() await self.setTimer()
} }
func retryProcessing(range: CompactBlockRange) { func retryProcessing(range: CompactBlockRange) async {
cancelableTask?.cancel() cancelableTask?.cancel()
// update retries // update retries
self.retryAttempts += 1 self.retryAttempts += 1
@ -752,10 +705,9 @@ public class CompactBlockProcessor {
try downloader.rewind(to: max(range.lowerBound, self.config.walletBirthday)) try downloader.rewind(to: max(range.lowerBound, self.config.walletBirthday))
// process next batch // process next batch
// processNewBlocks(range: Self.nextBatchBlockRange(latestHeight: latestBlockHeight, latestDownloadedHeight: try downloader.lastDownloadedBlockHeight(), walletBirthday: config.walletBirthday)) await nextBatch()
nextBatch()
} catch { } catch {
self.fail(error) await self.fail(error)
} }
} }
@ -790,41 +742,39 @@ public class CompactBlockProcessor {
} }
} }
private func nextBatch() { private func nextBatch() async {
setState(.downloading) state = .downloading
Task { @MainActor [self] in do {
do { let nextState = try await NextStateHelper.nextStateAsync(
let nextState = try await NextStateHelper.nextStateAsync( service: self.service,
service: self.service, downloader: self.downloader,
downloader: self.downloader, config: self.config,
config: self.config, rustBackend: self.rustBackend
rustBackend: self.rustBackend )
switch nextState {
case .finishProcessing(let height):
self.latestBlockHeight = height
await self.processingFinished(height: height)
case .processNewBlocks(let range):
self.latestBlockHeight = range.upperBound
self.lowerBoundHeight = range.lowerBound
await self.processNewBlocks(range: range)
case let .wait(latestHeight, latestDownloadHeight):
// Lightwalletd might be syncing
self.lowerBoundHeight = latestDownloadHeight
self.latestBlockHeight = latestHeight
LoggerProxy.info(
"Lightwalletd might be syncing: latest downloaded block height is: \(latestDownloadHeight)" +
"while latest blockheight is reported at: \(latestHeight)"
) )
switch nextState { await self.processingFinished(height: latestDownloadHeight)
case .finishProcessing(let height):
self.latestBlockHeight = height
self.processingFinished(height: height)
case .processNewBlocks(let range):
self.latestBlockHeight = range.upperBound
self.lowerBoundHeight = range.lowerBound
self.processNewBlocks(range: range)
case let .wait(latestHeight, latestDownloadHeight):
// Lightwalletd might be syncing
self.lowerBoundHeight = latestDownloadHeight
self.latestBlockHeight = latestHeight
LoggerProxy.info(
"Lightwalletd might be syncing: latest downloaded block height is: \(latestDownloadHeight)" +
"while latest blockheight is reported at: \(latestHeight)"
)
self.processingFinished(height: latestDownloadHeight)
}
} catch {
self.severeFailure(error)
} }
} catch {
self.severeFailure(error)
} }
} }
internal func validationFailed(at height: BlockHeight) { internal func validationFailed(at height: BlockHeight) async {
// cancel all Tasks // cancel all Tasks
cancelableTask?.cancel() cancelableTask?.cancel()
@ -840,7 +790,7 @@ public class CompactBlockProcessor {
) )
guard rustBackend.rewindToHeight(dbData: config.dataDb, height: Int32(rewindHeight), networkType: self.config.network.networkType) else { guard rustBackend.rewindToHeight(dbData: config.dataDb, height: Int32(rewindHeight), networkType: self.config.network.networkType) else {
fail(rustBackend.lastError() ?? RustWeldingError.genericError(message: "unknown error rewinding to height \(height)")) await fail(rustBackend.lastError() ?? RustWeldingError.genericError(message: "unknown error rewinding to height \(height)"))
return return
} }
@ -848,7 +798,7 @@ public class CompactBlockProcessor {
try downloader.rewind(to: rewindHeight) try downloader.rewind(to: rewindHeight)
// notify reorg // notify reorg
NotificationCenter.default.post( NotificationCenter.default.mainThreadPost(
name: Notification.Name.blockProcessorHandledReOrg, name: Notification.Name.blockProcessorHandledReOrg,
object: self, object: self,
userInfo: [ userInfo: [
@ -857,15 +807,15 @@ public class CompactBlockProcessor {
) )
// process next batch // process next batch
self.nextBatch() await self.nextBatch()
} catch { } catch {
self.fail(error) await self.fail(error)
} }
} }
internal func processBatchFinished(range: CompactBlockRange) { internal func processBatchFinished(range: CompactBlockRange) async {
guard processingError == nil else { guard processingError == nil else {
retryProcessing(range: range) await retryProcessing(range: range)
return return
} }
@ -873,15 +823,15 @@ public class CompactBlockProcessor {
consecutiveChainValidationErrors = 0 consecutiveChainValidationErrors = 0
guard !range.isEmpty else { guard !range.isEmpty else {
processingFinished(height: range.upperBound) await processingFinished(height: range.upperBound)
return return
} }
nextBatch() await nextBatch()
} }
private func processingFinished(height: BlockHeight) { private func processingFinished(height: BlockHeight) async {
NotificationCenter.default.post( NotificationCenter.default.mainThreadPost(
name: Notification.Name.blockProcessorFinished, name: Notification.Name.blockProcessorFinished,
object: self, object: self,
userInfo: [ userInfo: [
@ -889,40 +839,38 @@ public class CompactBlockProcessor {
CompactBlockProcessorNotificationKey.foundBlocks: self.foundBlocks CompactBlockProcessorNotificationKey.foundBlocks: self.foundBlocks
] ]
) )
setState(.synced) state = .synced
setTimer() await setTimer()
NotificationCenter.default.post( NotificationCenter.default.mainThreadPost(
name: Notification.Name.blockProcessorIdle, name: Notification.Name.blockProcessorIdle,
object: self, object: self,
userInfo: nil userInfo: nil
) )
} }
private func setTimer() { private func setTimer() async {
let interval = self.config.blockPollInterval let interval = self.config.blockPollInterval
self.backoffTimer?.invalidate() self.backoffTimer?.invalidate()
let timer = Timer( let timer = Timer(
timeInterval: interval, timeInterval: interval,
repeats: true, repeats: true,
block: { [weak self] _ in block: { [weak self] _ in
guard let self = self else { return } Task { [self] in
do { guard let self = self else { return }
if self.shouldStart { if await self.shouldStart {
LoggerProxy.debug( LoggerProxy.debug(
""" """
Timer triggered: Starting compact Block processor!. Timer triggered: Starting compact Block processor!.
Processor State: \(self.state) Processor State: \(await self.state)
latestHeight: \(self.latestBlockHeight) latestHeight: \(await self.latestBlockHeight)
attempts: \(self.retryAttempts) attempts: \(await self.retryAttempts)
lowerbound: \(String(describing: self.lowerBoundHeight)) lowerbound: \(String(describing: await self.lowerBoundHeight))
""" """
) )
try self.start() await self.start()
} else if self.maxAttemptsReached { } else if await self.maxAttemptsReached {
self.fail(CompactBlockProcessorError.maxAttemptsReached(attempts: self.config.retries)) await self.fail(CompactBlockProcessorError.maxAttemptsReached(attempts: self.config.retries))
} }
} catch {
self.fail(error)
} }
} }
) )
@ -936,7 +884,7 @@ public class CompactBlockProcessor {
return return
} }
NotificationCenter.default.post( NotificationCenter.default.mainThreadPost(
name: .blockProcessorStatusChanged, name: .blockProcessorStatusChanged,
object: self, object: self,
userInfo: [ userInfo: [
@ -947,27 +895,27 @@ public class CompactBlockProcessor {
switch newValue { switch newValue {
case .downloading: case .downloading:
NotificationCenter.default.post(name: Notification.Name.blockProcessorStartedDownloading, object: self) NotificationCenter.default.mainThreadPost(name: Notification.Name.blockProcessorStartedDownloading, object: self)
case .synced: case .synced:
// transition to this state is handled by `processingFinished(height: BlockHeight)` // transition to this state is handled by `processingFinished(height: BlockHeight)`
break break
case .error(let err): case .error(let err):
notifyError(err) notifyError(err)
case .scanning: case .scanning:
NotificationCenter.default.post(name: Notification.Name.blockProcessorStartedScanning, object: self) NotificationCenter.default.mainThreadPost(name: Notification.Name.blockProcessorStartedScanning, object: self)
case .stopped: case .stopped:
NotificationCenter.default.post(name: Notification.Name.blockProcessorStopped, object: self) NotificationCenter.default.mainThreadPost(name: Notification.Name.blockProcessorStopped, object: self)
case .validating: case .validating:
NotificationCenter.default.post(name: Notification.Name.blockProcessorStartedValidating, object: self) NotificationCenter.default.mainThreadPost(name: Notification.Name.blockProcessorStartedValidating, object: self)
case .enhancing: case .enhancing:
NotificationCenter.default.post(name: Notification.Name.blockProcessorStartedEnhancing, object: self) NotificationCenter.default.mainThreadPost(name: Notification.Name.blockProcessorStartedEnhancing, object: self)
case .fetching: case .fetching:
NotificationCenter.default.post(name: Notification.Name.blockProcessorStartedFetching, object: self) NotificationCenter.default.mainThreadPost(name: Notification.Name.blockProcessorStartedFetching, object: self)
} }
} }
private func notifyError(_ err: Error) { private func notifyError(_ err: Error) {
NotificationCenter.default.post( NotificationCenter.default.mainThreadPost(
name: Notification.Name.blockProcessorFailed, name: Notification.Name.blockProcessorFailed,
object: self, object: self,
userInfo: [CompactBlockProcessorNotificationKey.error: mapError(err)] userInfo: [CompactBlockProcessorNotificationKey.error: mapError(err)]
@ -1171,7 +1119,7 @@ extension CompactBlockProcessorError: LocalizedError {
extension CompactBlockProcessor: EnhancementStreamDelegate { extension CompactBlockProcessor: EnhancementStreamDelegate {
func transactionEnhancementProgressUpdated(_ progress: EnhancementProgress) { func transactionEnhancementProgressUpdated(_ progress: EnhancementProgress) {
NotificationCenter.default.post( NotificationCenter.default.mainThreadPost(
name: .blockProcessorEnhancementProgress, name: .blockProcessorEnhancementProgress,
object: self, object: self,
userInfo: [CompactBlockProcessorNotificationKey.enhancementProgress: progress] userInfo: [CompactBlockProcessorNotificationKey.enhancementProgress: progress]

View File

@ -12,7 +12,7 @@ extension CompactBlockProcessor {
func compactBlockBatchScanning(range: CompactBlockRange) async throws { func compactBlockBatchScanning(range: CompactBlockRange) async throws {
try Task.checkCancellation() try Task.checkCancellation()
setState(.scanning) state = .scanning
// TODO: remove this arbitrary batch size https://github.com/zcash/ZcashLightClientKit/issues/576 // TODO: remove this arbitrary batch size https://github.com/zcash/ZcashLightClientKit/issues/576
let batchSize = scanBatchSize(for: range, network: self.config.network.networkType) let batchSize = scanBatchSize(for: range, network: self.config.network.networkType)
@ -26,7 +26,7 @@ extension CompactBlockProcessor {
throw error throw error
} }
let scanFinishTime = Date() let scanFinishTime = Date()
NotificationCenter.default.post( NotificationCenter.default.mainThreadPostNotification(
SDKMetrics.progressReportNotification( SDKMetrics.progressReportNotification(
progress: BlockProgress( progress: BlockProgress(
startHeight: range.lowerBound, startHeight: range.lowerBound,
@ -70,7 +70,7 @@ extension CompactBlockProcessor {
if scannedNewBlocks { if scannedNewBlocks {
let progress = BlockProgress(startHeight: scanStartHeight, targetHeight: targetScanHeight, progressHeight: lastScannedHeight) let progress = BlockProgress(startHeight: scanStartHeight, targetHeight: targetScanHeight, progressHeight: lastScannedHeight)
notifyProgress(.scan(progress)) notifyProgress(.scan(progress))
NotificationCenter.default.post( NotificationCenter.default.mainThreadPostNotification(
SDKMetrics.progressReportNotification( SDKMetrics.progressReportNotification(
progress: progress, progress: progress,
start: scanStartTime, start: scanStartTime,
@ -83,9 +83,11 @@ extension CompactBlockProcessor {
let seconds = scanFinishTime.timeIntervalSinceReferenceDate - scanStartTime.timeIntervalSinceReferenceDate let seconds = scanFinishTime.timeIntervalSinceReferenceDate - scanStartTime.timeIntervalSinceReferenceDate
LoggerProxy.debug("Scanned \(heightCount) blocks in \(seconds) seconds") LoggerProxy.debug("Scanned \(heightCount) blocks in \(seconds) seconds")
} }
await Task.yield()
} while !Task.isCancelled && scannedNewBlocks && lastScannedHeight < targetScanHeight } while !Task.isCancelled && scannedNewBlocks && lastScannedHeight < targetScanHeight
if Task.isCancelled { if Task.isCancelled {
setState(.stopped) state = .stopped
LoggerProxy.debug("Warning: compactBlockBatchScanning cancelled") LoggerProxy.debug("Warning: compactBlockBatchScanning cancelled")
} }
} }

View File

@ -17,7 +17,7 @@ extension CompactBlockProcessor {
func compactBlockValidation() async throws { func compactBlockValidation() async throws {
try Task.checkCancellation() try Task.checkCancellation()
setState(.validating) state = .validating
let result = rustBackend.validateCombinedChain(dbCache: config.cacheDb, dbData: config.dataDb, networkType: config.network.networkType) let result = rustBackend.validateCombinedChain(dbCache: config.cacheDb, dbData: config.dataDb, networkType: config.network.networkType)
@ -30,7 +30,7 @@ extension CompactBlockProcessor {
case ZcashRustBackendWeldingConstants.validChain: case ZcashRustBackendWeldingConstants.validChain:
if Task.isCancelled { if Task.isCancelled {
setState(.stopped) state = .stopped
LoggerProxy.debug("Warning: compactBlockValidation cancelled") LoggerProxy.debug("Warning: compactBlockValidation cancelled")
} }
LoggerProxy.debug("validateChainFinished") LoggerProxy.debug("validateChainFinished")
@ -50,11 +50,11 @@ extension CompactBlockProcessor {
switch validationError { switch validationError {
case .validationFailed(let height): case .validationFailed(let height):
LoggerProxy.debug("chain validation at height: \(height)") LoggerProxy.debug("chain validation at height: \(height)")
validationFailed(at: height) await validationFailed(at: height)
case .failedWithError(let err): case .failedWithError(let err):
guard let validationFailure = err else { guard let validationFailure = err else {
LoggerProxy.error("validation failed without a specific error") LoggerProxy.error("validation failed without a specific error")
self.fail(CompactBlockProcessorError.generalError(message: "validation failed without a specific error")) await self.fail(CompactBlockProcessorError.generalError(message: "validation failed without a specific error"))
return return
} }

View File

@ -16,7 +16,7 @@ extension CompactBlockProcessor {
func fetchUnspentTxOutputs(range: CompactBlockRange) async throws { func fetchUnspentTxOutputs(range: CompactBlockRange) async throws {
try Task.checkCancellation() try Task.checkCancellation()
setState(.fetching) state = .fetching
do { do {
let tAddresses = try accountRepository.getAll() let tAddresses = try accountRepository.getAll()
@ -57,8 +57,8 @@ extension CompactBlockProcessor {
} }
let result = (inserted: refreshed, skipped: skipped) let result = (inserted: refreshed, skipped: skipped)
NotificationCenter.default.post( NotificationCenter.default.mainThreadPost(
name: .blockProcessorStoredUTXOs, name: .blockProcessorStoredUTXOs,
object: self, object: self,
userInfo: [CompactBlockProcessorNotificationKey.refreshedUTXOs: result] userInfo: [CompactBlockProcessorNotificationKey.refreshedUTXOs: result]
@ -67,7 +67,7 @@ extension CompactBlockProcessor {
if Task.isCancelled { if Task.isCancelled {
LoggerProxy.debug("Warning: fetchUnspentTxOutputs on range \(range) cancelled") LoggerProxy.debug("Warning: fetchUnspentTxOutputs on range \(range) cancelled")
} else { } else {
processBatchFinished(range: range) await processBatchFinished(range: range)
} }
} catch { } catch {
throw error throw error

View File

@ -0,0 +1,30 @@
//
// NotificationCenter+Post.swift
//
//
// Created by Lukáš Korba on 12.10.2022.
//
import Foundation
extension NotificationCenter {
func mainThreadPost(
name aName: NSNotification.Name,
object anObject: Any?,
userInfo aUserInfo: [AnyHashable : Any]? = nil
) {
DispatchQueue.main.async {
NotificationCenter.default.post(
name: aName,
object: anObject,
userInfo: aUserInfo
)
}
}
func mainThreadPostNotification(_ notification: Notification) {
DispatchQueue.main.async {
NotificationCenter.default.post(notification)
}
}
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1812500",
"hash": "00000000008f6e5ac009390e7daa092a3207581c211e2be70b60ce04b88505c3",
"time": 1663471780,
"saplingTree": "0180d672d65ad56265eeb9632dbe62473447bc7bf3e598d2e896cdcb798887b53d00190139d37c3a694b0104d15e447f51d96800d260a5a1053558cd4b7134b8c77f803400017175873eec18949e09f6df019a3d20530716d2307ba1f00fc0c09bae6656022400000198c1229f3df738745986e8aabc1d3ec92944865b43a910b8c74221f99b8ee0320000014c8a851d7fdb90d17b97621005c826c8b3ce8ee463062e324e2ad886f202b6440158720dd7a931f66eac2ff934b0765f807c9504ea318c18f606831c80feb3770401252adc68c56eae8331764a2d9d72851e7352bd33364963b6618f47d56bc21c6900017d8e001c7be91297f727edf47b650f44cbfbebb535b4f409e357b37c43a621040001ba60d34966fcefe1b8058d28b784f87089e693fde8b99902a4fc956021f7ee04017547ef39c632d9bfd0cf48e3e909dbc2e0890f2791a6929ca0d6674484f4c046000190ca77d38f0a408e33a8633f140441488cdedf7508a06d04771b53e4e4de9861019db47a02109d9232f4c4fa044613a8a0cc508865b185ee28318c86d16829895801acc2d2141fe4e5c43c11710e48bd0b12c0493b1721fad3b03470a4400157e020000001f416eb7e062c981dbbf76f8845fda959b948bc742fc62d9edb2f36bae852ba4e01c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
"orchardTree": "01cfc873591e05c184abdc8a1a78659e28df418ea5320a5bd81d2d761c1af5ed0a001f01244feb50d761d34ab22b04045ae9eface10df4b17bbded9d1da9fb71e48cf01400018a07d6cf54e4da1fb1f501850cba2a98ff5a8a8a5722896c3906c09447500f26014be19370bcb96d495a7c1e9719ccc057104a66a5ef6ea217f3fe29e26459131d000172482cc1f1fad25e86299334a24a63e6e86c0b04d319a230bd3aba2dd38279110124de7fbfe4a050ae1427d73491bdddedda1e2e7db48d2365b3cb108eb9833b34000000000000018eb08300830f838c7d212857b7e045a05458264f44f33a9f5d726631634da70201778055c0e5ef68c30c7fa81eb67f0cbf32b64dc5d6ff8138f380cb8f115418260000010fb2cb03705826ca086f605974429cfb0dc60df50a0d15d06c2cfef80017902f01c7146e487b3ae97b190ebf93eac554968e683d31115d13fe83dd620859d9a92d000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1815000",
"hash": "0000000000ff971a219416d483ec48af6e055182ab566572fdda7fedb2d15368",
"time": 1663659973,
"saplingTree": "013010baaf80742269ab7061e012c90e4ef805cc07bb62ef71869fe0badc141f6901d003b1d99e5dfc26894e0b1f393399560374425c34f89853df55a4e32be616251901b34b07fcc9a91eb89bc409a51f452bd21327cfdf3328e9913289760e464824710122ccb843dd5775365b143f0cd1cf309c1940a7c3ba7070c92d041f35b8fc3f4601da8ae93a665218fe154683bb6c75e5ded94f1780358591ce7524ca324f22a53601962ea341367529954ac02e5479c129499aae960e8448ec5697f216b45986b3040168c82ca2a31ca7b833886ee0b3dbb60c519a917b2dfcb6590175b38a0cf17944000196c2704fda3d5e124118cc6cbb1514bbdbdff5bab3a334bda2af27b3e1c7f42a0103bdd297e11f1bbf4d438079aefdc797b012e2e90903194011f9f23cd7ed5b510198ba7189c4d8aeaa58402a655214c69bfa4d09c7818a9691629a8643cca9c91d0000016fbc427c11055083f9330cb82d07c7ce7ca37ee43c7bcc687683f6a50cfdfa3e01201e7639e97113adcb84691cca72efb6f8f44293109ccf02f1beabe46412b75c01046ace0bc25d6c3889da89753306be90ce721bc463e9a6662f3ea10141b5db2801ac5fca146985e234d3ae22d84b09f7924d8baeee82fba8235dd9db60db8f6565000001f513025ec4ad6f392fa842db7cb2f0be10e1bc3662046db7cb6d5de314abe33a01ff0944072256da35186a74026d56db14a2c80ffc4dc7a3b2b5a93885c2622b5201471bdfaa9efa8b9adce9654553a07278f7e37156c73b03394ca8288d8983403001bc76949736399cf6d26d21a468ad6c8ee3e563e0ccef01d615c45a91bc9a612e0001f416eb7e062c981dbbf76f8845fda959b948bc742fc62d9edb2f36bae852ba4e01c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
"orchardTree": "013f012dabdfb54100613c405576e16bbc19a802910fde444e98827f568143ed0c001f010853c8f2d2423d321b9630781d679b884cde7b3160108ddc1233f955e1bd9628000001b8ef98edd02dbc2b74a63778968d66cb1361cc01f7b4434b141ec35c12a2741901c5a9a64d994fbe4c8d044c80f8f551595ff8a33b2bf98857058ab080aa2fbf31000000000116e9a922d1b143d61c100ff15dc2ffdf92e70721aed5a6c37270d7136fb38a1b013dedd8fa148f4930ad5fc0ac019753a21886453af5c94e8f8817a4e13b7ef810000001f69a0bb2a07942810983d469f50f2285c1ba6f0b3355378356cc62a17860cf170175dca061b72ede5cfcb867aa38ce3a37fc84b7586cbeac75d077c5e0eb33b3250001ccf521db485f5532163391b715519775dc86bbb7b938687551a8547a7c81b425010fb2cb03705826ca086f605974429cfb0dc60df50a0d15d06c2cfef80017902f01c7146e487b3ae97b190ebf93eac554968e683d31115d13fe83dd620859d9a92d000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1817500",
"hash": "00000000013757a4fc04d1817f08be13cb68212185a21938235d7f9685ef7133",
"time": 1663848803,
"saplingTree": "012b6c4df5794284a452d6486dc5c3a6e15e7daceed97674fb8990242a07d34a1301de28191ca049c8668eb11fb60a6e2d64b496e2c22620a3a3ee31bc0ab496613c190001ddb3f202a4fd2d677c0e4aa44f8f51ab116a7233f6a620ccbc35e27c4043626d0001eda65f389f39694d0a54c893b3d53d32f03b684c3d182198a934834064ed1302000001b235783e1d216027badf69ee580c0890801d7689b52767f0367ab446f7c2ff4701e5cd3f5440dd9f741ff9c12bf94face46d65d4ad396f08944db4b3b8e282b24601a2d8377a97563fdad5693d7e09b1ba3ac87de413e122fb202c826cc46f6d3b0b0000012945d8546f0aca5aef67fef1bf30e463c2fbb03d90e1f505c223f7170739982a0161bd0e7d1808fcbc370ace2968fb64589b4d5a545fe30f5ed671e38c45859f000001506579c448460360aee9a6da1a0bbba5d040a1f72aac41c4f5b443b0eca8743800016930c8a959bd90b1b62e9802f5cbcc3cb3d870517dbb13e929968e90dbc03966000001b0fe8fd198af091f423e1f6c055671b04a35ee33729cfbf33ad1b83b109a78570001b72a593c1c89f26daa1d1c5f5f3f24737ca0848b67a97b0c3a2cf9a0e163091f01f416eb7e062c981dbbf76f8845fda959b948bc742fc62d9edb2f36bae852ba4e01c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
"orchardTree": "01d74889ff71b2f7a95bb7f4fec78bc0e63148642ea50c3c353a7be5ba25788e2e001f0131419f315d75aea60effee990fee293d35664d941866ad3bdf3efdad9d44bd2e000000013eb62102cf5d6119d411bee8e92c3af1a5337fe11e8c0760920fe9b3ec6ed90d016aacd5fc71a5527c7b9789bb97078c16c9d1444b84a2a2f5a8d5306cece20f0d00011ebb565f5458dc9e3b49f69f64733873eaff2663f5a5ab4f0651a1835b67622800000001c6dedd851cf25be522f8794de3473dc3169d6e620580f11c8458a844f8aea13501ec6da1180a001154528a755ff85883761ab63a8cda1de497045cfe8235e81e260000000000000118b1d1d9f37857fd4b5b4eb5fca3d9246d9af2ff57dc556456777486cd8a943a0001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1820000",
"hash": "0000000000835c776b73524bd19ba4f31e0a2433a1b127d3fc249b76796678b0",
"time": 1664037050,
"saplingTree": "01f32c0a3edd77fb718880da85cb80fd6d700afdcb0c8d941d9cf83fba5af2870200190000012e2a332b86ca3c557c064a8d6e785316c5fa29a8ff0f9f2bf206c2a79273311e00000000000001f97daa24c7111f4d953c4f12ed983e9ff5024ee0bc9b5307cacd62809a63d263012f1870cbe92ff9b955331241ceef9c096782f9deae047f154d45dbe6e06e7a680001bda24aa178c8464c3f7d0f37f8f7b254ab2a28b0ebd0e1cad2b4461585ed650100000001bea76640d0f3a644f7c48d548d61082b1e59888ea45983588ead11372d9a7e220000016ee1f850fb81314c5cb719df85d19a05d71ab03ad0674c290bbedaaaf13f5830011a89f88b2081f74f07b1e3bfb9fa2dad973d38a2778ca9caf804269b1569e51a01b72a593c1c89f26daa1d1c5f5f3f24737ca0848b67a97b0c3a2cf9a0e163091f01f416eb7e062c981dbbf76f8845fda959b948bc742fc62d9edb2f36bae852ba4e01c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e",
"orchardTree": "018b4e53c9b54665efb59b6a574061e4e8c22719e77298207ec59eb450beabc236001f0000000001e571d044e6017197dfadb085a24e0ffb4db345f76802fcc5f855b983ac95ce3b01445284ba4559992af06ab7e0bd62fb6a0586d2c87e2e08538f4e3047fb8f5c270114468166c8102b612e569ed90c38e14d33dc9899974ede1dc1d1a9d537b3f6060140157de80b79ace710090ded467cf09f382c03a57391060b4d80e6caa83ca8290178d8f1f35f97aacd0a3ed6a488eb04a295d068e1101808005a8b991347df0d0e0188ead086968b6763947d155c8607050b9d86fe394563e404a808a4dfd042a21c01da5f438bf911526bc11ab824bf18c29f092a7057650fe30e741c098622fdfe360113696c5ac5b2b94583b6cc6cc01f4be7f9614afcd5a856690ebb18a7b325321f000116ce53cbd2f57591e271c4004c3b7e569383a34c2d0950777b7ee0133b022d05000001db0c50e709ec287e6423e6daa582695335b02a53ad1d5e06bf1499a2ccf5100400000118b1d1d9f37857fd4b5b4eb5fca3d9246d9af2ff57dc556456777486cd8a943a0001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1822500",
"hash": "0000000000a714468e7fbb0e3804f4ca38362f55cb0d9560a4d0c4e82f27643a",
"time": 1664225376,
"saplingTree": "01e55a3c5796b2057c7d4566262467f321bee7a49446831979c2fe7f00c514be2b001a0114f298f871efa88e8e3f0cf75332800066add4957252cb706c3e0a4a4a9cf812000000011f2ad5179ad1500123ba9ff6e4e3495a00aa51974d1185f7d8071eb63de396460001b2dc078bad9b7941458f393f54af4fefbda6648097a0d7db168dbf838318711801d7bf4d62710e875cbd9b376b9f4ef15b01c5e3a190947b9ee8326a4bba7fd00301f95116e45179eeed5481b4fd815ede7458f06ccde88af55c4e9ce8700875731600000001dfc746f32e8af2b7946f316f61f4b381af6a5d32edac92dbdf0832de6f9f430400000001afb52e8a8582fb920a38445d5dac564825de368d2a57d106b7e378427019176c000122761291bf884b6f5ee5c0b224ca06990534a932241ca10b99b7b978ebc33e6d0000000000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
"orchardTree": "01393f7dbe3707eda351d5ab2326369b818f6b6dfc5f81bb5228e085b2c7707323010c2f0639c08029304a03629453ee97d21c2ed7391edab49a77a5ed925a44f9051f000001c7b9e0d295ab3e74b1aafc5adfb2a1c6829108f8c3bf871edc721f2a78c5b40e0001a8758f3d483121113d71c2826f7a26b8a89017df94710146a844731852ba4d1501223105cff5efd73d74fc593f79f08ace789a6d2df4d6e3e98eb229d3cbe135140135e7a09bf3f8ee46559cc3917af50a5252ba98d51302f4958c4dea04e9f29b3e012a097d3bffa77778f5d2ed0bc6455c148f8d9f33b27e53e13ad71e83077ae408018a9af15099d6903002b63e90e7e2759f107dc427fe722a2e8a47caf811af8819018f54abcc86127e81658368df0e582d2f31595bae46b445caa49a4896ea16633b0001f1e08466d0331539a333be73620fc1f9ef3760be0807e8c087f19f8c5609341a010f099166cd67ccd6af73ada73f726b8667c1999ec18d16f32591ec073091ec0000014e69eda5e0a6dae5c0e259edc1a4883ad9f705bbfa67a19e89253c5318bd4f22000001aa1af2205aa6e2d1dbacb3b0ee2113a8bd8ab9bc326e82c1d0f412517b882b2a000118b1d1d9f37857fd4b5b4eb5fca3d9246d9af2ff57dc556456777486cd8a943a0001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1825000",
"hash": "00000000010bcacc0fc5d5c19ad9f7d8ed48611efc59a263e390b7616f874d19",
"time": 1664414144,
"saplingTree": "012e52a9a21c8fcae78103b11f846815a63c33e455347e7813152e9be8b42f6814001a01e67b5e015bbe91da7b4903c9252cf175b64c16b85027ad533357f66d80887a580000012a20a2299b66e1fcd4ca84774727025db6d667c3057198b929490495e4ec355701982e0e26ffa4c89f04e5c463b5499a1eab6566320ee4882602ba9b177b46d05c01b088dad9568f994021da7d4df0708efc28ce412be4006d9249a757d51737d70f000102ac23f0271b5c42a994cdbc0a234e5ef04ad8bb4cd478b770527d646a8f2353000000019870b43adfb257413d26f58805b078ab69bb5cd494b8e94a1d9e5c6843d1486e00000139061f9d2c783f9b9e8575b26a78924295737e997935f0b75c0cf125d1fd7a310000000001505fc602840d05f9586470bb490afbccd86fdf5ac4d0e77e5a9449524dbb732100000000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
"orchardTree": "018c73fb76a5c7d45cb95a5868d4e0464168d48a574824c572e7c1b14092485d33001f01d9f0e6d791cc1843a126113ec3df17afe78c4aa8b272a9a378a2be551477233b00019f01ba3ba2ada7b9fbc9d8f41fd952b19331ff68d5b5697400ab2191d5143939010f84108bf6e3ff0f1e122b52f871296142f56b2fedf210cf4a19d60a35c3ad090000000001624baf89d0e9674562f99abcfcb93dc90139515849e15e2d161b1ecd18a8cd2e016956a06c642fa1ce07638a0034f1621ba90d3745149c2cf71937af51b8487733017a74469c0a99a842c0ab1372538d4f02c2a4aad6bb4e361fc02e36e8e0b25325018503c3bacb212c18b19244328dc6a97f2b8a93e5bd92dd55d7a6dc49d0df642a0000000001f20860c3563401082d950e84fc8735d8925677da13f40f6cd7a38a8c0198d60801aa1af2205aa6e2d1dbacb3b0ee2113a8bd8ab9bc326e82c1d0f412517b882b2a000118b1d1d9f37857fd4b5b4eb5fca3d9246d9af2ff57dc556456777486cd8a943a0001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1827500",
"hash": "00000000002e6ec32020c160a205314eb7646170871ed96e087e21f79d0e211e",
"time": 1664602807,
"saplingTree": "019fb653789bc45e8ef60fe2f657cea80bc835089532652cdcc3ecd637fff37f6d001a00011beea3559174bd9611c5ae3b7492b48bd391a994f418ed56bfae625a2a251b5101abbb8acfc1ea7a2042bc3e022317b05163dd452aa1ebf20dad24fc28c11197170122da960988f7ce15a31ca4d983c99e096c367e8bb937c1784c1b9fe7bc3daa60000124e4eaf466a658118e62307b6e5a8d1f41bdefdd20a312e992060ac8be16866f000000013f2bdccdc5efd952aaad933a00e34af7104b492979988d3a05e7c2460518881000000001b1fcf215ed122ec3fe2a7d939c358571465c971173b178de878c89c6cb969f6000011ed08164a8db56dbd04cad0b6d5cc0db6f2145cc9fc003ccb2094c70a925662500000001505fc602840d05f9586470bb490afbccd86fdf5ac4d0e77e5a9449524dbb732100000000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
"orchardTree": "0128e91d3bea4e0e5075ea8443ff7d9e77f02f2eff009ecdf58782d6202b129114001f0000000001b801335384470e47a439ba9259062f0e914e7cb667fc44ef2af2259dd74a2a1f00016b684c958451f62a81ec79ea5f2ba03b65b2ae0b104706106946d9e396d4553201f7ed8044b12ec079446e47659a8ef170aa7cf0efc323bea8b8a15a336e41941801b4922700d16f0f31fe71f7f0ef427d550795351260b03168725da632d307081a0114b1074e3c2ee621a922a0a60d2aa74573be0e0387d5df7e53c5592b04e5fa0401d5cf6467ad8e2adae024a94ceff61d195410229ce80ae604a8ef66c5c222100d000001cb1bbbee0c509595b73a98bfd941691cddd4432d934207c2d015900dff7d7618000001f20860c3563401082d950e84fc8735d8925677da13f40f6cd7a38a8c0198d60801aa1af2205aa6e2d1dbacb3b0ee2113a8bd8ab9bc326e82c1d0f412517b882b2a000118b1d1d9f37857fd4b5b4eb5fca3d9246d9af2ff57dc556456777486cd8a943a0001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1830000",
"hash": "000000000004def347efa56ae1f4fe002a2a07ad16ca936ff6aeb4f41f3ec0d9",
"time": 1664791147,
"saplingTree": "0178aa88a229972f3f73511e2dbb786855d80fd65f75c0127f3bd4092daaafad0d001a00014d6a6a1d3c1b045c69c680050bc919d86792c2ba284155c198c032d553eb4900019c99d10c165d8221f235330241652a5fb301cdaf2810b15fc420cf914ef9495e0000000109ad606535d23460ec3ec992fa297618128847ff33b05984550e1c47404dbe0900000001cbe7b575c0b2f73f247dbb0cf0f624e465a8afe418d4806fbf1e7855e08fd029000001b1fcf215ed122ec3fe2a7d939c358571465c971173b178de878c89c6cb969f6000011ed08164a8db56dbd04cad0b6d5cc0db6f2145cc9fc003ccb2094c70a925662500000001505fc602840d05f9586470bb490afbccd86fdf5ac4d0e77e5a9449524dbb732100000000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
"orchardTree": "011ad796243f725e67de2145997c30f3def6b7efb15f721327a27790a47773d119001f011da0e512c975812b0923b71f7e43c893d27c70cb2d2d396130d0f516c907aa2c0001d9ca3845b8eff9f9f87091d368282a6147e79b1cdab855d3d77968c78ad3aa0301a0d3959c6722d9dfd0f452629e4696d34167c1dbe0b35f79ba8f13aeb688d00d000106e7a8d22f77f6a3dc8a445beb9aaa29d03212bcf082d2473c4ff99a81596128016b684c958451f62a81ec79ea5f2ba03b65b2ae0b104706106946d9e396d4553201f7ed8044b12ec079446e47659a8ef170aa7cf0efc323bea8b8a15a336e41941801b4922700d16f0f31fe71f7f0ef427d550795351260b03168725da632d307081a0114b1074e3c2ee621a922a0a60d2aa74573be0e0387d5df7e53c5592b04e5fa0401d5cf6467ad8e2adae024a94ceff61d195410229ce80ae604a8ef66c5c222100d000001cb1bbbee0c509595b73a98bfd941691cddd4432d934207c2d015900dff7d7618000001f20860c3563401082d950e84fc8735d8925677da13f40f6cd7a38a8c0198d60801aa1af2205aa6e2d1dbacb3b0ee2113a8bd8ab9bc326e82c1d0f412517b882b2a000118b1d1d9f37857fd4b5b4eb5fca3d9246d9af2ff57dc556456777486cd8a943a0001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1832500",
"hash": "00000000009e1bfb5c6b2bd724c919a18596b29c5ac48a30be29754fcdf001a3",
"time": 1664979856,
"saplingTree": "017d35c8b1fe8353fa65b77775deb2a25db4e77ec8198500f3223a4bc3e64d5231010f05785c5acaf53e4e9586000f41bab3048bde42771298e85867c008107a57261a000119d3ef4ec7b507059f4ca2741c339ea4307d50b9ea42616971c030f522390c4f017bb7a761d76618f986273f4f052493fcff21b58666f0b22b5a978281050967570001c7d62da2c0e245bea72e2cc622f03a7cf05905b1fea13f0a4a3b84bccf6134700001c268a889c7578fc7f6f180169117421eac63a993cb5c4608df4d8bbda061f263017119f88798b428978de3d80bc5520861a9c6c3eaaf0b432eadebaa9f2135da280001a712a6184599a51e8aab9749c1c6cc590ec2e91943016143571a6ef5d136164801dbdd827172d09d214ff210c063240e21e3b326c033771314438ecf82b56fb1490001f8bcc88bbd4826d499fb50b5690ebcbb4bf13e8fa94326d7766985c6fc75e7060170e7688fe48251420d10d5e3c793dcc47d75d7994ebb248831f0cd0743bd7f4d000147c0bdf0b09ebcb0ea103f80e6cbe9b02984c2ec7c0523feec25809d4ad599610127c9b9938654aa0663abda2287bfffe3cc54937995c245d097f740153b65012b0000000115e1f20e49fb7935b31b20ea4ee150567e407cfaac8ccdb01048822e64049228000000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
"orchardTree": "018c23927dda449704b1aebdbd99b8c883fc18dc89b9cf843112596ecefef72a19001f000001c039a9bd13c31443457ee252c77b24a80e12c105a81275af484494d43e254810012a442d12b5b33755b5593a64c6844a9a382dc453944bd7f2901909c0febc071e000189302d6ef7e07804db53b968c9751b8bf9a4780f81d81b3d42ca5b2558541f32012005aa830beee12f6906f73489c2fddee52f1a75278a6ac31f8b2fe678c599380001439e5950a544b0ac1967c73130044b57ffb7bf11fb35126fcc632318f4b8cd0d000001886e2f44043fe89abad72ba1543ccd920ad9455e13d8aeef22f783f757dac92101d9ce8e05739fa694e98cdc0f3e0947944606dc5cbe143e30e90e77725aa5321c0147b69b1780056e107c2169154dfad1afd097fe1c12f61a7e627c3fb627a10d120130f9e5f272c54bff347179b604867bc9fbeac88313c3351f89e55c641acbd33401b8d5dfe4cc3065beafa9b04aa3a8009104f7df8b340169e397d6d59673344d070112c5b16c486f2d31e33b79cbbc4453fe52a373e5d7dad19e606bd21a188e88300001b7c55911e9d02efe6a9644edb48e603699a68a9b370fae484ea8af48772ba9340118b1d1d9f37857fd4b5b4eb5fca3d9246d9af2ff57dc556456777486cd8a943a0001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1835000",
"hash": "0000000001103df2732af30631a2f49c5ab605554e9fe2c38f4651e3291ea9bd",
"time": 1665168048,
"saplingTree": "01074ebb332b5fccfed914dfc1a202b5d2a9ddfc114a1e02c275f5750f54238e0f001a0149e6a8d59b2acb7a2f1d9bae9907cb578b2b15e204db734d869ff01f2dbbdf39015a05e3ce4f28e6d8973dc26e4bf56bd40f8d2f2d97fd66217597bb6084508c0a01648930e7524412f434f42b61b6573edde1a4627c3bbab2648379cff9c886d40b0000014b5219957d0a9a35900e4ffb8ade76b2fe962c4ae4c331ff741ce893234a3f1801283589a038b24c537b3fa1db35951586c1c8f08e0c7e7c9d333fd2b399e108690001a25a1c53d083ba71c776cb1fe70d9dfef04786cac96c640c2c0fbbf833028f560001dde05573b84e569e7dde13ba5524abd8d71cda8c3f242c5f5c320fb581db22330000011e83a98f73bd6bb2fdeee9324c0694b295ea1da95f64abf45398890fd476fe5d016c3ab232b9106e87602df81d84416223e173cdbe325158ee7005fe37cbea293c0001e17cfb9ca8a03e9a702563f4c2cb66c4439498c8ae5846b9a750a597e79b4c6e01843bb589116c1fdda2027c517ee89c434b801d141011844df24147eb040b5d6100019b81ed79d6c2b55e902decee63ed1b1c26df85d9c814a74d4e053aba8f8c36560115e1f20e49fb7935b31b20ea4ee150567e407cfaac8ccdb01048822e64049228000000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
"orchardTree": "01488b69c6ae788b6e70812a1662a86df617e9e1c90ec1103aea126d9b77b1322c001f01af126999194487703ca8c7ac015c2cb228538638c9252504903792ab73f4963901e81d8e5b63692593a5180ad6a0e88b44c89b64188f695f63ede66a23f89c65260121f6819a12feda45cf97805120596d3fac5ac133dbb270d5bba2389c598278380157b4834291d7e1fd8fdfce1c2d2594e0e894da3e763b301cf10be71998781f11016ccf9fabf4a575fd371dcb121aa505531b5e09383f4831cbd1b8a07845e91314019d07c968c2b776bf4a81003640be8e36c2736d8b3109564e8cdba8146c78be1d014e53816914b86c5fedab78f38a29c842471ed8b162c9a8d29baac4fe599cde3b01fe3b2f92c9c33cf1b872df89abe09ede252fd848e5c2a93f08f7de4a64070a2001d4a3bd717758b2f5a13d23d2d88526e723259764b640aa8a0a564fee754061050000011fd00a10523c934360f323e75810931fc2f02423a144323dfeebb3773f93d33e01b61c124cb2dbac265a0a93236793532dc850731022e87eeb335837261b84500000010278ad82f36c3eb59257919611ca46d8f752518954fd259315495cb0d3d39e37017720873c334452d2e25834e13f33a70495462e5bfd35ddc3682a66d35aa6a90900000000019038c488e9bf16e4cbea3f12d056e2db48296d81c075f314b40542b1a21dcd2401020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1837500",
"hash": "00000000000f877e2a531cc93bdd3131bfaf3b0c05c64b225407f29fd09ef4b7",
"time": 1665356771,
"saplingTree": "011e47f9e3c4b031d671196398bf1dcf16b1a136c5d3bff1522622e1a8cf72cd67001a010eab6056ec7a976f9d49e64af89b3968c0b09d1425ef80bc9013bd3ed9174d660102624a22664951600d137e99b0eef3ffac30b10cde98ade7dd72b896c0b2315d01ede04410d2cbbfd48045108b2fca80857331a059bd735621c8b9129a63ce6d6f000001de1b5fe2626fc7faef37e3bbe82d8629cade50e7808d9fdbf91d08e95feeee16000000000116ffa08dc733175d806958ca7043714802b4c7e36479f686ebab9d7971f24c04016a220b9d29ae06cc77a9b288fc6461d54442c6b9a7336b7b0bd6ca602da7e2690001f91d5afaa3997435b65ae3dfdddfc943cbffd40bb480f936bb28120b8503146c0001654a9c702e5e09cfa0bf35e03e2214c53c346006c7ed7fdbedc3459b14000958017ecdd8bafd85940f76c4d4355dd78e76649ff32f18334c813e5053ec57474e1601d91eb3d1fb1b40ddf6cda125ec8f9340d6a804a53fb842f54088eb0d686c3b6a019e925ac0dce1bdb9f4b8564bd9c7e0413c61f2229749bb4fd1aa8cf3c99f7a61019b81ed79d6c2b55e902decee63ed1b1c26df85d9c814a74d4e053aba8f8c36560115e1f20e49fb7935b31b20ea4ee150567e407cfaac8ccdb01048822e64049228000000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
"orchardTree": "013af54fe790ea1acdf876302eca0e919d10d2add04d914fd592e91655c1f69a2d001f01991b7c0d0944a24478dc16c86924171a6203ce543433c5467272962dcc07523201b756ab3d547f0b1150af4992edb67eaa64ca7b48cb3a169b87c09da4bcf4661601e1b4ecfc1855f4cfb68f5eae6734714e1862e1d369bc9f3b00322be5973370090145b404daed53f95be97ea912c067fec2858cbdc09f70143764c611877eae030d011fa2b687d8456263e12b2949b6d079a55d4c55dfb0f85e153a6645707dd9e01301e5f09ecee154fe022b8e8e0bf06c21b39734723b69a952e075eca0a1dc1a611801b13a0640abc81d688c0c55943945ff680ac514ff8ebecef1dcef86e3768cca1400016ea52315040cfe27c15034c18b2a20e8679bee0fa40632d86cec84082310a9160001414c1227bfa2a90214586a740c34143e095ada26e3554225348ebdedd7cf422400013351bfe677ae28702c7083addefe2057f9561857710c0a0be9bbab8de4756a38011a7e755efdaeedcce837096200c5fd636ca4eab9d8d254d88b3a185325ee9b200000000001d83d50c25ac8bd6b3d7ea1683a59b18771c72d85b43641652d84e319d8be720d00019038c488e9bf16e4cbea3f12d056e2db48296d81c075f314b40542b1a21dcd2401020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1840000",
"hash": "0000000000223fa66e8eebb3be09c7bc1e327018c1bfab8bc87a1ac4756b609d",
"time": 1665544594,
"saplingTree": "01857ee7416e6ca3a65c590a912e48648f9ce87ccd1cdb162cde4c8ca5aee2031a001a01e7bb2d86ffb2cf2634fd532e70c529dce92f891ca69d18d32a015f47153f891d015e330e7e1e3214e1cbdd5e85fdfcc39178d74e75a48b4c68dfa36ad9dd8b3005015a1d4f4a559ec1afabbd8e6f88db90b98d609be056c8b9d7c81fd5e63bc5936d016312886e35fd2e65eb758fd077d94d20f6089f53bafab72af633935d3724490301af79b49472899af91e9c6946eee90aac52443a444ebdfa12f1525000916af5570125ff259f5af38bb899502dee5dc34ac14532f847d15a608c5bff2b0e5c4dec24000151360605e535fe2265c67dfe962ecd1b8f071b28177f6ee6ac98369218d1ee4801b984ae125de01fc0f1acc745a7d2ed4237f7c4c6c3ad45ff866ca6954c7bea5f01325df68fb084fa513051a9ebcfabb543055c4e7799b9eb87bf501954e21fcc7100017429a77956ae1275402a408ee286d4927f57ea4073ab53d8f7dcf2f7f760822501b877a72312b8ca0c0d0d00408706e6014d8e291499ba1bb2da7e99167fec49590001b085695ecfea237f02fdd97e66641c19f82146753e23e8d0a50227daf7160e4501654a9c702e5e09cfa0bf35e03e2214c53c346006c7ed7fdbedc3459b14000958017ecdd8bafd85940f76c4d4355dd78e76649ff32f18334c813e5053ec57474e1601d91eb3d1fb1b40ddf6cda125ec8f9340d6a804a53fb842f54088eb0d686c3b6a019e925ac0dce1bdb9f4b8564bd9c7e0413c61f2229749bb4fd1aa8cf3c99f7a61019b81ed79d6c2b55e902decee63ed1b1c26df85d9c814a74d4e053aba8f8c36560115e1f20e49fb7935b31b20ea4ee150567e407cfaac8ccdb01048822e64049228000000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
"orchardTree": "01711e3f4649b675556aecdc4d6658edfee8a99ed3dacae944fc7b385206545225001f000169bc9b81ec14c51edc88e8120c0a7a8238e2a46cb50290e42df86446c6a61a0701274bf921b871c04f28cbdc43cc64694a3c2036a19e4a46bb0b423625cb4f9c0d014ccebdc56f1f516893afd6179b6d319c25bf9e83071ffdacdb4e10a249c5c826015bd9fb52cb016acd1726d91cd99a6cfd5aff846be7d58e3dc11499d42fe8fb32019ad2de520c6ab6761b6fbe75dced8c3b81ce839d30409c1d57f00da42f17a11a01a8ed9b5c8fcd08aa77346029799e0925878bb5e63d6abe209685469fae120401000177de1f17aeb5b0df31b6dc3456435bca969ce2765b645ba256ccccde9244fb36012e52dc44175af3cf903cbb53e47a441590b142a3ecf50bab08a214945f1cd13401917e41edc5417a71ef857fef69f81179fc2e8cb782b2467a73dfdc87f5cc283900013be7f41ae067ada4cbfa2e36c364a51e62b552a7a74ac79f4cd093524d4c0c1200000000019194605343086da1d812d5c0ebcbf1b933523e156b4c542d728b573f9a2b51240001e08fd3be67691a163c5d9b379dee67aac66231338d337b1847d414d0405ad109019038c488e9bf16e4cbea3f12d056e2db48296d81c075f314b40542b1a21dcd2401020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1842500",
"hash": "0000000000399a69b0beaa3320c1bfe61dd7f1b52bf4af1d2fab28694588966d",
"time": 1665733194,
"saplingTree": "014bdf33ed5c693e6a988ae5e315f359951f4350c4c9238a118fc52e4fa6df2e42001a000001375fb8ee8454657a07dd02e27cbbc5f2ef316f90c749d613ff3f93721fbb4205019cf68f5059a52c45184065cfdd351d5ccffaa89eab1c9416e5ab41966b11a26f0155d7d639da967d62403f5b60540d2fdc8b618fb0f31b1e40bf05bf7445d0355e0001ec927b4bc50f2b366446ce51699b70f8cd2b6841c08ab138a87e4c2f095f7d4e01d0f7bfa7f5dc2bc9e02da593effc8f7690f9c59ef631c5e970d67982dfefb6400187b091189fc6be68385778ba02e46a9f6beb52ba43e340a3c326d6e8afc1442c0001c36f357664a4f98e64de8a3589df8ba9eab6c1456ad76f009669314478b58544017429a77956ae1275402a408ee286d4927f57ea4073ab53d8f7dcf2f7f760822501b877a72312b8ca0c0d0d00408706e6014d8e291499ba1bb2da7e99167fec49590001b085695ecfea237f02fdd97e66641c19f82146753e23e8d0a50227daf7160e4501654a9c702e5e09cfa0bf35e03e2214c53c346006c7ed7fdbedc3459b14000958017ecdd8bafd85940f76c4d4355dd78e76649ff32f18334c813e5053ec57474e1601d91eb3d1fb1b40ddf6cda125ec8f9340d6a804a53fb842f54088eb0d686c3b6a019e925ac0dce1bdb9f4b8564bd9c7e0413c61f2229749bb4fd1aa8cf3c99f7a61019b81ed79d6c2b55e902decee63ed1b1c26df85d9c814a74d4e053aba8f8c36560115e1f20e49fb7935b31b20ea4ee150567e407cfaac8ccdb01048822e64049228000000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
"orchardTree": "01bcf67b86b55901731268ecf83eb2beb10e6da821b7dfd3935ceb69158aea8917019961f93887986e00acffb5b59ed5d6b1d33a13a1473f0cd2e1f3aca973b22c011f0124ebf2c03ee8a2cfc6124a8815843d8ee7a5859273dec9470296dfe2b2ab1728000001785aaade10d232553ed944f61426321e4745b3d2eeb52688a891d8e901e73204000000000001f3749ea86fc0e833fead5d9909c6bd3ccfb83fdff02760bf3f2c3cdff7c1d4340110136ae02b4a36b3621b64626afa6429b5db7277339095550ccfb8610da2420b01842da7ac0e25d444be6e4daa6e453a318b0714772d1ecf15d90b1ef38f6add34019ac96beaa59b7ef829d5ef4d11ed0ed7e2bc4e51ec6d3eeff2103ad848fdad370000011ab64a94431fc2840d761fb5dbf86378b3ad42eca6f64b12933612902644c334000000000000017912a4e2f4dcabdba64cb381a3df33406db480a480c9f3dcf8a5c509804b313d0000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1845000",
"hash": "0000000000f5a2bc3b7762af88801f728bfcf288f68ac69b2a18b02076ca9794",
"time": 1665921106,
"saplingTree": "019d26046a50688bce14a0bbeb3aa444e22b5066998d42096c9d6eb911f2d9644c014f0e283b0d6e51929d2aa241a7a141927b88e852af11d8026d6b69fab50165561a0000000001aac99b0ff1e97f054ffb6abfe5ec682f8de5da53af12e100fa948ce0e8d7b84101a2a93738fee0ac3b6824b92ea32180489ded0c686a33544fe6650724e2cc0b600141d4dd8f4facb551081238fcc162ef666e29213135ec6465791eef6b08c011350000000001f94055447c1abde88173bebd79f038fde41fe4f68cea5baa49c7ff7ee3039445000000000000000000011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
"orchardTree": "014f38c1948feb179123d524a41a3311b4bc9c8f47b85026ae9ba2f843f7fa7018001f013889806c104d4c33a7b7b0f9258705cf10d76e3137c66537979a817b522a3720010fcc558250f53bc348213cd9d413a3d40d802eea0c9202202454ad7e4b464f29011d42da37e66d92f52554d80379cba8119576caefd4dbfe0a1ecceb089a270639010b6fdc8c06c36e15efe0f1fa69f1938c01814bb687dcd3f7c487d7568dc2e93c014f1ffa059a7ee1a3613a5a89c484bb86e0ddc92274ca0c6bd6f17ed04f32f02f0174fe3be65cfab7ee11e1356574dbcc6d9d6565d893618ed0f7ed9404a6705b0c0175b4f51d12f0b59cb8681c55128d49cc99da012638ad3dc3cc8af874e0261116010b1f7300abb2d54774fd91490b7557808cdb20a221abb6f800e60bf8815f7f1a000179a838662e881000a097029323f10959e6b2ea6bde92a5699c61b91cc7c5031801fd0ef4d4a41eea63029c8d4b10ba03b4731b8d6b74eccc8ef38911184bcbcc1e0111e07e142952f8c5822186f5a2b49cdd82c343f7cd963c72212bec368ccc550d01454191efd5452c67c8131f8b6be5ff8bd2fe281b22bb6208860af93cb1c8eb3801802ed7757badfc6f1770a5eeb3b7de0764c2fa47cfe88935e82e3b96797686130001c85cff1cf283d1185df96957ba7144dd39c1ce3cbe8f2c9751ca7adc1a38d5240001effece4db23774e28a0d2b5a4186344d564bff1fb33b0328640aa7b8deeba1330191e0f307afd0eb763ca3e38e501bb22825457000c7d5e26a1f038da44110480e000000017912a4e2f4dcabdba64cb381a3df33406db480a480c9f3dcf8a5c509804b313d0000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1847500",
"hash": "00000000015111c7f227c28f8388276950a2620ed1e45b5fbd1d630e1d496c34",
"time": 1666110079,
"saplingTree": "01f0f1dc7cc3b35e13ea03581cc9c10551bdc6663f87ece9ff8d2f1401511b993301cb658fc164c9fe6de844c72fc111f0bfdd208653a0b9226e5878a747f4dc6c6a1a000000016bf21d8bd0ceca9cf6deb036fc284b8c331bf926363172edfd61fa95c782ad0c00016f48c06ff0a773902c689003f9ffdefe10329bd957f04c79cf3e18ad4b1224350000000001bc8b862431d75642d34ee03c642dad35c3048582036781f8614d24e8b050d40701f5e7a27e93dd3e86b4cbf4be383df45b1394b9e6cc9bee489a47b775700b034f00018ef950dc76c9a46c255c0b68bfc2e997d7c94b94dc95609da2020ce448ff81160123d4b12c4de65c87bb2b4d7ea7716c279202d8af8a612d6882d72b173a8ce863000000000000011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
"orchardTree": "01efbdf8d91ddbe82aea16f8b95c9b9cffa38ba4bea58feb734ca9fce899096712001f011ade282ecbbf3fc17d4d6479d4fc3d5f761f7038f1f1b5d822587eb442adf720000001529f257d7869d9ece35f6bc089cb4bff0477e7027c8b5e1c2186b206778bd8150123d376274cc690625117ecbea0c3b5910d880331088cb4c450a03427091410360001e410649cda393f50729ebe137bffaf7c559dae2ea82d5712a6cd2b058e330f1401d80ea561a8f13ab5beb9f08c43056ac3a96eccb2270ed11876b4d98587eb7722000154b687eb1d5a6230ec29e587b7aa025cf1ca1c92ceccb31685ce37606c6f611900019859858dd37ae0f62be01bbb976106c7ff5c716c2220e1a674968f88d4a84832000001719bf63ec1d2d116dc1f78e20819408e7e03c0cf7c60ad90180bfb68be32791b015a96543adf51a9938c770417dd7815501251033f1d3fbb08f036cff15615b909000001382eaedd95be3cd7bf424f0b2992735fb542d4dd5a8c8c2e3d76d20e3cd0f6300104e9e9ad2aef9cef887765208a8bca0da58543c1a3bde7bcc88c92280bd2b30b0000017912a4e2f4dcabdba64cb381a3df33406db480a480c9f3dcf8a5c509804b313d0000000000000000"
}

View File

@ -0,0 +1,8 @@
{
"network": "main",
"height": "1850000",
"hash": "0000000000801f2c54b8fbe2f90496ea9dc0c0c992dd44d465c0ad3254d08066",
"time": 1666298056,
"saplingTree": "013778854e8aeb653352d1012fbeec74549c339d74332744820bdb5eb0d173636d001a016550aebcf982e60191e22ae349bfd7af0343e63f52b2d0648090ebc92feb575501c1b5e06a96ff169650015725ab9f1320eaeee71de34ee0b353792a690548d240010a977f9695ccffbc6dd1d8f8add32fb6295104bd4efc0deb7841635657b4f6720000011edc84073fb7e5428d031ef7d2cf71da367b04b641d1beae3d942900e070c3130118edb07e8e3de97cba932b09ca0bb37621aba1047dddb8b0a707751d9077f00201c237a7fab2a1339da31fc1155560929ea6c0a1d15b697908feb2f208665c03220001a43ecd6cd81187462b72251758504ce86cd69b77b2db3707280d354fac9a863e00000161809ba4619dab88fb42a8af5bb1f2b8f6dd5a48bbd8ec37482ff400c9e59f3c010dc073684263cb36b4cc1392af7287bf691433a7018f3778a49478a83d6e39050001d1020dfaef715ebcdab3a87327ea51ff75af24daa0ee45738941c69c2f7d225b0000000000011619f99023a69bb647eab2d2aa1a73c3673c74bb033c3c4930eacda19e6fd93b0000000160272b134ca494b602137d89e528c751c06d3ef4a87a45f33af343c15060cc1e",
"orchardTree": "0142b513693c44e6ee3190620981368e394cf8ff366e174465bc9dd395c2cd7829016a488c69835a1a8443faeef51d678b63171063e53537eced682ca1fb228bfb371f00011f1bf47c60b0bfbfa7855625fc32b2f9c8193d771be77ddae5812deb2e11332d017e11bddb81f36691aed35d7924c571d9720013bc64934abf6b414dda6e8a711901cfcc4f4e287c41afe7dd46df23732f55e1118c062aa48644d3791d5c9774a50601dc1e5280c2619a5e8cbe56de4c943c4fc7892a9d3dc98c3722ea50ae88c6023c0001405cd14f0159b70e8c5cc3af59ef9103dd7d37bae7ace2b53e8565dfeb460f0b014b39bca2a42615434bdac8042f373d5c5874025d99fbf1b34702dd6e46e9c73a01e5744f5c39d2ad4b6c57dee68097bc0ef3148b2865689376a70c6187d0559a26010138877a4bba42daada8e17045a8205cc89993d8167271c83de50c57943fe23c0001545921e1a8e2149f83c0edbcc76f803fd72b55a9554b799a72fcc201b8ac413501e13a71fdf4151f7e1ea31fb333c3a71a40c3488e6c1773f277ecac498a3f00150105012c93c4325d8c302d78ab98b206742504d14354e97a1c4f1b4c7b9804311a01d37e48d4f74c5d13ec873ec09680f556732a223e3173eb9cc377aba8bed86e15000001616c27d1588b1e06f59e6d53898217e11a4a66749d1682168dc4d1af7df7e10a000001ca59317d757278a838b884b284b60aa986c43a1324e394312ad3b2203759651d00017912a4e2f4dcabdba64cb381a3df33406db480a480c9f3dcf8a5c509804b313d0000000000000000"
}

View File

@ -417,7 +417,7 @@ extension LightWalletServiceError {
class ConnectionStatusManager: ConnectivityStateDelegate { class ConnectionStatusManager: ConnectivityStateDelegate {
func connectivityStateDidChange(from oldState: ConnectivityState, to newState: ConnectivityState) { func connectivityStateDidChange(from oldState: ConnectivityState, to newState: ConnectivityState) {
LoggerProxy.event("Connection Changed from \(oldState) to \(newState)") LoggerProxy.event("Connection Changed from \(oldState) to \(newState)")
NotificationCenter.default.post( NotificationCenter.default.mainThreadPost(
name: .blockProcessorConnectivityStateChanged, name: .blockProcessorConnectivityStateChanged,
object: self, object: self,
userInfo: [ userInfo: [

View File

@ -79,7 +79,7 @@ public protocol Synchronizer {
/// prepares this initializer to operate. Initializes the internal state with the given /// prepares this initializer to operate. Initializes the internal state with the given
/// Extended Viewing Keys and a wallet birthday found in the initializer object /// Extended Viewing Keys and a wallet birthday found in the initializer object
func prepare(with seed: [UInt8]?) throws -> Initializer.InitializationResult func prepare(with seed: [UInt8]?) async throws -> Initializer.InitializationResult
///Starts this synchronizer within the given scope. ///Starts this synchronizer within the given scope.
/// ///
@ -93,17 +93,17 @@ public protocol Synchronizer {
/// Gets the sapling shielded address for the given account. /// Gets the sapling shielded address for the given account.
/// - Parameter accountIndex: the optional accountId whose address is of interest. By default, the first account is used. /// - Parameter accountIndex: the optional accountId whose address is of interest. By default, the first account is used.
/// - Returns the address or nil if account index is incorrect /// - Returns the address or nil if account index is incorrect
func getSaplingAddress(accountIndex: Int) -> SaplingAddress? func getSaplingAddress(accountIndex: Int) async -> SaplingAddress?
/// Gets the unified address for the given account. /// Gets the unified address for the given account.
/// - Parameter accountIndex: the optional accountId whose address is of interest. By default, the first account is used. /// - Parameter accountIndex: the optional accountId whose address is of interest. By default, the first account is used.
/// - Returns the address or nil if account index is incorrect /// - Returns the address or nil if account index is incorrect
func getUnifiedAddress(accountIndex: Int) -> UnifiedAddress? func getUnifiedAddress(accountIndex: Int) async -> UnifiedAddress?
/// Gets the transparent address for the given account. /// Gets the transparent address for the given account.
/// - Parameter accountIndex: the optional accountId whose address is of interest. By default, the first account is used. /// - Parameter accountIndex: the optional accountId whose address is of interest. By default, the first account is used.
/// - Returns the address or nil if account index is incorrect /// - Returns the address or nil if account index is incorrect
func getTransparentAddress(accountIndex: Int) -> TransparentAddress? func getTransparentAddress(accountIndex: Int) async -> TransparentAddress?
/// Sends zatoshi. /// Sends zatoshi.
/// - Parameter spendingKey: the `UnifiedSpendingKey` that allows spends to occur. /// - Parameter spendingKey: the `UnifiedSpendingKey` that allows spends to occur.
@ -156,7 +156,7 @@ public protocol Synchronizer {
func allConfirmedTransactions(from transaction: ConfirmedTransactionEntity?, limit: Int) throws -> [ConfirmedTransactionEntity]? func allConfirmedTransactions(from transaction: ConfirmedTransactionEntity?, limit: Int) throws -> [ConfirmedTransactionEntity]?
/// Returns the latest downloaded height from the compact block cache /// Returns the latest downloaded height from the compact block cache
func latestDownloadedHeight() throws -> BlockHeight func latestDownloadedHeight() async throws -> BlockHeight
/// Returns the latest block height from the provided Lightwallet endpoint /// Returns the latest block height from the provided Lightwallet endpoint
@ -164,13 +164,14 @@ public protocol Synchronizer {
/// Returns the latest block height from the provided Lightwallet endpoint /// Returns the latest block height from the provided Lightwallet endpoint
/// Blocking /// Blocking
func latestHeight() throws -> BlockHeight func latestHeight() async throws -> BlockHeight
/// Returns the latests UTXOs for the given address from the specified height on /// Returns the latests UTXOs for the given address from the specified height on
func refreshUTXOs(address: TransparentAddress, from height: BlockHeight) async throws -> RefreshedUTXOs func refreshUTXOs(address: TransparentAddress, from height: BlockHeight) async throws -> RefreshedUTXOs
/// Returns the last stored transparent balance /// Returns the last stored transparent balance
func getTransparentBalance(accountIndex: Int) throws -> WalletBalance func getTransparentBalance(accountIndex: Int) async throws -> WalletBalance
/// Returns the shielded total balance (includes verified and unverified balance) /// Returns the shielded total balance (includes verified and unverified balance)
@available(*, deprecated, message: "This function will be removed soon, use the one returning a `Zatoshi` value instead") @available(*, deprecated, message: "This function will be removed soon, use the one returning a `Zatoshi` value instead")
@ -193,7 +194,7 @@ public protocol Synchronizer {
/// - Throws rewindErrorUnknownArchorHeight when the rewind points to an invalid height /// - Throws rewindErrorUnknownArchorHeight when the rewind points to an invalid height
/// - Throws rewindError for other errors /// - Throws rewindError for other errors
/// - Note rewind does not trigger notifications as a reorg would. You need to restart the synchronizer afterwards /// - Note rewind does not trigger notifications as a reorg would. You need to restart the synchronizer afterwards
func rewind(_ policy: RewindPolicy) throws func rewind(_ policy: RewindPolicy) async throws
} }
public enum SyncStatus: Equatable { public enum SyncStatus: Equatable {

View File

@ -151,15 +151,17 @@ public class SDKSynchronizer: Synchronizer {
deinit { deinit {
NotificationCenter.default.removeObserver(self) NotificationCenter.default.removeObserver(self)
self.blockProcessor.stop() Task { [blockProcessor] in
await blockProcessor.stop()
}
} }
public func prepare(with seed: [UInt8]?) throws -> Initializer.InitializationResult { public func prepare(with seed: [UInt8]?) async throws -> Initializer.InitializationResult {
if case .seedRequired = try self.initializer.initialize(with: seed) { if case .seedRequired = try self.initializer.initialize(with: seed) {
return .seedRequired return .seedRequired
} }
try self.blockProcessor.setStartHeight(initializer.walletBirthday) try await self.blockProcessor.setStartHeight(initializer.walletBirthday)
self.status = .disconnected self.status = .disconnected
return .success return .success
@ -178,10 +180,8 @@ public class SDKSynchronizer: Synchronizer {
return return
case .stopped, .synced, .disconnected, .error: case .stopped, .synced, .disconnected, .error:
do { Task {
try blockProcessor.start(retry: retry) await blockProcessor.start(retry: retry)
} catch {
throw mapError(error)
} }
} }
} }
@ -193,8 +193,10 @@ public class SDKSynchronizer: Synchronizer {
return return
} }
blockProcessor.stop() Task(priority: .high) {
self.status = .stopped await blockProcessor.stop()
self.status = .stopped
}
} }
private func subscribeToProcessorNotifications(_ processor: CompactBlockProcessor) { private func subscribeToProcessorNotifications(_ processor: CompactBlockProcessor) {
@ -308,7 +310,7 @@ public class SDKSynchronizer: Synchronizer {
} }
let currentState = ConnectionState(current) let currentState = ConnectionState(current)
NotificationCenter.default.post( NotificationCenter.default.mainThreadPost(
name: .synchronizerConnectionStateChanged, name: .synchronizerConnectionStateChanged,
object: self, object: self,
userInfo: [ userInfo: [
@ -330,7 +332,7 @@ public class SDKSynchronizer: Synchronizer {
return return
} }
NotificationCenter.default.post( NotificationCenter.default.mainThreadPost(
name: .synchronizerFoundTransactions, name: .synchronizerFoundTransactions,
object: self, object: self,
userInfo: [ userInfo: [
@ -476,7 +478,7 @@ public class SDKSynchronizer: Synchronizer {
let accountIndex = Int(spendingKey.account) let accountIndex = Int(spendingKey.account)
do { do {
let tBalance = try self.getTransparentBalance(accountIndex: accountIndex) 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. // Verify that at least there are funds for the fee. Ideally this logic will be improved by the shielding wallet.
guard tBalance.verified >= self.network.constants.defaultFee(for: self.latestScannedHeight) else { guard tBalance.verified >= self.network.constants.defaultFee(for: self.latestScannedHeight) else {
@ -550,8 +552,8 @@ public class SDKSynchronizer: Synchronizer {
PagedTransactionRepositoryBuilder.build(initializer: initializer, kind: .all) PagedTransactionRepositoryBuilder.build(initializer: initializer, kind: .all)
} }
public func latestDownloadedHeight() throws -> BlockHeight { public func latestDownloadedHeight() async throws -> BlockHeight {
try blockProcessor.downloader.lastDownloadedBlockHeight() try await blockProcessor.downloader.lastDownloadedBlockHeight()
} }
public func latestHeight(result: @escaping (Result<BlockHeight, Error>) -> Void) { public func latestHeight(result: @escaping (Result<BlockHeight, Error>) -> Void) {
@ -565,8 +567,8 @@ public class SDKSynchronizer: Synchronizer {
} }
} }
public func latestHeight() throws -> BlockHeight { public func latestHeight() async throws -> BlockHeight {
try blockProcessor.downloader.latestBlockHeight() try await blockProcessor.downloader.latestBlockHeight()
} }
public func latestUTXOs(address: String) async throws -> [UnspentTransactionOutputEntity] { public func latestUTXOs(address: String) async throws -> [UnspentTransactionOutputEntity] {
@ -610,25 +612,25 @@ public class SDKSynchronizer: Synchronizer {
initializer.getVerifiedBalance(account: accountIndex) initializer.getVerifiedBalance(account: accountIndex)
} }
public func getSaplingAddress(accountIndex: Int) -> SaplingAddress? { public func getSaplingAddress(accountIndex: Int) async -> SaplingAddress? {
blockProcessor.getSaplingAddress(accountIndex: accountIndex) await blockProcessor.getSaplingAddress(accountIndex: accountIndex)
} }
public func getUnifiedAddress(accountIndex: Int) -> UnifiedAddress? { public func getUnifiedAddress(accountIndex: Int) async -> UnifiedAddress? {
blockProcessor.getUnifiedAddress(accountIndex: accountIndex) await blockProcessor.getUnifiedAddress(accountIndex: accountIndex)
} }
public func getTransparentAddress(accountIndex: Int) -> TransparentAddress? { public func getTransparentAddress(accountIndex: Int) async -> TransparentAddress? {
blockProcessor.getTransparentAddress(accountIndex: accountIndex) await blockProcessor.getTransparentAddress(accountIndex: accountIndex)
} }
/// Returns the last stored transparent balance /// Returns the last stored transparent balance
public func getTransparentBalance(accountIndex: Int) throws -> WalletBalance { public func getTransparentBalance(accountIndex: Int) async throws -> WalletBalance {
try blockProcessor.getTransparentBalance(accountIndex: accountIndex) try await blockProcessor.getTransparentBalance(accountIndex: accountIndex)
} }
public func rewind(_ policy: RewindPolicy) throws { public func rewind(_ policy: RewindPolicy) async throws {
self.stop() self.stop()
var height: BlockHeight? var height: BlockHeight?
@ -638,7 +640,7 @@ public class SDKSynchronizer: Synchronizer {
break break
case .birthday: case .birthday:
let birthday = self.blockProcessor.config.walletBirthday let birthday = await self.blockProcessor.config.walletBirthday
height = birthday height = birthday
case .height(let rewindHeight): case .height(let rewindHeight):
@ -652,7 +654,7 @@ public class SDKSynchronizer: Synchronizer {
} }
do { do {
let rewindHeight = try self.blockProcessor.rewindTo(height) let rewindHeight = try await self.blockProcessor.rewindTo(height)
try self.transactionManager.handleReorg(at: rewindHeight) try self.transactionManager.handleReorg(at: rewindHeight)
} catch { } catch {
throw SynchronizerError.rewindError(underlyingError: error) throw SynchronizerError.rewindError(underlyingError: error)
@ -666,11 +668,11 @@ public class SDKSynchronizer: Synchronizer {
userInfo[NotificationKeys.blockHeight] = progress.progressHeight userInfo[NotificationKeys.blockHeight] = progress.progressHeight
self.status = SyncStatus(progress) self.status = SyncStatus(progress)
NotificationCenter.default.post(name: Notification.Name.synchronizerProgressUpdated, object: self, userInfo: userInfo) NotificationCenter.default.mainThreadPost(name: Notification.Name.synchronizerProgressUpdated, object: self, userInfo: userInfo)
} }
private func notifyStatusChange(newValue: SyncStatus, oldValue: SyncStatus) { private func notifyStatusChange(newValue: SyncStatus, oldValue: SyncStatus) {
NotificationCenter.default.post( NotificationCenter.default.mainThreadPost(
name: .synchronizerStatusWillUpdate, name: .synchronizerStatusWillUpdate,
object: self, object: self,
userInfo: userInfo:
@ -684,38 +686,40 @@ public class SDKSynchronizer: Synchronizer {
private func notify(status: SyncStatus) { private func notify(status: SyncStatus) {
switch status { switch status {
case .disconnected: case .disconnected:
NotificationCenter.default.post(name: Notification.Name.synchronizerDisconnected, object: self) NotificationCenter.default.mainThreadPost(name: Notification.Name.synchronizerDisconnected, object: self)
case .stopped: case .stopped:
NotificationCenter.default.post(name: Notification.Name.synchronizerStopped, object: self) NotificationCenter.default.mainThreadPost(name: Notification.Name.synchronizerStopped, object: self)
case .synced: case .synced:
NotificationCenter.default.post( Task {
name: Notification.Name.synchronizerSynced, NotificationCenter.default.mainThreadPost(
object: self, name: Notification.Name.synchronizerSynced,
userInfo: [ object: self,
SDKSynchronizer.NotificationKeys.blockHeight: self.latestScannedHeight, userInfo: [
SDKSynchronizer.NotificationKeys.synchronizerState: SynchronizerState( SDKSynchronizer.NotificationKeys.blockHeight: self.latestScannedHeight,
shieldedBalance: WalletBalance( SDKSynchronizer.NotificationKeys.synchronizerState: SynchronizerState(
verified: initializer.getVerifiedBalance(), shieldedBalance: WalletBalance(
total: initializer.getBalance() verified: initializer.getVerifiedBalance(),
), total: initializer.getBalance()
transparentBalance: (try? self.getTransparentBalance(accountIndex: 0)) ?? WalletBalance.zero, ),
syncStatus: status, transparentBalance: (try? await self.getTransparentBalance(accountIndex: 0)) ?? WalletBalance.zero,
latestScannedHeight: self.latestScannedHeight syncStatus: status,
) latestScannedHeight: self.latestScannedHeight
] )
) ]
)
}
case .unprepared: case .unprepared:
break break
case .downloading: case .downloading:
NotificationCenter.default.post(name: Notification.Name.synchronizerDownloading, object: self) NotificationCenter.default.mainThreadPost(name: Notification.Name.synchronizerDownloading, object: self)
case .validating: case .validating:
NotificationCenter.default.post(name: Notification.Name.synchronizerValidating, object: self) NotificationCenter.default.mainThreadPost(name: Notification.Name.synchronizerValidating, object: self)
case .scanning: case .scanning:
NotificationCenter.default.post(name: Notification.Name.synchronizerScanning, object: self) NotificationCenter.default.mainThreadPost(name: Notification.Name.synchronizerScanning, object: self)
case .enhancing: case .enhancing:
NotificationCenter.default.post(name: Notification.Name.synchronizerEnhancing, object: self) NotificationCenter.default.mainThreadPost(name: Notification.Name.synchronizerEnhancing, object: self)
case .fetching: case .fetching:
NotificationCenter.default.post(name: Notification.Name.synchronizerFetching, object: self) NotificationCenter.default.mainThreadPost(name: Notification.Name.synchronizerFetching, object: self)
case .error(let e): case .error(let e):
self.notifyFailure(e) self.notifyFailure(e)
} }
@ -757,7 +761,7 @@ public class SDKSynchronizer: Synchronizer {
DispatchQueue.main.async { [weak self] in DispatchQueue.main.async { [weak self] in
guard let self = self else { return } guard let self = self else { return }
NotificationCenter.default.post( NotificationCenter.default.mainThreadPost(
name: Notification.Name.synchronizerMinedTransaction, name: Notification.Name.synchronizerMinedTransaction,
object: self, object: self,
userInfo: [NotificationKeys.minedTransaction: transaction] userInfo: [NotificationKeys.minedTransaction: transaction]
@ -807,7 +811,7 @@ public class SDKSynchronizer: Synchronizer {
private func notifyFailure(_ error: Error) { private func notifyFailure(_ error: Error) {
DispatchQueue.main.async { [weak self] in DispatchQueue.main.async { [weak self] in
guard let self = self else { return } guard let self = self else { return }
NotificationCenter.default.post( NotificationCenter.default.mainThreadPost(
name: Notification.Name.synchronizerFailed, name: Notification.Name.synchronizerFailed,
object: self, object: self,
userInfo: [NotificationKeys.error: self.mapError(error)] userInfo: [NotificationKeys.error: self.mapError(error)]

View File

@ -31,16 +31,18 @@ class AdvancedReOrgTests: XCTestCase {
let branchID = "2bb40e60" let branchID = "2bb40e60"
let chainName = "main" let chainName = "main"
let network = DarksideWalletDNetwork() let network = DarksideWalletDNetwork()
override func setUpWithError() throws { override func setUpWithError() throws {
try super.setUpWithError() try super.setUpWithError()
coordinator = try TestCoordinator( wait { [self] in
seed: seedPhrase, self.coordinator = try await TestCoordinator(
walletBirthday: birthday + 50, //don't use an exact birthday, users never do. seed: seedPhrase,
channelProvider: ChannelProvider(), walletBirthday: birthday + 50, //don't use an exact birthday, users never do.
network: network channelProvider: ChannelProvider(),
) network: network
try coordinator.reset(saplingActivation: 663150, branchID: self.branchID, chainName: self.chainName) )
try coordinator.reset(saplingActivation: 663150, branchID: self.branchID, chainName: self.chainName)
}
} }
override func tearDownWithError() throws { override func tearDownWithError() throws {
@ -82,7 +84,7 @@ class AdvancedReOrgTests: XCTestCase {
/// 9. verify that balance equals initial balance /// 9. verify that balance equals initial balance
/// 10. sync up to received_Tx_height + 3 /// 10. sync up to received_Tx_height + 3
/// 11. verify that balance equals initial balance + tx amount /// 11. verify that balance equals initial balance + tx amount
func testReOrgChangesInboundTxMinedHeight() throws { func testReOrgChangesInboundTxMinedHeight() async throws {
hookToReOrgNotification() hookToReOrgNotification()
try FakeChainBuilder.buildChain(darksideWallet: coordinator.service, branchID: branchID, chainName: chainName) try FakeChainBuilder.buildChain(darksideWallet: coordinator.service, branchID: branchID, chainName: chainName)
var shouldContinue = false var shouldContinue = false
@ -100,16 +102,23 @@ class AdvancedReOrgTests: XCTestCase {
var synchronizer: SDKSynchronizer? var synchronizer: SDKSynchronizer?
try coordinator.sync( try await withCheckedThrowingContinuation { continuation in
completion: { synchro in do {
synchronizer = synchro try coordinator.sync(
initialVerifiedBalance = synchro.initializer.getVerifiedBalance() completion: { synchro in
initialTotalBalance = synchro.initializer.getBalance() synchronizer = synchro
preTxExpectation.fulfill() initialVerifiedBalance = synchro.initializer.getVerifiedBalance()
shouldContinue = true initialTotalBalance = synchro.initializer.getBalance()
}, preTxExpectation.fulfill()
error: self.handleError shouldContinue = true
) continuation.resume()
},
error: self.handleError
)
} catch {
continuation.resume(with: .failure(error))
}
}
wait(for: [preTxExpectation], timeout: 10) wait(for: [preTxExpectation], timeout: 10)
@ -131,12 +140,20 @@ class AdvancedReOrgTests: XCTestCase {
var receivedTxTotalBalance = Zatoshi(-1) var receivedTxTotalBalance = Zatoshi(-1)
var receivedTxVerifiedBalance = Zatoshi(-1) var receivedTxVerifiedBalance = Zatoshi(-1)
try coordinator.sync(completion: { synchro in try await withCheckedThrowingContinuation { continuation in
synchronizer = synchro do {
receivedTxVerifiedBalance = synchro.initializer.getVerifiedBalance() try coordinator.sync(completion: { synchro in
receivedTxTotalBalance = synchro.initializer.getBalance() synchronizer = synchro
receivedTxExpectation.fulfill() receivedTxVerifiedBalance = synchro.initializer.getVerifiedBalance()
}, error: self.handleError) receivedTxTotalBalance = synchro.initializer.getBalance()
receivedTxExpectation.fulfill()
continuation.resume()
}, error: self.handleError)
} catch {
continuation.resume(with: .failure(error))
}
}
sleep(2) sleep(2)
wait(for: [receivedTxExpectation], timeout: 10) wait(for: [receivedTxExpectation], timeout: 10)
@ -195,11 +212,21 @@ class AdvancedReOrgTests: XCTestCase {
var afterReorgTxTotalBalance = Zatoshi(-1) var afterReorgTxTotalBalance = Zatoshi(-1)
var afterReorgTxVerifiedBalance = Zatoshi(-1) var afterReorgTxVerifiedBalance = Zatoshi(-1)
try coordinator.sync(completion: { synchronizer in try await withCheckedThrowingContinuation { continuation in
afterReorgTxTotalBalance = synchronizer.initializer.getBalance() do {
afterReorgTxVerifiedBalance = synchronizer.initializer.getVerifiedBalance() try coordinator.sync(
reorgSyncexpectation.fulfill() completion: { synchronizer in
}, error: self.handleError(_:)) afterReorgTxTotalBalance = synchronizer.initializer.getBalance()
afterReorgTxVerifiedBalance = synchronizer.initializer.getVerifiedBalance()
reorgSyncexpectation.fulfill()
continuation.resume()
},
error: self.handleError
)
} catch {
continuation.resume(with: .failure(error))
}
}
/* /*
8. assert that reorg happened at received_Tx_height 8. assert that reorg happened at received_Tx_height
@ -223,11 +250,22 @@ class AdvancedReOrgTests: XCTestCase {
try coordinator.applyStaged(blockheight: reorgedTxheight + 1) try coordinator.applyStaged(blockheight: reorgedTxheight + 1)
sleep(3) sleep(3)
try coordinator.sync(completion: { synchronizer in
finalReorgTxTotalBalance = synchronizer.initializer.getBalance() try await withCheckedThrowingContinuation { continuation in
finalReorgTxVerifiedBalance = synchronizer.initializer.getVerifiedBalance() do {
finalsyncExpectation.fulfill() try coordinator.sync(
}, error: self.handleError(_:)) completion: { synchronizer in
finalReorgTxTotalBalance = synchronizer.initializer.getBalance()
finalReorgTxVerifiedBalance = synchronizer.initializer.getVerifiedBalance()
finalsyncExpectation.fulfill()
continuation.resume()
},
error: self.handleError
)
} catch {
continuation.resume(with: .failure(error))
}
}
wait(for: [finalsyncExpectation], timeout: 5) wait(for: [finalsyncExpectation], timeout: 5)
sleep(3) sleep(3)
@ -1228,7 +1266,7 @@ class AdvancedReOrgTests: XCTestCase {
XCTAssertEqual(coordinator.synchronizer.initializer.getBalance(), initialTotalBalance) XCTAssertEqual(coordinator.synchronizer.initializer.getBalance(), initialTotalBalance)
} }
func testLongSync() throws { func testLongSync() async throws {
hookToReOrgNotification() hookToReOrgNotification()
/* /*
@ -1247,21 +1285,31 @@ class AdvancedReOrgTests: XCTestCase {
/* /*
sync to latest height sync to latest height
*/ */
try coordinator.sync(completion: { _ in try await withCheckedThrowingContinuation { continuation in
firstSyncExpectation.fulfill() do {
}, error: { error in try coordinator.sync(
_ = try? self.coordinator.stop() completion: { _ in
firstSyncExpectation.fulfill() firstSyncExpectation.fulfill()
guard let testError = error else { continuation.resume()
XCTFail("failed with nil error") }, error: { error in
return _ = try? self.coordinator.stop()
firstSyncExpectation.fulfill()
guard let testError = error else {
XCTFail("failed with nil error")
return
}
XCTFail("Failed with error: \(testError)")
}
)
} catch {
continuation.resume(throwing: error)
} }
XCTFail("Failed with error: \(testError)") }
})
wait(for: [firstSyncExpectation], timeout: 500) wait(for: [firstSyncExpectation], timeout: 500)
XCTAssertEqual(try coordinator.synchronizer.latestDownloadedHeight(), birthday + fullSyncLength) let latestDownloadedHeight = try await coordinator.synchronizer.latestDownloadedHeight()
XCTAssertEqual(latestDownloadedHeight, birthday + fullSyncLength)
} }
func handleError(_ error: Error?) { func handleError(_ error: Error?) {

View File

@ -30,13 +30,16 @@ class BalanceTests: XCTestCase {
override func setUpWithError() throws { override func setUpWithError() throws {
try super.setUpWithError() try super.setUpWithError()
coordinator = try TestCoordinator( wait { [self] in
seed: seedPhrase, self.coordinator = try await TestCoordinator(
walletBirthday: birthday, seed: self.seedPhrase,
channelProvider: ChannelProvider(), walletBirthday: self.birthday,
network: network channelProvider: ChannelProvider(),
) network: self.network
try coordinator.reset(saplingActivation: 663150, branchID: "e9ff75a6", chainName: "main") )
try coordinator.reset(saplingActivation: 663150, branchID: "e9ff75a6", chainName: "main")
}
} }
/** /**

View File

@ -31,6 +31,8 @@ class BlockDownloaderTests: XCTestCase {
try FakeChainBuilder.buildChain(darksideWallet: darksideWalletService, branchID: branchID, chainName: chainName) try FakeChainBuilder.buildChain(darksideWallet: darksideWalletService, branchID: branchID, chainName: chainName)
try darksideWalletService.applyStaged(nextLatestHeight: 663250) try darksideWalletService.applyStaged(nextLatestHeight: 663250)
sleep(2)
} }
override func tearDown() { override func tearDown() {

View File

@ -33,14 +33,17 @@ class DarksideSanityCheckTests: XCTestCase {
override func setUpWithError() throws { override func setUpWithError() throws {
try super.setUpWithError() try super.setUpWithError()
coordinator = try TestCoordinator( wait { [self] in
seed: seedPhrase, self.coordinator = try await TestCoordinator(
walletBirthday: birthday, seed: self.seedPhrase,
channelProvider: ChannelProvider(), walletBirthday: self.birthday,
network: network channelProvider: ChannelProvider(),
) network: self.network
try coordinator.reset(saplingActivation: birthday, branchID: branchID, chainName: chainName) )
try coordinator.resetBlocks(dataset: .default)
try self.coordinator.reset(saplingActivation: self.birthday, branchID: self.branchID, chainName: self.chainName)
try self.coordinator.resetBlocks(dataset: .default)
}
} }
override func tearDownWithError() throws { override func tearDownWithError() throws {
@ -74,7 +77,7 @@ class DarksideSanityCheckTests: XCTestCase {
wait(for: [syncExpectation], timeout: 5) wait(for: [syncExpectation], timeout: 5)
let blocksDao = BlockSQLDAO(dbProvider: SimpleConnectionProvider(path: coordinator.databases.dataDB.absoluteString, readonly: true)) let blocksDao = BlockSQLDAO(dbProvider: SimpleConnectionProvider(path: coordinator.databases.dataDB.absoluteString, readonly: false))
let firstBlock = try blocksDao.block(at: expectedFirstBlock.height) let firstBlock = try blocksDao.block(at: expectedFirstBlock.height)
let lastBlock = try blocksDao.block(at: expectedLastBlock.height) let lastBlock = try blocksDao.block(at: expectedLastBlock.height)

View File

@ -31,17 +31,19 @@ class PendingTransactionUpdatesTest: XCTestCase {
let network = DarksideWalletDNetwork() let network = DarksideWalletDNetwork()
override func setUpWithError() throws { override func setUpWithError() throws {
try super.setUpWithError() try super.setUpWithError()
coordinator = try TestCoordinator( wait {
seed: seedPhrase, self.coordinator = try await TestCoordinator(
walletBirthday: birthday, seed: self.seedPhrase,
channelProvider: ChannelProvider(), walletBirthday: self.birthday,
network: network channelProvider: ChannelProvider(),
) network: self.network
try coordinator.reset(saplingActivation: 663150, branchID: "e9ff75a6", chainName: "main") )
try self.coordinator.reset(saplingActivation: 663150, branchID: "e9ff75a6", chainName: "main")
}
} }
override func tearDownWithError() throws { override func tearDownWithError() throws {
try super.tearDownWithError()
NotificationCenter.default.removeObserver(self) NotificationCenter.default.removeObserver(self)
try coordinator.stop() try coordinator.stop()
try? FileManager.default.removeItem(at: coordinator.databases.cacheDB) try? FileManager.default.removeItem(at: coordinator.databases.cacheDB)

View File

@ -47,24 +47,28 @@ class ReOrgTests: XCTestCase {
override func setUpWithError() throws { override func setUpWithError() throws {
try super.setUpWithError() try super.setUpWithError()
NotificationCenter.default.addObserver( wait { [self] in
self, NotificationCenter.default.addObserver(
selector: #selector(handleReOrgNotification(_:)), self,
name: Notification.Name.blockProcessorHandledReOrg, selector: #selector(self.handleReOrgNotification(_:)),
object: nil name: Notification.Name.blockProcessorHandledReOrg,
) object: nil
coordinator = try TestCoordinator( )
seed: seedPhrase,
walletBirthday: birthday, self.coordinator = try await TestCoordinator(
channelProvider: ChannelProvider(), seed: self.seedPhrase,
network: network walletBirthday: self.birthday,
) channelProvider: ChannelProvider(),
try coordinator.reset(saplingActivation: birthday, branchID: branchID, chainName: chainName) network: self.network
try coordinator.resetBlocks(dataset: .default) )
try self.coordinator.reset(saplingActivation: self.birthday, branchID: self.branchID, chainName: self.chainName)
try self.coordinator.resetBlocks(dataset: .default)
}
} }
override func tearDownWithError() throws { override func tearDownWithError() throws {
try super.tearDownWithError()
try? FileManager.default.removeItem(at: coordinator.databases.cacheDB) try? FileManager.default.removeItem(at: coordinator.databases.cacheDB)
try? FileManager.default.removeItem(at: coordinator.databases.dataDB) try? FileManager.default.removeItem(at: coordinator.databases.dataDB)
try? FileManager.default.removeItem(at: coordinator.databases.pendingDB) try? FileManager.default.removeItem(at: coordinator.databases.pendingDB)

View File

@ -34,20 +34,20 @@ class RewindRescanTests: XCTestCase {
override func setUpWithError() throws { override func setUpWithError() throws {
try super.setUpWithError() try super.setUpWithError()
wait { [self] in
self.coordinator = try await TestCoordinator(
seed: self.seedPhrase,
walletBirthday: self.birthday,
channelProvider: ChannelProvider(),
network: self.network
)
coordinator = try TestCoordinator( try self.coordinator.reset(saplingActivation: 663150, branchID: "e9ff75a6", chainName: "main")
seed: seedPhrase, }
walletBirthday: birthday,
channelProvider: ChannelProvider(),
network: network
)
try coordinator.reset(saplingActivation: 663150, branchID: "e9ff75a6", chainName: "main")
} }
override func tearDownWithError() throws { override func tearDownWithError() throws {
try super.tearDownWithError() try super.tearDownWithError()
NotificationCenter.default.removeObserver(self) NotificationCenter.default.removeObserver(self)
try coordinator.stop() try coordinator.stop()
@ -65,7 +65,7 @@ class RewindRescanTests: XCTestCase {
XCTFail("Failed with error: \(testError)") XCTFail("Failed with error: \(testError)")
} }
func testBirthdayRescan() throws { func testBirthdayRescan() async throws {
// 1 sync and get spendable funds // 1 sync and get spendable funds
try FakeChainBuilder.buildChain(darksideWallet: coordinator.service, branchID: branchID, chainName: chainName) try FakeChainBuilder.buildChain(darksideWallet: coordinator.service, branchID: branchID, chainName: chainName)
@ -87,7 +87,7 @@ class RewindRescanTests: XCTestCase {
XCTAssertEqual(verifiedBalance, totalBalance) XCTAssertEqual(verifiedBalance, totalBalance)
// rewind to birthday // rewind to birthday
try coordinator.synchronizer.rewind(.birthday) try await coordinator.synchronizer.rewind(.birthday)
// assert that after the new height is // assert that after the new height is
XCTAssertEqual(try coordinator.synchronizer.initializer.transactionRepository.lastScannedHeight(), self.birthday) XCTAssertEqual(try coordinator.synchronizer.initializer.transactionRepository.lastScannedHeight(), self.birthday)
@ -146,7 +146,7 @@ class RewindRescanTests: XCTestCase {
height: Int32(targetHeight), height: Int32(targetHeight),
networkType: network.networkType networkType: network.networkType
) )
try coordinator.synchronizer.rewind(.height(blockheight: targetHeight)) try await coordinator.synchronizer.rewind(.height(blockheight: targetHeight))
guard rewindHeight > 0 else { guard rewindHeight > 0 else {
XCTFail("get nearest height failed error: \(ZcashRustBackend.getLastError() ?? "null")") XCTFail("get nearest height failed error: \(ZcashRustBackend.getLastError() ?? "null")")
@ -191,7 +191,7 @@ class RewindRescanTests: XCTestCase {
wait(for: [sendExpectation], timeout: 15) wait(for: [sendExpectation], timeout: 15)
} }
func testRescanToTransaction() throws { func testRescanToTransaction() async throws {
// 1 sync and get spendable funds // 1 sync and get spendable funds
try FakeChainBuilder.buildChain(darksideWallet: coordinator.service, branchID: branchID, chainName: chainName) try FakeChainBuilder.buildChain(darksideWallet: coordinator.service, branchID: branchID, chainName: chainName)
@ -217,7 +217,7 @@ class RewindRescanTests: XCTestCase {
return return
} }
try coordinator.synchronizer.rewind(.transaction(transaction.transactionEntity)) try await coordinator.synchronizer.rewind(.transaction(transaction.transactionEntity))
// assert that after the new height is // assert that after the new height is
XCTAssertEqual( XCTAssertEqual(
@ -364,7 +364,7 @@ class RewindRescanTests: XCTestCase {
// rewind 5 blocks prior to sending // rewind 5 blocks prior to sending
try coordinator.synchronizer.rewind(.height(blockheight: sentTxHeight - 5)) try await coordinator.synchronizer.rewind(.height(blockheight: sentTxHeight - 5))
guard guard
let pendingEntity = try coordinator.synchronizer.allPendingTransactions() let pendingEntity = try coordinator.synchronizer.allPendingTransactions()

View File

@ -30,12 +30,14 @@ class ShieldFundsTests: XCTestCase {
override func setUpWithError() throws { override func setUpWithError() throws {
try super.setUpWithError() try super.setUpWithError()
coordinator = try TestCoordinator( Task { @MainActor [self] in
seed: seedPhrase, self.coordinator = try await TestCoordinator(
walletBirthday: birthday, seed: seedPhrase,
channelProvider: ChannelProvider(), walletBirthday: birthday,
network: network channelProvider: ChannelProvider(),
) network: network
)
}
try coordinator.reset(saplingActivation: birthday, branchID: self.branchID, chainName: self.chainName) try coordinator.reset(saplingActivation: birthday, branchID: self.branchID, chainName: self.chainName)
try coordinator.service.clearAddedUTXOs() try coordinator.service.clearAddedUTXOs()
} }
@ -93,7 +95,7 @@ class ShieldFundsTests: XCTestCase {
var shouldContinue = false var shouldContinue = false
var initialTotalBalance = Zatoshi(-1) var initialTotalBalance = Zatoshi(-1)
var initialVerifiedBalance = Zatoshi(-1) var initialVerifiedBalance = Zatoshi(-1)
var initialTransparentBalance: WalletBalance = try coordinator.synchronizer.getTransparentBalance(accountIndex: 0) var initialTransparentBalance: WalletBalance = try await coordinator.synchronizer.getTransparentBalance(accountIndex: 0)
let utxo = try GetAddressUtxosReply(jsonString: """ let utxo = try GetAddressUtxosReply(jsonString: """
{ {
@ -135,7 +137,7 @@ class ShieldFundsTests: XCTestCase {
// at this point the balance should be all zeroes for transparent and shielded funds // at this point the balance should be all zeroes for transparent and shielded funds
XCTAssertEqual(initialTotalBalance, Zatoshi.zero) XCTAssertEqual(initialTotalBalance, Zatoshi.zero)
XCTAssertEqual(initialVerifiedBalance, Zatoshi.zero) XCTAssertEqual(initialVerifiedBalance, Zatoshi.zero)
initialTransparentBalance = try coordinator.synchronizer.getTransparentBalance(accountIndex: 0) initialTransparentBalance = try await coordinator.synchronizer.getTransparentBalance(accountIndex: 0)
XCTAssertEqual(initialTransparentBalance.total, .zero) XCTAssertEqual(initialTransparentBalance.total, .zero)
XCTAssertEqual(initialTransparentBalance.verified, .zero) XCTAssertEqual(initialTransparentBalance.verified, .zero)
@ -169,7 +171,7 @@ class ShieldFundsTests: XCTestCase {
// at this point the balance should be zero for shielded, then zero verified transparent funds // at this point the balance should be zero for shielded, then zero verified transparent funds
// and 10000 zatoshi of total (not verified) transparent funds. // and 10000 zatoshi of total (not verified) transparent funds.
let tFundsDetectedBalance = try coordinator.synchronizer.getTransparentBalance(accountIndex: 0) let tFundsDetectedBalance = try await coordinator.synchronizer.getTransparentBalance(accountIndex: 0)
XCTAssertEqual(tFundsDetectedBalance.total, Zatoshi(10000)) XCTAssertEqual(tFundsDetectedBalance.total, Zatoshi(10000))
XCTAssertEqual(tFundsDetectedBalance.verified, Zatoshi(10000)) //FIXME: this should be zero XCTAssertEqual(tFundsDetectedBalance.verified, Zatoshi(10000)) //FIXME: this should be zero
@ -199,7 +201,7 @@ class ShieldFundsTests: XCTestCase {
wait(for: [tFundsConfirmationSyncExpectation], timeout: 5) wait(for: [tFundsConfirmationSyncExpectation], timeout: 5)
// the transparent funds should be 10000 zatoshis both total and verified // the transparent funds should be 10000 zatoshis both total and verified
let confirmedTFundsBalance = try coordinator.synchronizer.getTransparentBalance(accountIndex: 0) let confirmedTFundsBalance = try await coordinator.synchronizer.getTransparentBalance(accountIndex: 0)
XCTAssertEqual(confirmedTFundsBalance.total, Zatoshi(10000)) XCTAssertEqual(confirmedTFundsBalance.total, Zatoshi(10000))
XCTAssertEqual(confirmedTFundsBalance.verified, Zatoshi(10000)) XCTAssertEqual(confirmedTFundsBalance.verified, Zatoshi(10000))
@ -229,7 +231,7 @@ class ShieldFundsTests: XCTestCase {
guard shouldContinue else { return } guard shouldContinue else { return }
let postShieldingBalance = try coordinator.synchronizer.getTransparentBalance(accountIndex: 0) let postShieldingBalance = try await coordinator.synchronizer.getTransparentBalance(accountIndex: 0)
// when funds are shielded the UTXOs should be marked as spend and not shown on the balance. // when funds are shielded the UTXOs should be marked as spend and not shown on the balance.
// now balance should be zero shielded, zero transaparent. // now balance should be zero shielded, zero transaparent.
// verify that the balance has been marked as spent regardless of confirmation // verify that the balance has been marked as spent regardless of confirmation
@ -280,7 +282,7 @@ class ShieldFundsTests: XCTestCase {
// Now it should verify that the balance has been shielded. The resulting balance should be zero // Now it should verify that the balance has been shielded. The resulting balance should be zero
// transparent funds and `10000 - fee` total shielded funds, zero verified shielded funds. // transparent funds and `10000 - fee` total shielded funds, zero verified shielded funds.
// Fees at the time of writing the tests are 1000 zatoshi as defined on ZIP-313 // Fees at the time of writing the tests are 1000 zatoshi as defined on ZIP-313
let postShieldingShieldedBalance = try coordinator.synchronizer.getTransparentBalance(accountIndex: 0) let postShieldingShieldedBalance = try await coordinator.synchronizer.getTransparentBalance(accountIndex: 0)
XCTAssertEqual(postShieldingShieldedBalance.total, Zatoshi(10000)) //FIXME: this should be zero XCTAssertEqual(postShieldingShieldedBalance.total, Zatoshi(10000)) //FIXME: this should be zero
XCTAssertEqual(postShieldingShieldedBalance.verified, Zatoshi(10000)) //FIXME: this should be zero XCTAssertEqual(postShieldingShieldedBalance.verified, Zatoshi(10000)) //FIXME: this should be zero
@ -317,7 +319,7 @@ class ShieldFundsTests: XCTestCase {
XCTAssertNotNil(clearedTransaction) XCTAssertNotNil(clearedTransaction)
XCTAssertEqual(coordinator.synchronizer.getShieldedBalance(), Zatoshi(9000)) XCTAssertEqual(coordinator.synchronizer.getShieldedBalance(), Zatoshi(9000))
let postShieldingConfirmationShieldedBalance = try coordinator.synchronizer.getTransparentBalance(accountIndex: 0) let postShieldingConfirmationShieldedBalance = try await coordinator.synchronizer.getTransparentBalance(accountIndex: 0)
XCTAssertEqual(postShieldingConfirmationShieldedBalance.total, .zero) XCTAssertEqual(postShieldingConfirmationShieldedBalance.total, .zero)
XCTAssertEqual(postShieldingConfirmationShieldedBalance.verified, .zero) XCTAssertEqual(postShieldingConfirmationShieldedBalance.verified, .zero)

View File

@ -34,13 +34,16 @@ class SychronizerDarksideTests: XCTestCase {
override func setUpWithError() throws { override func setUpWithError() throws {
try super.setUpWithError() try super.setUpWithError()
coordinator = try TestCoordinator( wait { [self] in
seed: seedPhrase, self.coordinator = try await TestCoordinator(
walletBirthday: birthday, seed: self.seedPhrase,
channelProvider: ChannelProvider(), walletBirthday: self.birthday,
network: network channelProvider: ChannelProvider(),
) network: self.network
try coordinator.reset(saplingActivation: 663150, branchID: "e9ff75a6", chainName: "main") )
try self.coordinator.reset(saplingActivation: 663150, branchID: "e9ff75a6", chainName: "main")
}
} }
override func tearDownWithError() throws { override func tearDownWithError() throws {

View File

@ -34,13 +34,16 @@ final class SynchronizerTests: XCTestCase {
override func setUpWithError() throws { override func setUpWithError() throws {
try super.setUpWithError() try super.setUpWithError()
coordinator = try TestCoordinator( wait { [self] in
seed: seedPhrase, self.coordinator = try await TestCoordinator(
walletBirthday: birthday + 50, //don't use an exact birthday, users never do. seed: self.seedPhrase,
channelProvider: ChannelProvider(), walletBirthday:self.birthday + 50, //don't use an exact birthday, users never do.
network: network channelProvider: ChannelProvider(),
) network: self.network
try coordinator.reset(saplingActivation: 663150, branchID: self.branchID, chainName: self.chainName) )
try coordinator.reset(saplingActivation: 663150, branchID: self.branchID, chainName: self.chainName)
}
} }
override func tearDownWithError() throws { override func tearDownWithError() throws {
@ -67,7 +70,7 @@ final class SynchronizerTests: XCTestCase {
reorgExpectation.fulfill() reorgExpectation.fulfill()
} }
func testSynchronizerStops() throws { func testSynchronizerStops() async throws {
hookToReOrgNotification() hookToReOrgNotification()
/* /*
@ -102,14 +105,14 @@ final class SynchronizerTests: XCTestCase {
XCTFail("Failed with error: \(testError)") XCTFail("Failed with error: \(testError)")
}) })
DispatchQueue.main.asyncAfter(deadline: .now() + 5) { try await Task.sleep(nanoseconds: 5_000_000_000)
self.coordinator.synchronizer.stop() self.coordinator.synchronizer.stop()
}
wait(for: [processorStoppedExpectation,syncStoppedExpectation], timeout: 6, enforceOrder: true) wait(for: [syncStoppedExpectation, processorStoppedExpectation], timeout: 6, enforceOrder: true)
XCTAssertEqual(coordinator.synchronizer.status, .stopped) XCTAssertEqual(coordinator.synchronizer.status, .stopped)
XCTAssertEqual(coordinator.synchronizer.blockProcessor.state.getState(), .stopped) let state = await coordinator.synchronizer.blockProcessor.state
XCTAssertEqual(state, .stopped)
} }
func handleError(_ error: Error?) { func handleError(_ error: Error?) {

View File

@ -85,8 +85,6 @@ class TransactionEnhancementTests: XCTestCase {
return return
} }
guard case .success = dbInit else { guard case .success = dbInit else {
XCTFail("Failed to initDataDb. Expected `.success` got: \(String(describing: dbInit))") XCTFail("Failed to initDataDb. Expected `.success` got: \(String(describing: dbInit))")
return return
@ -137,7 +135,7 @@ class TransactionEnhancementTests: XCTestCase {
NotificationCenter.default.removeObserver(self) NotificationCenter.default.removeObserver(self)
} }
private func startProcessing() throws { private func startProcessing() async throws {
XCTAssertNotNil(processor) XCTAssertNotNil(processor)
// Subscribe to notifications // Subscribe to notifications
@ -149,10 +147,10 @@ class TransactionEnhancementTests: XCTestCase {
txFoundNotificationExpectation.subscribe(to: .blockProcessorFoundTransactions, object: processor) txFoundNotificationExpectation.subscribe(to: .blockProcessorFoundTransactions, object: processor)
idleNotificationExpectation.subscribe(to: .blockProcessorIdle, object: processor) idleNotificationExpectation.subscribe(to: .blockProcessorIdle, object: processor)
try processor.start() await processor.start()
} }
func testBasicEnhacement() throws { func testBasicEnhancement() async throws {
let targetLatestHeight = BlockHeight(663200) let targetLatestHeight = BlockHeight(663200)
do { do {
@ -181,7 +179,7 @@ class TransactionEnhancementTests: XCTestCase {
download and sync blocks from walletBirthday to firstLatestHeight download and sync blocks from walletBirthday to firstLatestHeight
*/ */
do { do {
try startProcessing() try await startProcessing()
} catch { } catch {
XCTFail("Error: \(error)") XCTFail("Error: \(error)")
} }

View File

@ -30,20 +30,20 @@ class Z2TReceiveTests: XCTestCase {
override func setUpWithError() throws { override func setUpWithError() throws {
try super.setUpWithError() try super.setUpWithError()
wait { [self] in
self.coordinator = try await TestCoordinator(
seed: self.seedPhrase,
walletBirthday: self.birthday,
channelProvider: ChannelProvider(),
network: self.network
)
coordinator = try TestCoordinator( try coordinator.reset(saplingActivation: 663150, branchID: self.branchID, chainName: self.chainName)
seed: seedPhrase, }
walletBirthday: birthday,
channelProvider: ChannelProvider(),
network: network
)
try coordinator.reset(saplingActivation: 663150, branchID: self.branchID, chainName: self.chainName)
} }
override func tearDownWithError() throws { override func tearDownWithError() throws {
try super.tearDownWithError() try super.tearDownWithError()
NotificationCenter.default.removeObserver(self) NotificationCenter.default.removeObserver(self)
try coordinator.stop() try coordinator.stop()

View File

@ -87,7 +87,7 @@ class BlockScanTests: XCTestCase {
range: range range: range
) )
XCTAssertFalse(Task.isCancelled) XCTAssertFalse(Task.isCancelled)
try compactBlockProcessor.compactBlockScanning( try await compactBlockProcessor.compactBlockScanning(
rustWelding: rustWelding, rustWelding: rustWelding,
cacheDb: cacheDbURL, cacheDb: cacheDbURL,
dataDb: dataDbURL, dataDb: dataDbURL,

View File

@ -79,7 +79,7 @@ class BlockStreamingTest: XCTestCase {
startHeight: startHeight startHeight: startHeight
) )
} catch { } catch {
XCTAssertNotNil(error as? CancellationError) XCTAssertTrue(Task.isCancelled)
} }
} }

View File

@ -102,7 +102,7 @@ class CompactBlockProcessorTests: XCTestCase {
} }
} }
private func startProcessing() { private func startProcessing() async {
XCTAssertNotNil(processor) XCTAssertNotNil(processor)
// Subscribe to notifications // Subscribe to notifications
@ -113,12 +113,16 @@ class CompactBlockProcessorTests: XCTestCase {
startedScanningNotificationExpectation.subscribe(to: Notification.Name.blockProcessorStartedScanning, object: processor) startedScanningNotificationExpectation.subscribe(to: Notification.Name.blockProcessorStartedScanning, object: processor)
idleNotificationExpectation.subscribe(to: Notification.Name.blockProcessorIdle, object: processor) idleNotificationExpectation.subscribe(to: Notification.Name.blockProcessorIdle, object: processor)
XCTAssertNoThrow(try processor.start()) do {
try await processor.start()
} catch {
XCTFail("shouldn't fail")
}
} }
// FIXME: disabled see https://github.com/zcash/ZcashLightClientKit/issues/590 // FIXME: disabled see https://github.com/zcash/ZcashLightClientKit/issues/590
func testStartNotifiesSuscriptors() { func testStartNotifiesSuscriptors() async {
startProcessing() await startProcessing()
wait( wait(
for: [ for: [
@ -133,7 +137,7 @@ class CompactBlockProcessorTests: XCTestCase {
} }
// FIXME: disabled see https://github.com/zcash/ZcashLightClientKit/issues/590 // FIXME: disabled see https://github.com/zcash/ZcashLightClientKit/issues/590
func testProgressNotifications() { func testProgressNotifications() async {
let expectedUpdates = expectedBatches( let expectedUpdates = expectedBatches(
currentHeight: processorConfig.walletBirthday, currentHeight: processorConfig.walletBirthday,
targetHeight: mockLatestHeight, targetHeight: mockLatestHeight,
@ -141,7 +145,7 @@ class CompactBlockProcessorTests: XCTestCase {
) )
updatedNotificationExpectation.expectedFulfillmentCount = expectedUpdates updatedNotificationExpectation.expectedFulfillmentCount = expectedUpdates
startProcessing() await startProcessing()
wait(for: [updatedNotificationExpectation], timeout: 300) wait(for: [updatedNotificationExpectation], timeout: 300)
} }
@ -197,23 +201,23 @@ class CompactBlockProcessorTests: XCTestCase {
) )
} }
func testDetermineLowerBoundPastBirthday() { func testDetermineLowerBoundPastBirthday() async {
let errorHeight = 781_906 let errorHeight = 781_906
let walletBirthday = 781_900 let walletBirthday = 781_900
let result = processor.determineLowerBound(errorHeight: errorHeight, consecutiveErrors: 1, walletBirthday: walletBirthday) let result = await processor.determineLowerBound(errorHeight: errorHeight, consecutiveErrors: 1, walletBirthday: walletBirthday)
let expected = 781_886 let expected = 781_886
XCTAssertEqual(result, expected) XCTAssertEqual(result, expected)
} }
func testDetermineLowerBound() { func testDetermineLowerBound() async {
let errorHeight = 781_906 let errorHeight = 781_906
let walletBirthday = 780_900 let walletBirthday = 780_900
let result = processor.determineLowerBound(errorHeight: errorHeight, consecutiveErrors: 0, walletBirthday: walletBirthday) let result = await processor.determineLowerBound(errorHeight: errorHeight, consecutiveErrors: 0, walletBirthday: walletBirthday)
let expected = 781_896 let expected = 781_896
XCTAssertEqual(result, expected) XCTAssertEqual(result, expected)

View File

@ -0,0 +1,167 @@
//
// CompactBlockReorgTests.swift
// ZcashLightClientKit-Unit-Tests
//
// Created by Francisco Gindre on 11/13/19.
//
// Copyright © 2019 Electric Coin Company. All rights reserved.
import XCTest
@testable import TestUtils
@testable import ZcashLightClientKit
// swiftlint:disable implicitly_unwrapped_optional force_try
class CompactBlockReorgTests: XCTestCase {
let processorConfig = CompactBlockProcessor.Configuration.standard(
for: ZcashNetworkBuilder.network(for: .testnet),
walletBirthday: ZcashNetworkBuilder.network(for: .testnet).constants.saplingActivationHeight
)
var processor: CompactBlockProcessor!
var downloadStartedExpect: XCTestExpectation!
var updatedNotificationExpectation: XCTestExpectation!
var stopNotificationExpectation: XCTestExpectation!
var startedScanningNotificationExpectation: XCTestExpectation!
var startedValidatingNotificationExpectation: XCTestExpectation!
var idleNotificationExpectation: XCTestExpectation!
var reorgNotificationExpectation: XCTestExpectation!
let network = ZcashNetworkBuilder.network(for: .testnet)
let mockLatestHeight = ZcashNetworkBuilder.network(for: .testnet).constants.saplingActivationHeight + 2000
override func setUpWithError() throws {
try super.setUpWithError()
logger = SampleLogger(logLevel: .debug)
let service = MockLightWalletService(
latestBlockHeight: mockLatestHeight,
service: LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.eccTestnet)
)
let branchID = try ZcashRustBackend.consensusBranchIdFor(height: Int32(mockLatestHeight), networkType: network.networkType)
service.mockLightDInfo = LightdInfo.with { info in
info.blockHeight = UInt64(mockLatestHeight)
info.branch = "asdf"
info.buildDate = "today"
info.buildUser = "testUser"
info.chainName = "test"
info.consensusBranchID = branchID.toString()
info.estimatedHeight = UInt64(mockLatestHeight)
info.saplingActivationHeight = UInt64(network.constants.saplingActivationHeight)
}
guard case .success = try ZcashRustBackend.initDataDb(dbData: processorConfig.dataDb, seed: nil, networkType: .testnet) else {
XCTFail("initDataDb failed. Expected Success but got .seedRequired")
return
}
let storage = CompactBlockStorage.init(connectionProvider: SimpleConnectionProvider(path: processorConfig.cacheDb.absoluteString))
try! storage.createTable()
let mockBackend = MockRustBackend.self
mockBackend.mockValidateCombinedChainFailAfterAttempts = 3
mockBackend.mockValidateCombinedChainKeepFailing = false
mockBackend.mockValidateCombinedChainFailureHeight = self.network.constants.saplingActivationHeight + 320
processor = CompactBlockProcessor(
service: service,
storage: storage,
backend: mockBackend,
config: processorConfig
)
downloadStartedExpect = XCTestExpectation(description: "\(self.description) downloadStartedExpect")
stopNotificationExpectation = XCTestExpectation(description: "\(self.description) stopNotificationExpectation")
updatedNotificationExpectation = XCTestExpectation(description: "\(self.description) updatedNotificationExpectation")
startedValidatingNotificationExpectation = XCTestExpectation(
description: "\(self.description) startedValidatingNotificationExpectation"
)
startedScanningNotificationExpectation = XCTestExpectation(
description: "\(self.description) startedScanningNotificationExpectation"
)
idleNotificationExpectation = XCTestExpectation(description: "\(self.description) idleNotificationExpectation")
reorgNotificationExpectation = XCTestExpectation(description: "\(self.description) reorgNotificationExpectation")
NotificationCenter.default.addObserver(
self,
selector: #selector(processorHandledReorg(_:)),
name: Notification.Name.blockProcessorHandledReOrg,
object: processor
)
NotificationCenter.default.addObserver(
self,
selector: #selector(processorFailed(_:)),
name: Notification.Name.blockProcessorFailed,
object: processor
)
}
override func tearDown() {
super.tearDown()
try! FileManager.default.removeItem(at: processorConfig.cacheDb)
try? FileManager.default.removeItem(at: processorConfig.dataDb)
downloadStartedExpect.unsubscribeFromNotifications()
stopNotificationExpectation.unsubscribeFromNotifications()
updatedNotificationExpectation.unsubscribeFromNotifications()
startedScanningNotificationExpectation.unsubscribeFromNotifications()
startedValidatingNotificationExpectation.unsubscribeFromNotifications()
idleNotificationExpectation.unsubscribeFromNotifications()
reorgNotificationExpectation.unsubscribeFromNotifications()
NotificationCenter.default.removeObserver(self)
}
@objc func processorHandledReorg(_ notification: Notification) {
XCTAssertNotNil(notification.userInfo)
if let reorg = notification.userInfo?[CompactBlockProcessorNotificationKey.reorgHeight] as? BlockHeight,
let rewind = notification.userInfo?[CompactBlockProcessorNotificationKey.rewindHeight] as? BlockHeight {
XCTAssertTrue( reorg == 0 || reorg > self.network.constants.saplingActivationHeight)
XCTAssertTrue( rewind == 0 || rewind > self.network.constants.saplingActivationHeight)
XCTAssertTrue( rewind <= reorg )
reorgNotificationExpectation.fulfill()
} else {
XCTFail("CompactBlockProcessor reorg notification is malformed")
}
}
@objc func processorFailed(_ notification: Notification) {
XCTAssertNotNil(notification.userInfo)
if let error = notification.userInfo?["error"] {
XCTFail("CompactBlockProcessor failed with Error: \(error)")
} else {
XCTFail("CompactBlockProcessor failed")
}
}
private func startProcessing() async {
XCTAssertNotNil(processor)
// Subscribe to notifications
downloadStartedExpect.subscribe(to: Notification.Name.blockProcessorStartedDownloading, object: processor)
stopNotificationExpectation.subscribe(to: Notification.Name.blockProcessorStopped, object: processor)
updatedNotificationExpectation.subscribe(to: Notification.Name.blockProcessorUpdated, object: processor)
startedValidatingNotificationExpectation.subscribe(to: Notification.Name.blockProcessorStartedValidating, object: processor)
startedScanningNotificationExpectation.subscribe(to: Notification.Name.blockProcessorStartedScanning, object: processor)
idleNotificationExpectation.subscribe(to: Notification.Name.blockProcessorFinished, object: processor)
reorgNotificationExpectation.subscribe(to: Notification.Name.blockProcessorHandledReOrg, object: processor)
await processor.start()
}
func testNotifiesReorg() async {
await startProcessing()
wait(
for: [
downloadStartedExpect,
startedValidatingNotificationExpectation,
startedScanningNotificationExpectation,
reorgNotificationExpectation,
idleNotificationExpectation
],
timeout: 300,
enforceOrder: true
)
}
private func expectedBatches(currentHeight: BlockHeight, targetHeight: BlockHeight, batchSize: Int) -> Int {
(abs(currentHeight - targetHeight) / batchSize)
}
}

View File

@ -33,7 +33,7 @@ class WalletTests: XCTestCase {
} }
} }
func testWalletInitialization() throws { func testWalletInitialization() async throws {
let derivationTool = DerivationTool(networkType: network.networkType) let derivationTool = DerivationTool(networkType: network.networkType)
let ufvk = try derivationTool.deriveUnifiedSpendingKey(seed: seedData.bytes, accountIndex: 0) let ufvk = try derivationTool.deriveUnifiedSpendingKey(seed: seedData.bytes, accountIndex: 0)
.map( { try derivationTool.deriveUnifiedFullViewingKey(from: $0) }) .map( { try derivationTool.deriveUnifiedFullViewingKey(from: $0) })
@ -50,14 +50,13 @@ class WalletTests: XCTestCase {
) )
let synchronizer = try SDKSynchronizer(initializer: wallet) let synchronizer = try SDKSynchronizer(initializer: wallet)
do {
var dbInit: Initializer.InitializationResult! guard case .success = try await synchronizer.prepare(with: seedData.bytes) else {
XCTFail("Failed to initDataDb. Expected `.success` got: `.seedRequired`")
XCTAssertNoThrow(try { dbInit = try synchronizer.prepare(with: nil) }()) return
}
guard case .success = dbInit else { } catch {
XCTFail("Failed to initDataDb. Expected `.success` got: \(String(describing: dbInit))") XCTFail("shouldn't fail here")
return
} }
// fileExists actually sucks, so attempting to delete the file and checking what happens is far better :) // fileExists actually sucks, so attempting to delete the file and checking what happens is far better :)

View File

@ -25,18 +25,18 @@ class MockLightWalletService: LightWalletService {
var queue = DispatchQueue(label: "mock service queue") var queue = DispatchQueue(label: "mock service queue")
func blockStream(startHeight: BlockHeight, endHeight: BlockHeight) -> AsyncThrowingStream<ZcashCompactBlock, Error> { func blockStream(startHeight: BlockHeight, endHeight: BlockHeight) -> AsyncThrowingStream<ZcashCompactBlock, Error> {
AsyncThrowingStream { _ in } service.blockStream(startHeight: startHeight, endHeight: endHeight)
} }
func closeConnection() { func closeConnection() {
} }
func fetchUTXOs(for tAddress: String, height: BlockHeight) -> AsyncThrowingStream<UnspentTransactionOutputEntity, Error> { func fetchUTXOs(for tAddress: String, height: BlockHeight) -> AsyncThrowingStream<UnspentTransactionOutputEntity, Error> {
AsyncThrowingStream { _ in } service.fetchUTXOs(for: tAddress, height: height)
} }
func fetchUTXOs(for tAddresses: [String], height: BlockHeight) -> AsyncThrowingStream<UnspentTransactionOutputEntity, Error> { func fetchUTXOs(for tAddresses: [String], height: BlockHeight) -> AsyncThrowingStream<UnspentTransactionOutputEntity, Error> {
AsyncThrowingStream { _ in } service.fetchUTXOs(for: tAddresses, height: height)
} }
private var service: LightWalletService private var service: LightWalletService

View File

@ -50,7 +50,7 @@ class TestCoordinator {
walletBirthday: BlockHeight, walletBirthday: BlockHeight,
channelProvider: ChannelProvider, channelProvider: ChannelProvider,
network: ZcashNetwork network: ZcashNetwork
) throws { ) async throws {
let derivationTool = DerivationTool(networkType: network.networkType) let derivationTool = DerivationTool(networkType: network.networkType)
let spendingKey = try derivationTool.deriveUnifiedSpendingKey( let spendingKey = try derivationTool.deriveUnifiedSpendingKey(
@ -60,7 +60,7 @@ class TestCoordinator {
let ufvk = try derivationTool.deriveUnifiedFullViewingKey(from: spendingKey) let ufvk = try derivationTool.deriveUnifiedFullViewingKey(from: spendingKey)
try self.init( await try self.init(
spendingKey: spendingKey, spendingKey: spendingKey,
unifiedFullViewingKey: ufvk, unifiedFullViewingKey: ufvk,
walletBirthday: walletBirthday, walletBirthday: walletBirthday,
@ -75,7 +75,7 @@ class TestCoordinator {
walletBirthday: BlockHeight, walletBirthday: BlockHeight,
channelProvider: ChannelProvider, channelProvider: ChannelProvider,
network: ZcashNetwork network: ZcashNetwork
) throws { ) async throws {
self.spendingKey = spendingKey self.spendingKey = spendingKey
self.birthday = walletBirthday self.birthday = walletBirthday
self.channelProvider = channelProvider self.channelProvider = channelProvider
@ -93,7 +93,7 @@ class TestCoordinator {
let storage = CompactBlockStorage(url: databases.cacheDB, readonly: false) let storage = CompactBlockStorage(url: databases.cacheDB, readonly: false)
try storage.createTable() try storage.createTable()
let buildResult = try TestSynchronizerBuilder.build( let buildResult = try await TestSynchronizerBuilder.build(
rustBackend: ZcashRustBackend.self, rustBackend: ZcashRustBackend.self,
lowerBoundHeight: self.birthday, lowerBoundHeight: self.birthday,
cacheDbURL: databases.cacheDB, cacheDbURL: databases.cacheDB,
@ -186,6 +186,12 @@ class TestCoordinator {
} }
} }
extension CompactBlockProcessor {
public func setConfig(_ config: Configuration) {
self.config = config
}
}
extension TestCoordinator { extension TestCoordinator {
func resetBlocks(dataset: DarksideData) throws { func resetBlocks(dataset: DarksideData) throws {
switch dataset { switch dataset {
@ -219,19 +225,25 @@ extension TestCoordinator {
} }
func reset(saplingActivation: BlockHeight, branchID: String, chainName: String) throws { func reset(saplingActivation: BlockHeight, branchID: String, chainName: String) throws {
let config = self.synchronizer.blockProcessor.config Task {
await self.synchronizer.blockProcessor.stop()
self.synchronizer.blockProcessor.config = CompactBlockProcessor.Configuration( let config = await self.synchronizer.blockProcessor.config
cacheDb: config.cacheDb,
dataDb: config.dataDb, let newConfig = CompactBlockProcessor.Configuration(
downloadBatchSize: config.downloadBatchSize, cacheDb: config.cacheDb,
retries: config.retries, dataDb: config.dataDb,
maxBackoffInterval: config.maxBackoffInterval, downloadBatchSize: config.downloadBatchSize,
rewindDistance: config.rewindDistance, retries: config.retries,
walletBirthday: config.walletBirthday, maxBackoffInterval: config.maxBackoffInterval,
saplingActivation: config.saplingActivation, rewindDistance: config.rewindDistance,
network: config.network walletBirthday: config.walletBirthday,
) saplingActivation: config.saplingActivation,
network: config.network
)
await self.synchronizer.blockProcessor.setConfig(newConfig)
}
try service.reset(saplingActivation: saplingActivation, branchID: branchID, chainName: chainName) try service.reset(saplingActivation: saplingActivation, branchID: branchID, chainName: chainName)
} }
@ -279,7 +291,7 @@ enum TestSynchronizerBuilder {
network: ZcashNetwork, network: ZcashNetwork,
seed: [UInt8]? = nil, seed: [UInt8]? = nil,
loggerProxy: Logger? = nil loggerProxy: Logger? = nil
) throws -> (spendingKeys: [UnifiedSpendingKey]?, synchronizer: SDKSynchronizer) { ) async throws -> (spendingKeys: [UnifiedSpendingKey]?, synchronizer: SDKSynchronizer) {
let initializer = Initializer( let initializer = Initializer(
cacheDbURL: cacheDbURL, cacheDbURL: cacheDbURL,
dataDbURL: dataDbURL, dataDbURL: dataDbURL,
@ -295,7 +307,7 @@ enum TestSynchronizerBuilder {
) )
let synchronizer = try SDKSynchronizer(initializer: initializer) let synchronizer = try SDKSynchronizer(initializer: initializer)
if case .seedRequired = try synchronizer.prepare(with: seed) { if case .seedRequired = try await synchronizer.prepare(with: seed) {
throw TestCoordinator.CoordinatorError.seedRequiredForMigration throw TestCoordinator.CoordinatorError.seedRequiredForMigration
} }
@ -319,7 +331,7 @@ enum TestSynchronizerBuilder {
walletBirthday: BlockHeight, walletBirthday: BlockHeight,
network: ZcashNetwork, network: ZcashNetwork,
loggerProxy: Logger? = nil loggerProxy: Logger? = nil
) throws -> (spendingKeys: [UnifiedSpendingKey]?, synchronizer: SDKSynchronizer) { ) async throws -> (spendingKeys: [UnifiedSpendingKey]?, synchronizer: SDKSynchronizer) {
let spendingKey = try DerivationTool(networkType: network.networkType) let spendingKey = try DerivationTool(networkType: network.networkType)
.deriveUnifiedSpendingKey(seed: seedBytes, accountIndex: 0) .deriveUnifiedSpendingKey(seed: seedBytes, accountIndex: 0)
@ -327,7 +339,7 @@ enum TestSynchronizerBuilder {
let uvk = try DerivationTool(networkType: network.networkType) let uvk = try DerivationTool(networkType: network.networkType)
.deriveUnifiedFullViewingKey(from: spendingKey) .deriveUnifiedFullViewingKey(from: spendingKey)
return try build( return try await build(
rustBackend: rustBackend, rustBackend: rustBackend,
lowerBoundHeight: lowerBoundHeight, lowerBoundHeight: lowerBoundHeight,
cacheDbURL: cacheDbURL, cacheDbURL: cacheDbURL,

View File

@ -0,0 +1,22 @@
//
// XCAsyncTestCase.swift
//
//
// credits: https://betterprogramming.pub/async-setup-and-teardown-in-xctestcase-dc7a2cdb9fb
//
///
/// A subclass of ``XCTestCase`` that supports async setup and teardown.
///
import Foundation
import XCTest
extension XCTestCase {
func wait(asyncBlock: @escaping (() async throws -> Void)) {
let semaphore = DispatchSemaphore(value: 0)
Task.init {
try await asyncBlock()
semaphore.signal()
}
semaphore.wait()
}
}