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
os: osx
osx_image: xcode13.4
osx_image: xcode14
xcode_project: ./Example/ZcashLightClientSample/ZcashLightClientSample.xcodeproj
xcode_scheme: ZcashLightClientSample
xcode_destination: platform=iOS Simulator,OS=15.5,name=iPhone 8

View File

@ -9,6 +9,34 @@
# 0.17.0-alpha.1
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
- [#532] [0.16.x-beta] Download does not stop correctly

View File

@ -1,149 +1,151 @@
{
"pins" : [
{
"identity" : "grpc-swift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/grpc/grpc-swift.git",
"state" : {
"revision" : "466cc881f1760ed8c0e685900ed62dab7846a571",
"version" : "1.8.0"
"object": {
"pins": [
{
"package": "grpc-swift",
"repositoryURL": "https://github.com/grpc/grpc-swift.git",
"state": {
"branch": null,
"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",
"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
]
},
"version": 1
}

View File

@ -16,6 +16,7 @@ enum DemoAppConfig {
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 address: String {
"\(host):\(port)"
}

View File

@ -28,11 +28,13 @@ class GetUTXOsViewController: UIViewController {
self.transparentAddressLabel.text = tAddress
// swiftlint:disable:next force_try
let balance = try! synchronizer.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))
Task { @MainActor in
// 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))
}
}
@IBAction func shieldFunds(_ sender: Any) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -17,7 +17,7 @@ extension CompactBlockProcessor {
func compactBlockValidation() async throws {
try Task.checkCancellation()
setState(.validating)
state = .validating
let result = rustBackend.validateCombinedChain(dbCache: config.cacheDb, dbData: config.dataDb, networkType: config.network.networkType)
@ -30,7 +30,7 @@ extension CompactBlockProcessor {
case ZcashRustBackendWeldingConstants.validChain:
if Task.isCancelled {
setState(.stopped)
state = .stopped
LoggerProxy.debug("Warning: compactBlockValidation cancelled")
}
LoggerProxy.debug("validateChainFinished")
@ -50,11 +50,11 @@ extension CompactBlockProcessor {
switch validationError {
case .validationFailed(let height):
LoggerProxy.debug("chain validation at height: \(height)")
validationFailed(at: height)
await validationFailed(at: height)
case .failedWithError(let err):
guard let validationFailure = err else {
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
}

View File

@ -16,7 +16,7 @@ extension CompactBlockProcessor {
func fetchUnspentTxOutputs(range: CompactBlockRange) async throws {
try Task.checkCancellation()
setState(.fetching)
state = .fetching
do {
let tAddresses = try accountRepository.getAll()
@ -57,8 +57,8 @@ extension CompactBlockProcessor {
}
let result = (inserted: refreshed, skipped: skipped)
NotificationCenter.default.post(
NotificationCenter.default.mainThreadPost(
name: .blockProcessorStoredUTXOs,
object: self,
userInfo: [CompactBlockProcessorNotificationKey.refreshedUTXOs: result]
@ -67,7 +67,7 @@ extension CompactBlockProcessor {
if Task.isCancelled {
LoggerProxy.debug("Warning: fetchUnspentTxOutputs on range \(range) cancelled")
} else {
processBatchFinished(range: range)
await processBatchFinished(range: range)
}
} catch {
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 {
func connectivityStateDidChange(from oldState: ConnectivityState, to newState: ConnectivityState) {
LoggerProxy.event("Connection Changed from \(oldState) to \(newState)")
NotificationCenter.default.post(
NotificationCenter.default.mainThreadPost(
name: .blockProcessorConnectivityStateChanged,
object: self,
userInfo: [

View File

@ -79,7 +79,7 @@ public protocol Synchronizer {
/// prepares this initializer to operate. Initializes the internal state with the given
/// 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.
///
@ -93,17 +93,17 @@ public protocol Synchronizer {
/// 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.
/// - 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.
/// - 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
func getUnifiedAddress(accountIndex: Int) -> UnifiedAddress?
func getUnifiedAddress(accountIndex: Int) async -> UnifiedAddress?
/// 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.
/// - Returns the address or nil if account index is incorrect
func getTransparentAddress(accountIndex: Int) -> TransparentAddress?
func getTransparentAddress(accountIndex: Int) async -> TransparentAddress?
/// Sends zatoshi.
/// - 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]?
/// 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
@ -164,13 +164,14 @@ public protocol Synchronizer {
/// Returns the latest block height from the provided Lightwallet endpoint
/// Blocking
func latestHeight() throws -> BlockHeight
func latestHeight() async throws -> BlockHeight
/// Returns the latests UTXOs for the given address from the specified height on
func refreshUTXOs(address: TransparentAddress, from height: BlockHeight) async throws -> RefreshedUTXOs
/// 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)
@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 rewindError for other errors
/// - 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 {

View File

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

View File

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

View File

@ -30,13 +30,16 @@ class BalanceTests: XCTestCase {
override func setUpWithError() throws {
try super.setUpWithError()
coordinator = try TestCoordinator(
seed: seedPhrase,
walletBirthday: birthday,
channelProvider: ChannelProvider(),
network: network
)
try coordinator.reset(saplingActivation: 663150, branchID: "e9ff75a6", chainName: "main")
wait { [self] in
self.coordinator = try await TestCoordinator(
seed: self.seedPhrase,
walletBirthday: self.birthday,
channelProvider: ChannelProvider(),
network: self.network
)
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 darksideWalletService.applyStaged(nextLatestHeight: 663250)
sleep(2)
}
override func tearDown() {

View File

@ -33,14 +33,17 @@ class DarksideSanityCheckTests: XCTestCase {
override func setUpWithError() throws {
try super.setUpWithError()
coordinator = try TestCoordinator(
seed: seedPhrase,
walletBirthday: birthday,
channelProvider: ChannelProvider(),
network: network
)
try coordinator.reset(saplingActivation: birthday, branchID: branchID, chainName: chainName)
try coordinator.resetBlocks(dataset: .default)
wait { [self] in
self.coordinator = try await TestCoordinator(
seed: self.seedPhrase,
walletBirthday: self.birthday,
channelProvider: ChannelProvider(),
network: self.network
)
try self.coordinator.reset(saplingActivation: self.birthday, branchID: self.branchID, chainName: self.chainName)
try self.coordinator.resetBlocks(dataset: .default)
}
}
override func tearDownWithError() throws {
@ -74,7 +77,7 @@ class DarksideSanityCheckTests: XCTestCase {
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 lastBlock = try blocksDao.block(at: expectedLastBlock.height)

View File

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

View File

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

View File

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

View File

@ -30,12 +30,14 @@ class ShieldFundsTests: XCTestCase {
override func setUpWithError() throws {
try super.setUpWithError()
coordinator = try TestCoordinator(
seed: seedPhrase,
walletBirthday: birthday,
channelProvider: ChannelProvider(),
network: network
)
Task { @MainActor [self] in
self.coordinator = try await TestCoordinator(
seed: seedPhrase,
walletBirthday: birthday,
channelProvider: ChannelProvider(),
network: network
)
}
try coordinator.reset(saplingActivation: birthday, branchID: self.branchID, chainName: self.chainName)
try coordinator.service.clearAddedUTXOs()
}
@ -93,7 +95,7 @@ class ShieldFundsTests: XCTestCase {
var shouldContinue = false
var initialTotalBalance = 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: """
{
@ -135,7 +137,7 @@ class ShieldFundsTests: XCTestCase {
// at this point the balance should be all zeroes for transparent and shielded funds
XCTAssertEqual(initialTotalBalance, 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.verified, .zero)
@ -169,7 +171,7 @@ class ShieldFundsTests: XCTestCase {
// at this point the balance should be zero for shielded, then zero 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.verified, Zatoshi(10000)) //FIXME: this should be zero
@ -199,7 +201,7 @@ class ShieldFundsTests: XCTestCase {
wait(for: [tFundsConfirmationSyncExpectation], timeout: 5)
// 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.verified, Zatoshi(10000))
@ -229,7 +231,7 @@ class ShieldFundsTests: XCTestCase {
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.
// now balance should be zero shielded, zero transaparent.
// 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
// 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
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.verified, Zatoshi(10000)) //FIXME: this should be zero
@ -317,7 +319,7 @@ class ShieldFundsTests: XCTestCase {
XCTAssertNotNil(clearedTransaction)
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.verified, .zero)

View File

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

View File

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

View File

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

View File

@ -30,20 +30,20 @@ class Z2TReceiveTests: XCTestCase {
override func setUpWithError() throws {
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(
seed: seedPhrase,
walletBirthday: birthday,
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 {
try super.tearDownWithError()
NotificationCenter.default.removeObserver(self)
try coordinator.stop()

View File

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

View File

@ -79,7 +79,7 @@ class BlockStreamingTest: XCTestCase {
startHeight: startHeight
)
} 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)
// Subscribe to notifications
@ -113,12 +113,16 @@ class CompactBlockProcessorTests: XCTestCase {
startedScanningNotificationExpectation.subscribe(to: Notification.Name.blockProcessorStartedScanning, 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
func testStartNotifiesSuscriptors() {
startProcessing()
func testStartNotifiesSuscriptors() async {
await startProcessing()
wait(
for: [
@ -133,7 +137,7 @@ class CompactBlockProcessorTests: XCTestCase {
}
// FIXME: disabled see https://github.com/zcash/ZcashLightClientKit/issues/590
func testProgressNotifications() {
func testProgressNotifications() async {
let expectedUpdates = expectedBatches(
currentHeight: processorConfig.walletBirthday,
targetHeight: mockLatestHeight,
@ -141,7 +145,7 @@ class CompactBlockProcessorTests: XCTestCase {
)
updatedNotificationExpectation.expectedFulfillmentCount = expectedUpdates
startProcessing()
await startProcessing()
wait(for: [updatedNotificationExpectation], timeout: 300)
}
@ -197,23 +201,23 @@ class CompactBlockProcessorTests: XCTestCase {
)
}
func testDetermineLowerBoundPastBirthday() {
func testDetermineLowerBoundPastBirthday() async {
let errorHeight = 781_906
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
XCTAssertEqual(result, expected)
}
func testDetermineLowerBound() {
func testDetermineLowerBound() async {
let errorHeight = 781_906
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
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 ufvk = try derivationTool.deriveUnifiedSpendingKey(seed: seedData.bytes, accountIndex: 0)
.map( { try derivationTool.deriveUnifiedFullViewingKey(from: $0) })
@ -50,14 +50,13 @@ class WalletTests: XCTestCase {
)
let synchronizer = try SDKSynchronizer(initializer: wallet)
var dbInit: Initializer.InitializationResult!
XCTAssertNoThrow(try { dbInit = try synchronizer.prepare(with: nil) }())
guard case .success = dbInit else {
XCTFail("Failed to initDataDb. Expected `.success` got: \(String(describing: dbInit))")
return
do {
guard case .success = try await synchronizer.prepare(with: seedData.bytes) else {
XCTFail("Failed to initDataDb. Expected `.success` got: `.seedRequired`")
return
}
} catch {
XCTFail("shouldn't fail here")
}
// 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")
func blockStream(startHeight: BlockHeight, endHeight: BlockHeight) -> AsyncThrowingStream<ZcashCompactBlock, Error> {
AsyncThrowingStream { _ in }
service.blockStream(startHeight: startHeight, endHeight: endHeight)
}
func closeConnection() {
}
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> {
AsyncThrowingStream { _ in }
service.fetchUTXOs(for: tAddresses, height: height)
}
private var service: LightWalletService

View File

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