Merge branch 'master' into release/0.16.13-beta
This commit is contained in:
commit
ce641280fb
|
@ -17,7 +17,7 @@ jobs:
|
|||
permissions:
|
||||
contents: read
|
||||
|
||||
runs-on: macos-latest
|
||||
runs-on: macos-12
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
language: swift
|
||||
os: osx
|
||||
osx_image: xcode13.2
|
||||
osx_image: xcode14
|
||||
xcode_project: ./Example/ZcashLightClientSample/ZcashLightClientSample.xcodeproj
|
||||
xcode_scheme: ZcashLightClientSample
|
||||
xcode_destination: platform=iOS Simulator,OS=15.2,name=iPhone 8
|
||||
|
|
|
@ -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)"
|
||||
}
|
||||
|
@ -30,7 +31,7 @@ enum DemoAppConfig {
|
|||
}()
|
||||
|
||||
static var endpoint: LightWalletEndpoint {
|
||||
return LightWalletEndpoint(address: self.host, port: self.port, secure: true)
|
||||
return LightWalletEndpoint(address: self.host, port: self.port, secure: true, streamingCallTimeoutInMillis: 10 * 60 * 60 * 1000)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -29,11 +29,13 @@ class GetUTXOsViewController: UIViewController {
|
|||
|
||||
self.transparentAddressLabel.text = tAddress
|
||||
|
||||
// swiftlint:disable:next force_try
|
||||
let balance = try! 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))
|
||||
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) {
|
||||
|
@ -46,25 +48,17 @@ class GetUTXOsViewController: UIViewController {
|
|||
|
||||
KRProgressHUD.showMessage("🛡 Shielding 🛡")
|
||||
|
||||
AppDelegate.shared.sharedSynchronizer.shieldFunds(
|
||||
spendingKey: spendingKey,
|
||||
transparentSecretKey: transparentSecretKey,
|
||||
memo: "shielding is fun!",
|
||||
from: 0,
|
||||
resultBlock: { result in
|
||||
DispatchQueue.main.async {
|
||||
KRProgressHUD.dismiss()
|
||||
switch result {
|
||||
case .success(let transaction):
|
||||
self.messageLabel.text = "funds shielded \(transaction)"
|
||||
case .failure(let error):
|
||||
self.messageLabel.text = "Shielding failed: \(error)"
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
Task { @MainActor in
|
||||
let transaction = try await AppDelegate.shared.sharedSynchronizer.shieldFunds(
|
||||
spendingKey: spendingKey,
|
||||
transparentSecretKey: transparentSecretKey,
|
||||
memo: "shielding is fun!",
|
||||
from: 0)
|
||||
KRProgressHUD.dismiss()
|
||||
self.messageLabel.text = "funds shielded \(transaction)"
|
||||
}
|
||||
} catch {
|
||||
self.messageLabel.text = "Error \(error)"
|
||||
self.messageLabel.text = "Shielding failed \(error)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,18 +28,13 @@ class LatestHeightViewController: UIViewController {
|
|||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
service.latestBlockHeight { result in
|
||||
switch result {
|
||||
case .success(let height):
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.model = height
|
||||
}
|
||||
|
||||
case .failure(let error):
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.fail(error)
|
||||
}
|
||||
|
||||
/// Note: It's safe to modify model or call fail() because all methods of a UIViewController are MainActor methods by default.
|
||||
Task {
|
||||
do {
|
||||
model = try await service.latestBlockHeightAsync()
|
||||
} catch {
|
||||
fail(error as? LightWalletServiceError ?? .unknown)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -79,25 +79,21 @@ class SaplingParametersViewController: UIViewController {
|
|||
@IBAction func download(_ sender: Any) {
|
||||
let outputParameter = try! outputParamsURLHelper()
|
||||
let spendParameter = try! spendParamsURLHelper()
|
||||
SaplingParameterDownloader.downloadParamsIfnotPresent(
|
||||
spendURL: spendParameter,
|
||||
outputURL: outputParameter,
|
||||
result: { result in
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
switch result {
|
||||
case .success(let urls):
|
||||
self.spendPath.text = urls.spend.path
|
||||
self.outputPath.text = urls.output.path
|
||||
self.updateColor()
|
||||
self.updateButtons()
|
||||
|
||||
case .failure(let error):
|
||||
self.showError(error)
|
||||
}
|
||||
}
|
||||
|
||||
Task { @MainActor in
|
||||
do {
|
||||
let urls = try await SaplingParameterDownloader.downloadParamsIfnotPresent(
|
||||
spendURL: spendParameter,
|
||||
outputURL: outputParameter
|
||||
)
|
||||
spendPath.text = urls.spend.path
|
||||
outputPath.text = urls.output.path
|
||||
updateColor()
|
||||
updateButtons()
|
||||
} catch {
|
||||
showError(error)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func fileExists(_ path: String) -> Bool {
|
||||
|
|
|
@ -32,8 +32,10 @@ class SendViewController: UIViewController {
|
|||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
synchronizer = AppDelegate.shared.sharedSynchronizer
|
||||
// swiftlint:disable:next force_try
|
||||
try! synchronizer.prepare()
|
||||
Task { @MainActor in
|
||||
// swiftlint:disable:next force_try
|
||||
try! await synchronizer.prepare()
|
||||
}
|
||||
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(viewTapped(_:)))
|
||||
self.view.addGestureRecognizer(tapRecognizer)
|
||||
setUp()
|
||||
|
@ -41,12 +43,14 @@ class SendViewController: UIViewController {
|
|||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -214,26 +218,20 @@ class SendViewController: UIViewController {
|
|||
|
||||
KRProgressHUD.show()
|
||||
|
||||
synchronizer.sendToAddress(
|
||||
spendingKey: address,
|
||||
zatoshi: zec,
|
||||
toAddress: recipient,
|
||||
memo: !self.memoField.text.isEmpty ? self.memoField.text : nil,
|
||||
from: 0
|
||||
) { [weak self] result in
|
||||
DispatchQueue.main.async {
|
||||
Task { @MainActor in
|
||||
do {
|
||||
let pendingTransaction = try await synchronizer.sendToAddress(
|
||||
spendingKey: address,
|
||||
zatoshi: zec,
|
||||
toAddress: recipient,
|
||||
memo: !self.memoField.text.isEmpty ? self.memoField.text : nil,
|
||||
from: 0
|
||||
)
|
||||
KRProgressHUD.dismiss()
|
||||
}
|
||||
|
||||
switch result {
|
||||
case .success(let pendingTransaction):
|
||||
loggerProxy.info("transaction created: \(pendingTransaction)")
|
||||
|
||||
case .failure(let error):
|
||||
DispatchQueue.main.async {
|
||||
self?.fail(error)
|
||||
loggerProxy.error("SEND FAILED: \(error)")
|
||||
}
|
||||
} catch {
|
||||
fail(error)
|
||||
loggerProxy.error("SEND FAILED: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,9 @@ class SyncBlocksViewController: UIViewController {
|
|||
// swiftlint:disable:next force_try
|
||||
try! wallet.initialize()
|
||||
processor = CompactBlockProcessor(initializer: wallet)
|
||||
statusLabel.text = textFor(state: processor?.state ?? .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 {
|
||||
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(cancelTasks: true)
|
||||
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 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)
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
// swift-tools-version:5.5
|
||||
// swift-tools-version:5.6
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "ZcashLightClientKit",
|
||||
platforms: [
|
||||
.iOS(.v13),
|
||||
.macOS(.v10_12)
|
||||
.macOS(.v10_15)
|
||||
],
|
||||
products: [
|
||||
.library(
|
||||
|
|
|
@ -85,39 +85,24 @@ extension CompactBlockStorage: CompactBlockRepository {
|
|||
try latestBlockHeight()
|
||||
}
|
||||
|
||||
func latestHeight(result: @escaping (Swift.Result<BlockHeight, Error>) -> Void) {
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
do {
|
||||
result(.success(try self.latestBlockHeight()))
|
||||
} catch {
|
||||
result(.failure(error))
|
||||
}
|
||||
func latestHeightAsync() async throws -> BlockHeight {
|
||||
let task = Task(priority: .userInitiated) {
|
||||
try latestBlockHeight()
|
||||
}
|
||||
return try await task.value
|
||||
}
|
||||
|
||||
func write(blocks: [ZcashCompactBlock]) throws {
|
||||
try insert(blocks)
|
||||
}
|
||||
|
||||
func write(blocks: [ZcashCompactBlock], completion: ((Error?) -> Void)?) {
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
do {
|
||||
try self.insert(blocks)
|
||||
completion?(nil)
|
||||
} catch {
|
||||
completion?(error)
|
||||
}
|
||||
func write(blocks: [ZcashCompactBlock]) async throws {
|
||||
let task = Task(priority: .userInitiated) {
|
||||
try insert(blocks)
|
||||
}
|
||||
try await task.value
|
||||
}
|
||||
|
||||
func rewind(to height: BlockHeight, completion: ((Error?) -> Void)?) {
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
do {
|
||||
try self.rewind(to: height)
|
||||
completion?(nil)
|
||||
} catch {
|
||||
completion?(error)
|
||||
}
|
||||
|
||||
func rewindAsync(to height: BlockHeight) async throws {
|
||||
let task = Task(priority: .userInitiated) {
|
||||
try rewind(to: height)
|
||||
}
|
||||
try await task.value
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,78 +20,54 @@ public protocol CompactBlockDownloading {
|
|||
Downloads and stores the given block range.
|
||||
Non-Blocking
|
||||
*/
|
||||
func downloadBlockRange(
|
||||
_ heightRange: CompactBlockRange,
|
||||
completion: @escaping (Error?) -> Void
|
||||
)
|
||||
|
||||
/**
|
||||
Remove newer blocks and go back to the given height
|
||||
- Parameters:
|
||||
- height: the given height to rewind to
|
||||
- completion: block to be executed after completing rewind
|
||||
*/
|
||||
func rewind(to height: BlockHeight, completion: @escaping (Error?) -> Void)
|
||||
|
||||
/**
|
||||
returns the height of the latest compact block stored locally
|
||||
BlockHeight.empty() if no blocks are stored yet
|
||||
non-blocking
|
||||
*/
|
||||
func lastDownloadedBlockHeight(result: @escaping (Result<BlockHeight, Error>) -> Void)
|
||||
|
||||
/**
|
||||
Returns the last height on the blockchain
|
||||
Non-blocking
|
||||
*/
|
||||
func latestBlockHeight(result: @escaping (Result<BlockHeight, Error>) -> Void)
|
||||
|
||||
/**
|
||||
Downloads and stores the given block range.
|
||||
Blocking
|
||||
*/
|
||||
func downloadBlockRange(_ range: CompactBlockRange) throws
|
||||
|
||||
func downloadBlockRange(_ heightRange: CompactBlockRange) async throws
|
||||
|
||||
/**
|
||||
Restore the download progress up to the given height.
|
||||
*/
|
||||
func rewind(to height: BlockHeight) throws
|
||||
|
||||
/**
|
||||
Remove newer blocks and go back to the given height
|
||||
- Parameter height: the given height to rewind to
|
||||
*/
|
||||
func rewindAsync(to height: BlockHeight) async throws
|
||||
|
||||
/**
|
||||
Returns the height of the latest compact block stored locally.
|
||||
BlockHeight.empty() if no blocks are stored yet
|
||||
Blocking
|
||||
*/
|
||||
func lastDownloadedBlockHeight() throws -> BlockHeight
|
||||
|
||||
|
||||
/**
|
||||
returns the height of the latest compact block stored locally
|
||||
BlockHeight.empty() if no blocks are stored yet
|
||||
non-blocking
|
||||
*/
|
||||
func lastDownloadedBlockHeightAsync() async throws -> BlockHeight
|
||||
|
||||
/**
|
||||
Returns the latest block height
|
||||
Blocking
|
||||
*/
|
||||
func latestBlockHeight() throws -> BlockHeight
|
||||
|
||||
/**
|
||||
Returns the last height on the blockchain
|
||||
Non-blocking
|
||||
*/
|
||||
func latestBlockHeightAsync() async throws -> BlockHeight
|
||||
|
||||
/**
|
||||
Gets the transaction for the Id given
|
||||
- Parameter txId: Data representing the transaction Id
|
||||
- Returns: a transaction entity with the requested transaction
|
||||
- Throws: An error if the fetch failed
|
||||
*/
|
||||
func fetchTransaction(txId: Data) throws -> TransactionEntity
|
||||
|
||||
/**
|
||||
Gets the transaction for the Id given
|
||||
- Parameter txId: Data representing the transaction Id
|
||||
- Parameter result: a handler for the result of the operation
|
||||
*/
|
||||
func fetchTransaction(txId: Data, result: @escaping (Result<TransactionEntity, Error>) -> Void)
|
||||
|
||||
func fetchUnspentTransactionOutputs(tAddress: String, startHeight: BlockHeight) throws -> [UnspentTransactionOutputEntity]
|
||||
|
||||
func fetchUnspentTransactionOutputs(tAddress: String, startHeight: BlockHeight, result: @escaping (Result<[UnspentTransactionOutputEntity], Error>) -> Void)
|
||||
|
||||
func fetchUnspentTransactionOutputs(tAddresses: [String], startHeight: BlockHeight) throws -> [UnspentTransactionOutputEntity]
|
||||
|
||||
func fetchUnspentTransactionOutputs(tAddresses: [String], startHeight: BlockHeight, result: @escaping (Result<[UnspentTransactionOutputEntity], Error>) -> Void)
|
||||
func fetchTransaction(txId: Data) async throws -> TransactionEntity
|
||||
|
||||
func fetchUnspentTransactionOutputs(tAddress: String, startHeight: BlockHeight) -> AsyncThrowingStream<UnspentTransactionOutputEntity, Error>
|
||||
|
||||
func fetchUnspentTransactionOutputs(tAddresses: [String], startHeight: BlockHeight) -> AsyncThrowingStream<UnspentTransactionOutputEntity, Error>
|
||||
|
||||
func closeConnection()
|
||||
}
|
||||
|
@ -118,103 +94,53 @@ extension CompactBlockDownloader: CompactBlockDownloading {
|
|||
func closeConnection() {
|
||||
lightwalletService.closeConnection()
|
||||
}
|
||||
|
||||
func fetchUnspentTransactionOutputs(tAddresses: [String], startHeight: BlockHeight) throws -> [UnspentTransactionOutputEntity] {
|
||||
try lightwalletService.fetchUTXOs(for: tAddresses, height: startHeight)
|
||||
|
||||
func fetchUnspentTransactionOutputs(tAddresses: [String], startHeight: BlockHeight ) -> AsyncThrowingStream<UnspentTransactionOutputEntity, Error> {
|
||||
lightwalletService.fetchUTXOs(for: tAddresses, height: startHeight)
|
||||
}
|
||||
|
||||
func fetchUnspentTransactionOutputs(
|
||||
tAddresses: [String],
|
||||
startHeight: BlockHeight,
|
||||
result: @escaping (Result<[UnspentTransactionOutputEntity], Error>) -> Void
|
||||
) {
|
||||
lightwalletService.fetchUTXOs(for: tAddresses, height: startHeight) { fetchResult in
|
||||
switch fetchResult {
|
||||
case .success(let utxos):
|
||||
result(.success(utxos))
|
||||
case .failure(let error):
|
||||
result(.failure(error))
|
||||
}
|
||||
}
|
||||
func fetchUnspentTransactionOutputs(tAddress: String, startHeight: BlockHeight) -> AsyncThrowingStream<UnspentTransactionOutputEntity, Error> {
|
||||
lightwalletService.fetchUTXOs(for: tAddress, height: startHeight)
|
||||
}
|
||||
|
||||
func fetchUnspentTransactionOutputs(tAddress: String, startHeight: BlockHeight) throws -> [UnspentTransactionOutputEntity] {
|
||||
try lightwalletService.fetchUTXOs(for: tAddress, height: startHeight)
|
||||
}
|
||||
|
||||
func fetchUnspentTransactionOutputs(tAddress: String, startHeight: BlockHeight, result: @escaping (Result<[UnspentTransactionOutputEntity], Error>) -> Void) {
|
||||
lightwalletService.fetchUTXOs(for: tAddress, height: startHeight) { fetchResult in
|
||||
switch fetchResult {
|
||||
case .success(let utxos):
|
||||
result(.success(utxos))
|
||||
case .failure(let error):
|
||||
result(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func latestBlockHeight(result: @escaping (Result<BlockHeight, Error>) -> Void) {
|
||||
lightwalletService.latestBlockHeight { fetchResult in
|
||||
switch fetchResult {
|
||||
case .failure(let error):
|
||||
result(.failure(error))
|
||||
case .success(let height):
|
||||
result(.success(height))
|
||||
}
|
||||
}
|
||||
func latestBlockHeightAsync() async throws -> BlockHeight {
|
||||
try await lightwalletService.latestBlockHeightAsync()
|
||||
}
|
||||
|
||||
func latestBlockHeight() throws -> BlockHeight {
|
||||
try lightwalletService.latestBlockHeight()
|
||||
}
|
||||
|
||||
/**
|
||||
Downloads and stores the given block range.
|
||||
Non-Blocking
|
||||
*/
|
||||
func downloadBlockRange(
|
||||
_ heightRange: CompactBlockRange,
|
||||
completion: @escaping (Error?) -> Void
|
||||
) {
|
||||
lightwalletService.blockRange(heightRange) { [weak self] result in
|
||||
guard let self = self else {
|
||||
return
|
||||
}
|
||||
|
||||
switch result {
|
||||
case .failure(let error):
|
||||
completion(error)
|
||||
case .success(let compactBlocks):
|
||||
self.storage.write(blocks: compactBlocks) { storeError in
|
||||
completion(storeError)
|
||||
}
|
||||
|
||||
func downloadBlockRange( _ heightRange: CompactBlockRange) async throws {
|
||||
let stream: AsyncThrowingStream<ZcashCompactBlock, Error> = lightwalletService.blockRange(heightRange)
|
||||
do {
|
||||
var compactBlocks: [ZcashCompactBlock] = []
|
||||
for try await compactBlock in stream {
|
||||
compactBlocks.append(compactBlock)
|
||||
}
|
||||
try await self.storage.write(blocks: compactBlocks)
|
||||
} catch {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
func downloadBlockRange(_ range: CompactBlockRange) throws {
|
||||
let blocks = try lightwalletService.blockRange(range)
|
||||
try storage.write(blocks: blocks)
|
||||
}
|
||||
|
||||
func rewind(to height: BlockHeight, completion: @escaping (Error?) -> Void) {
|
||||
storage.rewind(to: height) { e in
|
||||
completion(e)
|
||||
func rewindAsync(to height: BlockHeight) async throws {
|
||||
do {
|
||||
try await storage.rewindAsync(to: height)
|
||||
} catch {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
func lastDownloadedBlockHeight(result: @escaping (Result<BlockHeight, Error>) -> Void) {
|
||||
storage.latestHeight { heightResult in
|
||||
switch heightResult {
|
||||
case .failure(let e):
|
||||
result(.failure(CompactBlockDownloadError.generalError(error: e)))
|
||||
return
|
||||
case .success(let height):
|
||||
result(.success(height))
|
||||
}
|
||||
func lastDownloadedBlockHeightAsync() async throws -> BlockHeight {
|
||||
do {
|
||||
let latestHeight = try await storage.latestHeightAsync()
|
||||
return latestHeight
|
||||
} catch {
|
||||
throw CompactBlockDownloadError.generalError(error: error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func rewind(to height: BlockHeight) throws {
|
||||
try self.storage.rewind(to: height)
|
||||
}
|
||||
|
@ -223,18 +149,7 @@ extension CompactBlockDownloader: CompactBlockDownloading {
|
|||
try self.storage.latestHeight()
|
||||
}
|
||||
|
||||
func fetchTransaction(txId: Data) throws -> TransactionEntity {
|
||||
try lightwalletService.fetchTransaction(txId: txId)
|
||||
}
|
||||
|
||||
func fetchTransaction(txId: Data, result: @escaping (Result<TransactionEntity, Error>) -> Void) {
|
||||
lightwalletService.fetchTransaction(txId: txId) { txResult in
|
||||
switch txResult {
|
||||
case .failure(let error):
|
||||
result(.failure(error))
|
||||
case .success(let transaction):
|
||||
result(.success(transaction))
|
||||
}
|
||||
}
|
||||
func fetchTransaction(txId: Data) async throws -> TransactionEntity {
|
||||
try await lightwalletService.fetchTransaction(txId: txId)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,160 @@
|
|||
//
|
||||
// CompactBlockDownload.swift
|
||||
// ZcashLightClientKit
|
||||
//
|
||||
// Created by Francisco Gindre on 10/16/19.
|
||||
// Copyright © 2019 Electric Coin Company. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension CompactBlockProcessor {
|
||||
func compactBlockStreamDownload(
|
||||
blockBufferSize: Int,
|
||||
startHeight: BlockHeight? = nil,
|
||||
targetHeight: BlockHeight? = nil
|
||||
) async throws {
|
||||
try Task.checkCancellation()
|
||||
|
||||
state = .downloading
|
||||
|
||||
var buffer: [ZcashCompactBlock] = []
|
||||
var targetHeightInternal: BlockHeight?
|
||||
|
||||
do {
|
||||
targetHeightInternal = targetHeight
|
||||
if targetHeight == nil {
|
||||
targetHeightInternal = try await service.latestBlockHeightAsync()
|
||||
}
|
||||
guard let latestHeight = targetHeightInternal else {
|
||||
throw LightWalletServiceError.generalError(message: "missing target height on compactBlockStreamDownload")
|
||||
}
|
||||
try Task.checkCancellation()
|
||||
let latestDownloaded = try await storage.latestHeightAsync()
|
||||
let startHeight = max(startHeight ?? BlockHeight.empty(), latestDownloaded)
|
||||
|
||||
let stream = service.blockStream(
|
||||
startHeight: startHeight,
|
||||
endHeight: latestHeight
|
||||
)
|
||||
|
||||
for try await zcashCompactBlock in stream {
|
||||
try Task.checkCancellation()
|
||||
buffer.append(zcashCompactBlock)
|
||||
if buffer.count >= blockBufferSize {
|
||||
try await storage.write(blocks: buffer)
|
||||
buffer.removeAll(keepingCapacity: true)
|
||||
}
|
||||
|
||||
let progress = BlockProgress(
|
||||
startHeight: startHeight,
|
||||
targetHeight: latestHeight,
|
||||
progressHeight: zcashCompactBlock.height
|
||||
)
|
||||
notifyProgress(.download(progress))
|
||||
}
|
||||
try await storage.write(blocks: buffer)
|
||||
buffer.removeAll(keepingCapacity: true)
|
||||
} catch {
|
||||
guard let err = error as? LightWalletServiceError, case .userCancelled = err else {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension CompactBlockProcessor {
|
||||
func compactBlockDownload(
|
||||
downloader: CompactBlockDownloading,
|
||||
range: CompactBlockRange
|
||||
) async throws {
|
||||
try Task.checkCancellation()
|
||||
|
||||
do {
|
||||
try await downloader.downloadBlockRange(range)
|
||||
} catch {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension CompactBlockProcessor {
|
||||
enum CompactBlockBatchDownloadError: Error {
|
||||
case startHeightMissing
|
||||
case batchDownloadFailed(range: CompactBlockRange, error: Error?)
|
||||
}
|
||||
|
||||
func compactBlockBatchDownload(
|
||||
range: CompactBlockRange,
|
||||
batchSize: Int = 100,
|
||||
maxRetries: Int = 5
|
||||
) async throws {
|
||||
try Task.checkCancellation()
|
||||
|
||||
var startHeight = range.lowerBound
|
||||
let targetHeight = range.upperBound
|
||||
|
||||
do {
|
||||
let localDownloadedHeight = try await self.storage.latestHeightAsync()
|
||||
|
||||
if localDownloadedHeight != BlockHeight.empty() && localDownloadedHeight > startHeight {
|
||||
LoggerProxy.warn("provided startHeight (\(startHeight)) differs from local latest downloaded height (\(localDownloadedHeight))")
|
||||
startHeight = localDownloadedHeight + 1
|
||||
}
|
||||
|
||||
var currentHeight = startHeight
|
||||
notifyProgress(
|
||||
.download(
|
||||
BlockProgress(
|
||||
startHeight: currentHeight,
|
||||
targetHeight: targetHeight,
|
||||
progressHeight: currentHeight
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
while !Task.isCancelled && currentHeight <= targetHeight {
|
||||
var retries = 0
|
||||
var success = true
|
||||
var localError: Error?
|
||||
|
||||
let range = CompactBlockRange(uncheckedBounds: (lower: currentHeight, upper: min(currentHeight + batchSize, targetHeight)))
|
||||
|
||||
repeat {
|
||||
do {
|
||||
let stream: AsyncThrowingStream<ZcashCompactBlock, Error> = service.blockRange(range)
|
||||
|
||||
var blocks: [ZcashCompactBlock] = []
|
||||
for try await compactBlock in stream {
|
||||
blocks.append(compactBlock)
|
||||
}
|
||||
try storage.insert(blocks)
|
||||
success = true
|
||||
} catch {
|
||||
success = false
|
||||
localError = error
|
||||
retries += 1
|
||||
}
|
||||
} while !Task.isCancelled && !success && retries < maxRetries
|
||||
|
||||
if retries >= maxRetries {
|
||||
throw CompactBlockBatchDownloadError.batchDownloadFailed(range: range, error: localError)
|
||||
}
|
||||
|
||||
notifyProgress(
|
||||
.download(
|
||||
BlockProgress(
|
||||
startHeight: startHeight,
|
||||
targetHeight: targetHeight,
|
||||
progressHeight: range.upperBound
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
currentHeight = range.upperBound + 1
|
||||
}
|
||||
} catch {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,312 +0,0 @@
|
|||
//
|
||||
// CompactBlockDownloadOperation.swift
|
||||
// ZcashLightClientKit
|
||||
//
|
||||
// Created by Francisco Gindre on 10/16/19.
|
||||
// Copyright © 2019 Electric Coin Company. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class CompactBlockDownloadOperation: ZcashOperation {
|
||||
override var isConcurrent: Bool { false }
|
||||
override var isAsynchronous: Bool { false }
|
||||
|
||||
private var downloader: CompactBlockDownloading
|
||||
private var range: CompactBlockRange
|
||||
|
||||
required init(downloader: CompactBlockDownloading, range: CompactBlockRange) {
|
||||
self.range = range
|
||||
self.downloader = downloader
|
||||
super.init()
|
||||
self.name = "Download Operation: \(range)"
|
||||
}
|
||||
|
||||
override func main() {
|
||||
guard !shouldCancel() else {
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
self.startedHandler?()
|
||||
do {
|
||||
try downloader.downloadBlockRange(range)
|
||||
} catch {
|
||||
self.error = error
|
||||
self.fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protocol CompactBlockProgressDelegate: AnyObject {
|
||||
func progressUpdated(_ progress: CompactBlockProgress)
|
||||
}
|
||||
|
||||
class CompactBlockStreamDownloadOperation: ZcashOperation {
|
||||
enum CompactBlockStreamDownloadOperationError: Error {
|
||||
case startHeightMissing
|
||||
}
|
||||
|
||||
override var isConcurrent: Bool { false }
|
||||
override var isAsynchronous: Bool { false }
|
||||
|
||||
private var storage: CompactBlockStorage
|
||||
private var service: LightWalletService
|
||||
private var done = false
|
||||
private var cancelable: CancellableCall?
|
||||
private var startHeight: BlockHeight?
|
||||
private var targetHeight: BlockHeight?
|
||||
private var blockBufferSize: Int
|
||||
private var buffer: [ZcashCompactBlock] = []
|
||||
|
||||
private weak var progressDelegate: CompactBlockProgressDelegate?
|
||||
|
||||
/// Creates an Compact Block Stream Download Operation Operation
|
||||
/// - Parameters:
|
||||
/// - service: instance that conforms to `LightWalletService`
|
||||
/// - storage: instance that conforms to `CompactBlockStorage`
|
||||
/// - blockBufferSize: the number of blocks that the stream downloader will store in memory
|
||||
/// before writing them to disk. Making this number smaller makes the downloader easier on RAM
|
||||
/// memory while being less efficient on disk writes. Making it bigger takes up more RAM memory
|
||||
/// but is less straining on Disk Writes. Too little or too big buffer will make this less efficient.
|
||||
/// - startHeight: the height this downloader will start downloading from. If `nil`,
|
||||
/// it will start from the latest height found on the local cacheDb
|
||||
/// - targetHeight: the upper bound for this stream download. If `nil`, the
|
||||
/// streamer will call `service.latestBlockHeight()`
|
||||
/// - progressDelegate: Optional delegate to report ongoing progress conforming to
|
||||
/// `CompactBlockProgressDelegate`
|
||||
///
|
||||
required init(
|
||||
service: LightWalletService,
|
||||
storage: CompactBlockStorage,
|
||||
blockBufferSize: Int,
|
||||
startHeight: BlockHeight? = nil,
|
||||
targetHeight: BlockHeight? = nil,
|
||||
progressDelegate: CompactBlockProgressDelegate? = nil
|
||||
) {
|
||||
self.storage = storage
|
||||
self.service = service
|
||||
self.startHeight = startHeight
|
||||
self.targetHeight = targetHeight
|
||||
self.progressDelegate = progressDelegate
|
||||
self.blockBufferSize = blockBufferSize
|
||||
super.init()
|
||||
self.name = "Download Stream Operation"
|
||||
}
|
||||
|
||||
// swiftlint:disable cyclomatic_complexity
|
||||
override func main() {
|
||||
guard !shouldCancel() else {
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
self.startedHandler?()
|
||||
do {
|
||||
if self.targetHeight == nil {
|
||||
self.targetHeight = try service.latestBlockHeight()
|
||||
}
|
||||
guard let latestHeight = self.targetHeight else {
|
||||
throw LightWalletServiceError.generalError(message: "missing target height on block stream operation")
|
||||
}
|
||||
let latestDownloaded = try storage.latestHeight()
|
||||
let startHeight = max(self.startHeight ?? BlockHeight.empty(), latestDownloaded)
|
||||
|
||||
self.cancelable = self.service.blockStream(startHeight: startHeight, endHeight: latestHeight) { [weak self] blockResult in
|
||||
guard let self = self, !self.isCancelled else {
|
||||
self?.cancel()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
switch blockResult {
|
||||
case .success(let result):
|
||||
switch result {
|
||||
case .success:
|
||||
do {
|
||||
try self.flush()
|
||||
self.done = true
|
||||
} catch {
|
||||
self.fail(error: error)
|
||||
}
|
||||
return
|
||||
case .error(let e):
|
||||
self.fail(error: e)
|
||||
}
|
||||
case .failure(let e):
|
||||
if case .userCancelled = e {
|
||||
self.done = true
|
||||
} else {
|
||||
self.fail(error: e)
|
||||
}
|
||||
}
|
||||
} handler: {[weak self] block in
|
||||
guard let self = self else { return }
|
||||
guard !self.isCancelled else {
|
||||
self.cancel()
|
||||
return
|
||||
}
|
||||
do {
|
||||
try self.cache(block, flushCache: false)
|
||||
} catch {
|
||||
self.fail(error: error)
|
||||
}
|
||||
} progress: { progress in
|
||||
guard !self.isCancelled else {
|
||||
self.cancel()
|
||||
return
|
||||
}
|
||||
self.progressDelegate?.progressUpdated(.download(progress))
|
||||
}
|
||||
|
||||
while !done && !isCancelled {
|
||||
sleep(1)
|
||||
}
|
||||
if isCancelled {
|
||||
self.cancel()
|
||||
}
|
||||
} catch {
|
||||
self.fail(error: error)
|
||||
}
|
||||
}
|
||||
|
||||
override func fail(error: Error? = nil) {
|
||||
self.cancelable?.cancel()
|
||||
super.fail(error: error)
|
||||
}
|
||||
|
||||
override func cancel() {
|
||||
self.cancelable?.cancel()
|
||||
super.cancel()
|
||||
}
|
||||
|
||||
func cache(_ block: ZcashCompactBlock, flushCache: Bool) throws {
|
||||
self.buffer.append(block)
|
||||
|
||||
if flushCache || buffer.count >= blockBufferSize {
|
||||
try flush()
|
||||
}
|
||||
}
|
||||
|
||||
func flush() throws {
|
||||
try self.storage.write(blocks: self.buffer)
|
||||
self.buffer.removeAll(keepingCapacity: true)
|
||||
}
|
||||
}
|
||||
|
||||
class CompactBlockBatchDownloadOperation: ZcashOperation {
|
||||
enum CompactBlockBatchDownloadOperationError: Error {
|
||||
case startHeightMissing
|
||||
case batchDownloadFailed(range: CompactBlockRange, error: Error?)
|
||||
}
|
||||
|
||||
override var isConcurrent: Bool { false }
|
||||
override var isAsynchronous: Bool { false }
|
||||
|
||||
private var batch: Int
|
||||
private var maxRetries: Int
|
||||
private var storage: CompactBlockStorage
|
||||
private var service: LightWalletService
|
||||
private var cancelable: CancellableCall?
|
||||
private var startHeight: BlockHeight
|
||||
private var targetHeight: BlockHeight
|
||||
|
||||
private weak var progressDelegate: CompactBlockProgressDelegate?
|
||||
|
||||
required init(
|
||||
service: LightWalletService,
|
||||
storage: CompactBlockStorage,
|
||||
startHeight: BlockHeight,
|
||||
targetHeight: BlockHeight,
|
||||
batchSize: Int = 100,
|
||||
maxRetries: Int = 5,
|
||||
progressDelegate: CompactBlockProgressDelegate? = nil
|
||||
) {
|
||||
self.storage = storage
|
||||
self.service = service
|
||||
self.startHeight = startHeight
|
||||
self.targetHeight = targetHeight
|
||||
self.progressDelegate = progressDelegate
|
||||
self.batch = batchSize
|
||||
self.maxRetries = maxRetries
|
||||
super.init()
|
||||
self.name = "Download Batch Operation"
|
||||
}
|
||||
|
||||
override func main() {
|
||||
guard !shouldCancel() else {
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
self.startedHandler?()
|
||||
do {
|
||||
let localDownloadedHeight = try self.storage.latestHeight()
|
||||
|
||||
if localDownloadedHeight != BlockHeight.empty() && localDownloadedHeight > startHeight {
|
||||
LoggerProxy.warn("provided startHeight (\(startHeight)) differs from local latest downloaded height (\(localDownloadedHeight))")
|
||||
startHeight = localDownloadedHeight + 1
|
||||
}
|
||||
|
||||
var currentHeight = startHeight
|
||||
self.progressDelegate?.progressUpdated(
|
||||
.download(
|
||||
BlockProgress(
|
||||
startHeight: currentHeight,
|
||||
targetHeight: targetHeight,
|
||||
progressHeight: currentHeight
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
while !isCancelled && currentHeight <= targetHeight {
|
||||
var retries = 0
|
||||
var success = true
|
||||
var localError: Error?
|
||||
|
||||
let range = nextRange(currentHeight: currentHeight, targetHeight: targetHeight)
|
||||
|
||||
repeat {
|
||||
do {
|
||||
let blocks = try service.blockRange(range)
|
||||
try storage.insert(blocks)
|
||||
success = true
|
||||
} catch {
|
||||
success = false
|
||||
localError = error
|
||||
retries += 1
|
||||
}
|
||||
} while !isCancelled && !success && retries < maxRetries
|
||||
|
||||
if retries >= maxRetries {
|
||||
throw CompactBlockBatchDownloadOperationError.batchDownloadFailed(range: range, error: localError)
|
||||
}
|
||||
|
||||
self.progressDelegate?.progressUpdated(
|
||||
.download(
|
||||
BlockProgress(
|
||||
startHeight: startHeight,
|
||||
targetHeight: targetHeight,
|
||||
progressHeight: range.upperBound
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
currentHeight = range.upperBound + 1
|
||||
}
|
||||
} catch {
|
||||
self.fail(error: error)
|
||||
}
|
||||
}
|
||||
|
||||
override func fail(error: Error? = nil) {
|
||||
self.cancelable?.cancel()
|
||||
super.fail(error: error)
|
||||
}
|
||||
|
||||
override func cancel() {
|
||||
self.cancelable?.cancel()
|
||||
super.cancel()
|
||||
}
|
||||
|
||||
func nextRange(currentHeight: BlockHeight, targetHeight: BlockHeight) -> CompactBlockRange {
|
||||
CompactBlockRange(uncheckedBounds: (lower: currentHeight, upper: min(currentHeight + batch, targetHeight)))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
//
|
||||
// CompactBlockEnhancement.swift
|
||||
// ZcashLightClientKit
|
||||
//
|
||||
// Created by Francisco Gindre on 4/10/20.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension CompactBlockProcessor {
|
||||
enum EnhancementError: Error {
|
||||
case noRawData(message: String)
|
||||
case unknownError
|
||||
case decryptError(error: Error)
|
||||
case txIdNotFound(txId: Data)
|
||||
}
|
||||
|
||||
private func enhance(transaction: TransactionEntity) async throws -> ConfirmedTransactionEntity {
|
||||
LoggerProxy.debug("Zoom.... Enhance... Tx: \(transaction.transactionId.toHexStringTxId())")
|
||||
|
||||
let transaction = try await downloader.fetchTransaction(txId: transaction.transactionId)
|
||||
|
||||
let transactionID = transaction.transactionId.toHexStringTxId()
|
||||
let block = String(describing: transaction.minedHeight)
|
||||
LoggerProxy.debug("Decrypting and storing transaction id: \(transactionID) block: \(block)")
|
||||
|
||||
guard let rawBytes = transaction.raw?.bytes else {
|
||||
let error = EnhancementError.noRawData(
|
||||
message: "Critical Error: transaction id: \(transaction.transactionId.toHexStringTxId()) has no data"
|
||||
)
|
||||
LoggerProxy.error("\(error)")
|
||||
throw error
|
||||
}
|
||||
|
||||
guard let minedHeight = transaction.minedHeight else {
|
||||
let error = EnhancementError.noRawData(
|
||||
message: "Critical Error - Attempt to decrypt and store an unmined transaction. Id: \(transaction.transactionId.toHexStringTxId())"
|
||||
)
|
||||
LoggerProxy.error("\(error)")
|
||||
throw error
|
||||
}
|
||||
|
||||
guard rustBackend.decryptAndStoreTransaction(dbData: config.dataDb, txBytes: rawBytes, minedHeight: Int32(minedHeight), networkType: config.network.networkType) else {
|
||||
if let rustError = rustBackend.lastError() {
|
||||
throw EnhancementError.decryptError(error: rustError)
|
||||
}
|
||||
throw EnhancementError.unknownError
|
||||
}
|
||||
guard let confirmedTx = try transactionRepository.findConfirmedTransactionBy(rawId: transaction.transactionId) else {
|
||||
throw EnhancementError.txIdNotFound(txId: transaction.transactionId)
|
||||
}
|
||||
return confirmedTx
|
||||
}
|
||||
|
||||
func compactBlockEnhancement(range: CompactBlockRange) async throws {
|
||||
try Task.checkCancellation()
|
||||
|
||||
LoggerProxy.debug("Started Enhancing range: \(range)")
|
||||
state = .enhancing
|
||||
|
||||
let blockRange = range.blockRange()
|
||||
var retries = 0
|
||||
let maxRetries = 5
|
||||
|
||||
// fetch transactions
|
||||
do {
|
||||
guard let transactions = try transactionRepository.findTransactions(in: blockRange, limit: Int.max), !transactions.isEmpty else {
|
||||
LoggerProxy.debug("no transactions detected on range: \(blockRange.printRange)")
|
||||
return
|
||||
}
|
||||
|
||||
for index in 0 ..< transactions.count {
|
||||
let transaction = transactions[index]
|
||||
var retry = true
|
||||
|
||||
while retry && retries < maxRetries {
|
||||
try Task.checkCancellation()
|
||||
do {
|
||||
let confirmedTx = try await enhance(transaction: transaction)
|
||||
retry = false
|
||||
notifyProgress(
|
||||
.enhance(
|
||||
EnhancementStreamProgress(
|
||||
totalTransactions: transactions.count,
|
||||
enhancedTransactions: index + 1,
|
||||
lastFoundTransaction: confirmedTx,
|
||||
range: range
|
||||
)
|
||||
)
|
||||
)
|
||||
} catch {
|
||||
retries += 1
|
||||
LoggerProxy.error("could not enhance txId \(transaction.transactionId.toHexStringTxId()) - Error: \(error)")
|
||||
if retries > maxRetries {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
LoggerProxy.error("error enhancing transactions! \(error)")
|
||||
throw error
|
||||
}
|
||||
|
||||
if let foundTxs = try? transactionRepository.findConfirmedTransactions(in: blockRange, offset: 0, limit: Int.max) {
|
||||
notifyTransactions(foundTxs, in: blockRange)
|
||||
}
|
||||
|
||||
if Task.isCancelled {
|
||||
LoggerProxy.debug("Warning: compactBlockEnhancement on range \(range) cancelled")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension BlockRange {
|
||||
var printRange: String {
|
||||
"\(self.start.height) ... \(self.end.height)"
|
||||
}
|
||||
}
|
|
@ -1,157 +0,0 @@
|
|||
//
|
||||
// CompactBlockEnhancementOperation.swift
|
||||
// ZcashLightClientKit
|
||||
//
|
||||
// Created by Francisco Gindre on 4/10/20.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class CompactBlockEnhancementOperation: ZcashOperation {
|
||||
enum EnhancementError: Error {
|
||||
case noRawData(message: String)
|
||||
case unknownError
|
||||
case decryptError(error: Error)
|
||||
case txIdNotFound(txId: Data)
|
||||
}
|
||||
|
||||
override var isConcurrent: Bool { false }
|
||||
override var isAsynchronous: Bool { false }
|
||||
|
||||
var rustBackend: ZcashRustBackendWelding.Type
|
||||
var txFoundHandler: (([ConfirmedTransactionEntity], BlockRange) -> Void)?
|
||||
var downloader: CompactBlockDownloading
|
||||
var repository: TransactionRepository
|
||||
var range: BlockRange
|
||||
var maxRetries: Int = 5
|
||||
var retries: Int = 0
|
||||
|
||||
private(set) var network: NetworkType
|
||||
private weak var progressDelegate: CompactBlockProgressDelegate?
|
||||
private var dataDb: URL
|
||||
|
||||
init(
|
||||
rustWelding: ZcashRustBackendWelding.Type,
|
||||
dataDb: URL,
|
||||
downloader: CompactBlockDownloading,
|
||||
repository: TransactionRepository,
|
||||
range: BlockRange,
|
||||
networkType: NetworkType,
|
||||
progressDelegate: CompactBlockProgressDelegate? = nil
|
||||
) {
|
||||
rustBackend = rustWelding
|
||||
self.dataDb = dataDb
|
||||
self.downloader = downloader
|
||||
self.repository = repository
|
||||
self.range = range
|
||||
self.progressDelegate = progressDelegate
|
||||
self.network = networkType
|
||||
|
||||
super.init()
|
||||
}
|
||||
|
||||
override func main() {
|
||||
guard !shouldCancel() else {
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
|
||||
self.startedHandler?()
|
||||
|
||||
// fetch transactions
|
||||
do {
|
||||
guard let transactions = try repository.findTransactions(in: self.range, limit: Int.max), !transactions.isEmpty else {
|
||||
LoggerProxy.debug("no transactions detected on range: \(range.printRange)")
|
||||
return
|
||||
}
|
||||
|
||||
for index in 0 ..< transactions.count {
|
||||
let transaction = transactions[index]
|
||||
var retry = true
|
||||
|
||||
while retry && self.retries < maxRetries {
|
||||
do {
|
||||
let confirmedTx = try enhance(transaction: transaction)
|
||||
retry = false
|
||||
self.reportProgress(
|
||||
totalTransactions: transactions.count,
|
||||
enhanced: index + 1,
|
||||
txEnhanced: confirmedTx
|
||||
)
|
||||
} catch {
|
||||
self.retries += 1
|
||||
LoggerProxy.error("could not enhance txId \(transaction.transactionId.toHexStringTxId()) - Error: \(error)")
|
||||
if retries > maxRetries {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
LoggerProxy.error("error enhancing transactions! \(error)")
|
||||
self.error = error
|
||||
self.fail()
|
||||
return
|
||||
}
|
||||
|
||||
if let handler = self.txFoundHandler, let foundTxs = try? repository.findConfirmedTransactions(in: self.range, offset: 0, limit: Int.max) {
|
||||
handler(foundTxs, self.range)
|
||||
}
|
||||
}
|
||||
|
||||
func reportProgress(totalTransactions: Int, enhanced: Int, txEnhanced: ConfirmedTransactionEntity) {
|
||||
self.progressDelegate?.progressUpdated(
|
||||
.enhance(
|
||||
EnhancementStreamProgress(
|
||||
totalTransactions: totalTransactions,
|
||||
enhancedTransactions: enhanced,
|
||||
lastFoundTransaction: txEnhanced,
|
||||
range: self.range.compactBlockRange
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
func enhance(transaction: TransactionEntity) throws -> ConfirmedTransactionEntity {
|
||||
LoggerProxy.debug("Zoom.... Enhance... Tx: \(transaction.transactionId.toHexStringTxId())")
|
||||
|
||||
let transaction = try downloader.fetchTransaction(txId: transaction.transactionId)
|
||||
|
||||
let transactionID = transaction.transactionId.toHexStringTxId()
|
||||
let block = String(describing: transaction.minedHeight)
|
||||
LoggerProxy.debug("Decrypting and storing transaction id: \(transactionID) block: \(block)")
|
||||
|
||||
guard let rawBytes = transaction.raw?.bytes else {
|
||||
let error = EnhancementError.noRawData(
|
||||
message: "Critical Error: transaction id: \(transaction.transactionId.toHexStringTxId()) has no data"
|
||||
)
|
||||
LoggerProxy.error("\(error)")
|
||||
throw error
|
||||
}
|
||||
|
||||
guard let minedHeight = transaction.minedHeight else {
|
||||
let error = EnhancementError.noRawData(
|
||||
message: "Critical Error - Attempt to decrypt and store an unmined transaction. Id: \(transaction.transactionId.toHexStringTxId())"
|
||||
)
|
||||
LoggerProxy.error("\(error)")
|
||||
throw error
|
||||
}
|
||||
|
||||
guard rustBackend.decryptAndStoreTransaction(dbData: dataDb, txBytes: rawBytes, minedHeight: Int32(minedHeight), networkType: network) else {
|
||||
if let rustError = rustBackend.lastError() {
|
||||
throw EnhancementError.decryptError(error: rustError)
|
||||
}
|
||||
throw EnhancementError.unknownError
|
||||
}
|
||||
guard let confirmedTx = try self.repository.findConfirmedTransactionBy(rawId: transaction.transactionId) else {
|
||||
throw EnhancementError.txIdNotFound(txId: transaction.transactionId)
|
||||
}
|
||||
return confirmedTx
|
||||
}
|
||||
}
|
||||
|
||||
private extension BlockRange {
|
||||
var printRange: String {
|
||||
"\(self.start.height) ... \(self.end.height)"
|
||||
}
|
||||
}
|
|
@ -30,6 +30,7 @@ public enum CompactBlockProcessorError: Error {
|
|||
case wrongConsensusBranchId(expectedLocally: ConsensusBranchID, found: ConsensusBranchID)
|
||||
case networkMismatch(expected: NetworkType, found: NetworkType)
|
||||
case saplingActivationMismatch(expected: BlockHeight, found: BlockHeight)
|
||||
case unknown
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -102,7 +103,7 @@ public enum CompactBlockProgress {
|
|||
}
|
||||
|
||||
protocol EnhancementStreamDelegate: AnyObject {
|
||||
func transactionEnhancementProgressUpdated(_ progress: EnhancementProgress)
|
||||
func transactionEnhancementProgressUpdated(_ progress: EnhancementProgress) async
|
||||
}
|
||||
|
||||
public protocol EnhancementProgress {
|
||||
|
@ -212,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
|
||||
///
|
||||
|
@ -311,12 +312,12 @@ public class CompactBlockProcessor {
|
|||
case synced
|
||||
}
|
||||
|
||||
public private(set) var state: State = .stopped {
|
||||
public internal(set) var state: State = .stopped {
|
||||
didSet {
|
||||
transitionState(from: oldValue, to: self.state)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var config: Configuration {
|
||||
willSet {
|
||||
self.stop()
|
||||
|
@ -336,19 +337,19 @@ public class CompactBlockProcessor {
|
|||
}
|
||||
}
|
||||
|
||||
private var service: LightWalletService
|
||||
var service: LightWalletService
|
||||
private(set) var downloader: CompactBlockDownloading
|
||||
private var storage: CompactBlockStorage
|
||||
private var transactionRepository: TransactionRepository
|
||||
private var accountRepository: AccountRepository
|
||||
private var rustBackend: ZcashRustBackendWelding.Type
|
||||
var storage: CompactBlockStorage
|
||||
var transactionRepository: TransactionRepository
|
||||
var accountRepository: AccountRepository
|
||||
var rustBackend: ZcashRustBackendWelding.Type
|
||||
private var retryAttempts: Int = 0
|
||||
private var backoffTimer: Timer?
|
||||
private var lowerBoundHeight: BlockHeight?
|
||||
private var latestBlockHeight: BlockHeight
|
||||
private var lastChainValidationFailure: BlockHeight?
|
||||
private var consecutiveChainValidationErrors: Int = 0
|
||||
private var processingError: Error?
|
||||
var processingError: Error?
|
||||
private var foundBlocks = false
|
||||
private var maxAttempts: Int {
|
||||
config.retries
|
||||
|
@ -358,13 +359,7 @@ public class CompactBlockProcessor {
|
|||
BlockHeight(self.config.downloadBatchSize)
|
||||
}
|
||||
|
||||
private var operationQueue: OperationQueue = {
|
||||
let queue = OperationQueue()
|
||||
queue.name = "CompactBlockProcessorQueue"
|
||||
queue.maxConcurrentOperationCount = 1
|
||||
return queue
|
||||
}()
|
||||
|
||||
private var cancelableTask: Task<Void, Error>?
|
||||
|
||||
/// Initializes a CompactBlockProcessor instance
|
||||
/// - Parameters:
|
||||
|
@ -372,7 +367,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,
|
||||
|
@ -393,7 +388,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,
|
||||
|
@ -431,7 +426,7 @@ public class CompactBlockProcessor {
|
|||
}
|
||||
|
||||
deinit {
|
||||
self.operationQueue.cancelAllOperations()
|
||||
cancelableTask?.cancel()
|
||||
}
|
||||
|
||||
static func validateServerInfo(
|
||||
|
@ -480,18 +475,13 @@ 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
|
||||
self.backoffTimer?.invalidate()
|
||||
self.backoffTimer = nil
|
||||
}
|
||||
guard !operationQueue.isSuspended else {
|
||||
LoggerProxy.debug("restarting suspended queue")
|
||||
operationQueue.isSuspended = false
|
||||
return
|
||||
}
|
||||
|
||||
guard shouldStart else {
|
||||
switch self.state {
|
||||
|
@ -499,7 +489,7 @@ public class CompactBlockProcessor {
|
|||
// max attempts have been reached
|
||||
LoggerProxy.info("max retry attempts reached with error: \(e)")
|
||||
notifyError(CompactBlockProcessorError.maxAttemptsReached(attempts: self.maxAttempts))
|
||||
self.state = .stopped
|
||||
state = .stopped
|
||||
case .stopped:
|
||||
// max attempts have been reached
|
||||
LoggerProxy.info("max retry attempts reached")
|
||||
|
@ -514,7 +504,7 @@ public class CompactBlockProcessor {
|
|||
return
|
||||
}
|
||||
|
||||
self.nextBatch()
|
||||
await self.nextBatch()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -523,25 +513,20 @@ public class CompactBlockProcessor {
|
|||
Note: retry count is reset
|
||||
- Parameter cancelTasks: cancel the pending tasks. Defaults to true
|
||||
*/
|
||||
public func stop(cancelTasks: Bool = true) {
|
||||
public func stop() {
|
||||
self.backoffTimer?.invalidate()
|
||||
self.backoffTimer = nil
|
||||
|
||||
if cancelTasks {
|
||||
operationQueue.cancelAllOperations()
|
||||
} else {
|
||||
self.operationQueue.isSuspended = true
|
||||
}
|
||||
cancelableTask?.cancel()
|
||||
|
||||
self.retryAttempts = 0
|
||||
self.state = .stopped
|
||||
}
|
||||
|
||||
/**
|
||||
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()
|
||||
|
@ -552,7 +537,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
|
||||
}
|
||||
|
||||
|
@ -560,7 +545,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
|
||||
}
|
||||
|
||||
|
@ -586,269 +571,48 @@ public class CompactBlockProcessor {
|
|||
self.config = config
|
||||
}
|
||||
|
||||
func validateServer(completionBlock: @escaping (() -> Void)) {
|
||||
self.service.getInfo(result: { [weak self] result in
|
||||
guard let self = self else { return }
|
||||
|
||||
switch result {
|
||||
case .success(let info):
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
do {
|
||||
try Self.validateServerInfo(
|
||||
info,
|
||||
saplingActivation: self.config.saplingActivation,
|
||||
localNetwork: self.config.network,
|
||||
rustBackend: self.rustBackend
|
||||
)
|
||||
completionBlock()
|
||||
} catch {
|
||||
self.severeFailure(error)
|
||||
}
|
||||
}
|
||||
case .failure(let error):
|
||||
self.severeFailure(error.mapToProcessorError())
|
||||
}
|
||||
})
|
||||
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
|
||||
the way operations are queued is implemented based on the following good practice https://forums.developer.apple.com/thread/25761
|
||||
|
||||
*/
|
||||
// swiftlint:disable cyclomatic_complexity
|
||||
func processNewBlocks(range: CompactBlockRange) {
|
||||
|
||||
/// Processes new blocks on the given range based on the configuration set for this instance
|
||||
func processNewBlocks(range: CompactBlockRange) async {
|
||||
self.foundBlocks = true
|
||||
self.backoffTimer?.invalidate()
|
||||
self.backoffTimer = nil
|
||||
|
||||
let cfg = self.config
|
||||
let downloadBlockOperation = CompactBlockStreamDownloadOperation(
|
||||
service: self.service,
|
||||
storage: self.storage,
|
||||
blockBufferSize: self.config.downloadBufferSize,
|
||||
startHeight: range.lowerBound,
|
||||
targetHeight: range.upperBound,
|
||||
progressDelegate: self
|
||||
)
|
||||
|
||||
downloadBlockOperation.startedHandler = { [weak self] in
|
||||
DispatchQueue.main.async {
|
||||
self?.state = .downloading
|
||||
}
|
||||
}
|
||||
|
||||
downloadBlockOperation.errorHandler = { [weak self] error in
|
||||
DispatchQueue.main.async {
|
||||
guard let self = self else { return }
|
||||
self.processingError = error
|
||||
self.fail(error)
|
||||
}
|
||||
}
|
||||
|
||||
let validateChainOperation = CompactBlockValidationOperation(
|
||||
rustWelding: self.rustBackend,
|
||||
cacheDb: cfg.cacheDb,
|
||||
dataDb: cfg.dataDb,
|
||||
networkType: self.config.network.networkType
|
||||
)
|
||||
|
||||
let downloadValidateAdapterOperation = BlockOperation { [weak validateChainOperation, weak downloadBlockOperation] in
|
||||
validateChainOperation?.error = downloadBlockOperation?.error
|
||||
}
|
||||
|
||||
validateChainOperation.completionHandler = { [weak self] _, cancelled in
|
||||
guard !cancelled else {
|
||||
DispatchQueue.main.async {
|
||||
self?.state = .stopped
|
||||
LoggerProxy.debug("Warning: validateChainOperation operation cancelled")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
LoggerProxy.debug("validateChainFinished")
|
||||
}
|
||||
|
||||
validateChainOperation.errorHandler = { [weak self] error in
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
guard let validationError = error as? CompactBlockValidationError else {
|
||||
LoggerProxy.error("Warning: validateChain operation returning generic error: \(error)")
|
||||
return
|
||||
}
|
||||
|
||||
switch validationError {
|
||||
case .validationFailed(let height):
|
||||
LoggerProxy.debug("chain validation at height: \(height)")
|
||||
self.validationFailed(at: height)
|
||||
case .failedWithError(let e):
|
||||
guard let validationFailure = e else {
|
||||
LoggerProxy.error("validation failed without a specific error")
|
||||
self.fail(CompactBlockProcessorError.generalError(message: "validation failed without a specific error"))
|
||||
return
|
||||
}
|
||||
|
||||
self.fail(validationFailure)
|
||||
cancelableTask = Task(priority: .userInitiated) {
|
||||
do {
|
||||
try await compactBlockStreamDownload(
|
||||
blockBufferSize: config.downloadBufferSize,
|
||||
startHeight: range.lowerBound,
|
||||
targetHeight: range.upperBound
|
||||
)
|
||||
try await compactBlockValidation()
|
||||
try await compactBlockBatchScanning(range: range)
|
||||
try await compactBlockEnhancement(range: range)
|
||||
try await fetchUnspentTxOutputs(range: range)
|
||||
//state = .stopped
|
||||
} catch {
|
||||
if !(Task.isCancelled) {
|
||||
await fail(error)
|
||||
} else {
|
||||
state = .stopped
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
validateChainOperation.startedHandler = { [weak self] in
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.state = .validating
|
||||
}
|
||||
}
|
||||
|
||||
let scanBlocksOperation = CompactBlockBatchScanningOperation(
|
||||
rustWelding: rustBackend,
|
||||
cacheDb: config.cacheDb,
|
||||
dataDb: config.dataDb,
|
||||
transactionRepository: transactionRepository,
|
||||
range: range,
|
||||
batchSize: UInt32(self.config.scanningBatchSize),
|
||||
networkType: self.config.network.networkType,
|
||||
progressDelegate: self
|
||||
)
|
||||
|
||||
let validateScanningAdapterOperation = BlockOperation { [weak scanBlocksOperation, weak validateChainOperation] in
|
||||
scanBlocksOperation?.error = validateChainOperation?.error
|
||||
}
|
||||
|
||||
scanBlocksOperation.startedHandler = { [weak self] in
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.state = .scanning
|
||||
}
|
||||
}
|
||||
|
||||
scanBlocksOperation.completionHandler = { [weak self] _, cancelled in
|
||||
guard !cancelled else {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.state = .stopped
|
||||
LoggerProxy.debug("Warning: scanBlocksOperation operation cancelled")
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
scanBlocksOperation.errorHandler = { [weak self] error in
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.processingError = error
|
||||
self.fail(error)
|
||||
}
|
||||
}
|
||||
|
||||
let enhanceOperation = CompactBlockEnhancementOperation(
|
||||
rustWelding: rustBackend,
|
||||
dataDb: config.dataDb,
|
||||
downloader: downloader,
|
||||
repository: transactionRepository,
|
||||
range: range.blockRange(),
|
||||
networkType: self.config.network.networkType
|
||||
)
|
||||
|
||||
enhanceOperation.startedHandler = {
|
||||
LoggerProxy.debug("Started Enhancing range: \(range)")
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.state = .enhancing
|
||||
}
|
||||
}
|
||||
|
||||
enhanceOperation.txFoundHandler = { [weak self] txs, range in
|
||||
self?.notifyTransactions(txs, in: range)
|
||||
}
|
||||
|
||||
enhanceOperation.completionHandler = { _, cancelled in
|
||||
guard !cancelled else {
|
||||
LoggerProxy.debug("Warning: enhance operation on range \(range) cancelled")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
enhanceOperation.errorHandler = { [weak self] error in
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
self.processingError = error
|
||||
self.fail(error)
|
||||
}
|
||||
}
|
||||
|
||||
let scanEnhanceAdapterOperation = BlockOperation { [weak enhanceOperation, weak scanBlocksOperation] in
|
||||
enhanceOperation?.error = scanBlocksOperation?.error
|
||||
}
|
||||
|
||||
let fetchOperation = FetchUnspentTxOutputsOperation(
|
||||
accountRepository: accountRepository,
|
||||
downloader: self.downloader,
|
||||
rustbackend: rustBackend,
|
||||
dataDb: config.dataDb,
|
||||
startHeight: config.walletBirthday,
|
||||
networkType: self.config.network.networkType
|
||||
)
|
||||
|
||||
fetchOperation.startedHandler = { [weak self] in
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.state = .fetching
|
||||
}
|
||||
}
|
||||
|
||||
fetchOperation.completionHandler = { [weak self] _, cancelled in
|
||||
guard !cancelled else {
|
||||
LoggerProxy.debug("Warning: fetch operation on range \(range) cancelled")
|
||||
return
|
||||
}
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.processBatchFinished(range: range)
|
||||
}
|
||||
}
|
||||
|
||||
fetchOperation.errorHandler = { [weak self] error in
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
self.processingError = error
|
||||
self.fail(error)
|
||||
}
|
||||
}
|
||||
|
||||
fetchOperation.fetchedUTXOsHandler = { result in
|
||||
NotificationCenter.default.post(
|
||||
name: .blockProcessorStoredUTXOs,
|
||||
object: self,
|
||||
userInfo: [CompactBlockProcessorNotificationKey.refreshedUTXOs: result]
|
||||
)
|
||||
}
|
||||
|
||||
let enhanceFetchAdapterOperation = BlockOperation { [weak fetchOperation, weak enhanceOperation] in
|
||||
fetchOperation?.error = enhanceOperation?.error
|
||||
}
|
||||
|
||||
downloadValidateAdapterOperation.addDependency(downloadBlockOperation)
|
||||
validateChainOperation.addDependency(downloadValidateAdapterOperation)
|
||||
validateScanningAdapterOperation.addDependency(validateChainOperation)
|
||||
scanBlocksOperation.addDependency(validateScanningAdapterOperation)
|
||||
scanEnhanceAdapterOperation.addDependency(scanBlocksOperation)
|
||||
enhanceOperation.addDependency(scanEnhanceAdapterOperation)
|
||||
enhanceFetchAdapterOperation.addDependency(enhanceOperation)
|
||||
fetchOperation.addDependency(enhanceFetchAdapterOperation)
|
||||
|
||||
operationQueue.addOperations(
|
||||
[
|
||||
downloadBlockOperation,
|
||||
downloadValidateAdapterOperation,
|
||||
validateChainOperation,
|
||||
validateScanningAdapterOperation,
|
||||
scanBlocksOperation,
|
||||
scanEnhanceAdapterOperation,
|
||||
enhanceOperation,
|
||||
enhanceFetchAdapterOperation,
|
||||
fetchOperation
|
||||
],
|
||||
waitUntilFinished: false
|
||||
)
|
||||
}
|
||||
|
||||
func calculateProgress(start: BlockHeight, current: BlockHeight, latest: BlockHeight) -> Float {
|
||||
|
@ -864,7 +628,7 @@ public class CompactBlockProcessor {
|
|||
|
||||
LoggerProxy.debug("progress: \(progress)")
|
||||
|
||||
NotificationCenter.default.post(
|
||||
NotificationCenter.default.mainThreadPost(
|
||||
name: Notification.Name.blockProcessorUpdated,
|
||||
object: self,
|
||||
userInfo: userInfo
|
||||
|
@ -872,7 +636,7 @@ public class CompactBlockProcessor {
|
|||
}
|
||||
|
||||
func notifyTransactions(_ txs: [ConfirmedTransactionEntity], in range: BlockRange) {
|
||||
NotificationCenter.default.post(
|
||||
NotificationCenter.default.mainThreadPost(
|
||||
name: .blockProcessorFoundTransactions,
|
||||
object: self,
|
||||
userInfo: [
|
||||
|
@ -892,19 +656,19 @@ public class CompactBlockProcessor {
|
|||
}
|
||||
|
||||
func severeFailure(_ error: Error) {
|
||||
operationQueue.cancelAllOperations()
|
||||
cancelableTask?.cancel()
|
||||
LoggerProxy.error("show stoppper failure: \(error)")
|
||||
self.backoffTimer?.invalidate()
|
||||
self.retryAttempts = config.retries
|
||||
self.processingError = error
|
||||
self.state = .error(error)
|
||||
state = .error(error)
|
||||
self.notifyError(error)
|
||||
}
|
||||
|
||||
func fail(_ error: Error) {
|
||||
func fail(_ error: Error) async {
|
||||
// todo specify: failure
|
||||
LoggerProxy.error("\(error)")
|
||||
operationQueue.cancelAllOperations()
|
||||
cancelableTask?.cancel()
|
||||
self.retryAttempts += 1
|
||||
self.processingError = error
|
||||
switch self.state {
|
||||
|
@ -913,14 +677,14 @@ public class CompactBlockProcessor {
|
|||
default:
|
||||
break
|
||||
}
|
||||
self.state = .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) {
|
||||
operationQueue.cancelAllOperations()
|
||||
func retryProcessing(range: CompactBlockRange) async {
|
||||
cancelableTask?.cancel()
|
||||
// update retries
|
||||
self.retryAttempts += 1
|
||||
self.processingError = nil
|
||||
|
@ -934,10 +698,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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -972,47 +735,41 @@ public class CompactBlockProcessor {
|
|||
}
|
||||
}
|
||||
|
||||
private func nextBatch() {
|
||||
self.state = .downloading
|
||||
NextStateHelper.nextState(
|
||||
service: self.service,
|
||||
downloader: self.downloader,
|
||||
config: self.config,
|
||||
rustBackend: self.rustBackend,
|
||||
queue: nil
|
||||
) { result in
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
switch result {
|
||||
case .success(let nextState):
|
||||
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)
|
||||
}
|
||||
case .failure(let error):
|
||||
self.severeFailure(error)
|
||||
}
|
||||
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)"
|
||||
)
|
||||
await self.processingFinished(height: latestDownloadHeight)
|
||||
}
|
||||
} catch {
|
||||
self.severeFailure(error)
|
||||
}
|
||||
}
|
||||
|
||||
private func validationFailed(at height: BlockHeight) {
|
||||
internal func validationFailed(at height: BlockHeight) async {
|
||||
// cancel all Tasks
|
||||
operationQueue.cancelAllOperations()
|
||||
cancelableTask?.cancel()
|
||||
|
||||
// register latest failure
|
||||
self.lastChainValidationFailure = height
|
||||
|
@ -1026,7 +783,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
|
||||
}
|
||||
|
||||
|
@ -1034,7 +791,7 @@ public class CompactBlockProcessor {
|
|||
try downloader.rewind(to: rewindHeight)
|
||||
|
||||
// notify reorg
|
||||
NotificationCenter.default.post(
|
||||
NotificationCenter.default.mainThreadPost(
|
||||
name: Notification.Name.blockProcessorHandledReOrg,
|
||||
object: self,
|
||||
userInfo: [
|
||||
|
@ -1043,15 +800,15 @@ public class CompactBlockProcessor {
|
|||
)
|
||||
|
||||
// process next batch
|
||||
self.nextBatch()
|
||||
await self.nextBatch()
|
||||
} catch {
|
||||
self.fail(error)
|
||||
await self.fail(error)
|
||||
}
|
||||
}
|
||||
|
||||
private func processBatchFinished(range: CompactBlockRange) {
|
||||
internal func processBatchFinished(range: CompactBlockRange) async {
|
||||
guard processingError == nil else {
|
||||
retryProcessing(range: range)
|
||||
await retryProcessing(range: range)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -1059,15 +816,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: [
|
||||
|
@ -1075,35 +832,33 @@ public class CompactBlockProcessor {
|
|||
CompactBlockProcessorNotificationKey.foundBlocks: self.foundBlocks
|
||||
]
|
||||
)
|
||||
self.state = .synced
|
||||
setTimer()
|
||||
state = .synced
|
||||
await setTimer()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
@ -1117,7 +872,7 @@ public class CompactBlockProcessor {
|
|||
return
|
||||
}
|
||||
|
||||
NotificationCenter.default.post(
|
||||
NotificationCenter.default.mainThreadPost(
|
||||
name: .blockProcessorStatusChanged,
|
||||
object: self,
|
||||
userInfo: [
|
||||
|
@ -1128,27 +883,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)]
|
||||
|
@ -1262,33 +1017,27 @@ extension UnifiedAddressShim: UnifiedAddress {
|
|||
}
|
||||
|
||||
extension CompactBlockProcessor {
|
||||
func refreshUTXOs(tAddress: String, startHeight: BlockHeight, result: @escaping (Result<RefreshedUTXOs, Error>) -> Void) {
|
||||
func refreshUTXOs(tAddress: String, startHeight: BlockHeight) async throws -> RefreshedUTXOs {
|
||||
let dataDb = self.config.dataDb
|
||||
self.downloader.fetchUnspentTransactionOutputs(tAddress: tAddress, startHeight: startHeight) { [weak self] fetchResult in
|
||||
switch fetchResult {
|
||||
case .success(let utxos):
|
||||
DispatchQueue.main.async {
|
||||
self?.operationQueue.addOperation { [self] in
|
||||
guard let self = self else { return }
|
||||
do {
|
||||
guard try self.rustBackend.clearUtxos(
|
||||
dbData: dataDb,
|
||||
address: tAddress,
|
||||
sinceHeight: startHeight - 1,
|
||||
networkType: self.config.network.networkType
|
||||
) >= 0 else {
|
||||
result(.failure(CompactBlockProcessorError.generalError(message: "attempted to clear utxos but -1 was returned")))
|
||||
return
|
||||
}
|
||||
} catch {
|
||||
result(.failure(self.mapError(error)))
|
||||
}
|
||||
result(.success(self.storeUTXOs(utxos, in: dataDb)))
|
||||
}
|
||||
}
|
||||
case .failure(let error):
|
||||
result(.failure(self?.mapError(error) ?? error))
|
||||
|
||||
let stream: AsyncThrowingStream<UnspentTransactionOutputEntity, Error> = downloader.fetchUnspentTransactionOutputs(tAddress: tAddress, startHeight: startHeight)
|
||||
var utxos: [UnspentTransactionOutputEntity] = []
|
||||
|
||||
do {
|
||||
for try await utxo in stream {
|
||||
utxos.append(utxo)
|
||||
}
|
||||
guard try rustBackend.clearUtxos(
|
||||
dbData: dataDb,
|
||||
address: tAddress,
|
||||
sinceHeight: startHeight - 1,
|
||||
networkType: self.config.network.networkType
|
||||
) >= 0 else {
|
||||
throw CompactBlockProcessorError.generalError(message: "attempted to clear utxos but -1 was returned")
|
||||
}
|
||||
return storeUTXOs(utxos, in: dataDb)
|
||||
} catch {
|
||||
throw mapError(error)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1351,6 +1100,7 @@ extension CompactBlockProcessorError: LocalizedError {
|
|||
case let .wrongConsensusBranchId(expectedLocally, found):
|
||||
// swiftlint:disable:next line_length
|
||||
return "The remote server you are connecting to is publishing a different branch ID \(found) than the one your App is expecting to be (\(expectedLocally)). This could be caused by your App being out of date or the server you are connecting you being either on a different network or out of date after a network upgrade."
|
||||
case .unknown: return "Unknown error occured."
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1370,15 +1120,9 @@ extension CompactBlockProcessorError: LocalizedError {
|
|||
}
|
||||
}
|
||||
|
||||
extension CompactBlockProcessor: CompactBlockProgressDelegate {
|
||||
func progressUpdated(_ progress: CompactBlockProgress) {
|
||||
notifyProgress(progress)
|
||||
}
|
||||
}
|
||||
|
||||
extension CompactBlockProcessor: EnhancementStreamDelegate {
|
||||
func transactionEnhancementProgressUpdated(_ progress: EnhancementProgress) {
|
||||
NotificationCenter.default.post(
|
||||
NotificationCenter.default.mainThreadPost(
|
||||
name: .blockProcessorEnhancementProgress,
|
||||
object: self,
|
||||
userInfo: [CompactBlockProcessorNotificationKey.enhancementProgress: progress]
|
||||
|
@ -1388,65 +1132,46 @@ extension CompactBlockProcessor: EnhancementStreamDelegate {
|
|||
|
||||
extension CompactBlockProcessor {
|
||||
enum NextStateHelper {
|
||||
// swiftlint:disable:next function_parameter_count
|
||||
static func nextState(
|
||||
service: LightWalletService,
|
||||
downloader: CompactBlockDownloading,
|
||||
config: Configuration,
|
||||
rustBackend: ZcashRustBackendWelding.Type,
|
||||
queue: DispatchQueue?,
|
||||
result: @escaping (Result<FigureNextBatchOperation.NextState, Error>) -> Void
|
||||
) {
|
||||
let dispatchQueue = queue ?? DispatchQueue.global(qos: .userInitiated)
|
||||
|
||||
dispatchQueue.async {
|
||||
do {
|
||||
let nextResult = try self.nextState(
|
||||
service: service,
|
||||
downloader: downloader,
|
||||
config: config,
|
||||
rustBackend: rustBackend
|
||||
)
|
||||
result(.success(nextResult))
|
||||
} catch {
|
||||
result(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func nextState(
|
||||
static func nextStateAsync(
|
||||
service: LightWalletService,
|
||||
downloader: CompactBlockDownloading,
|
||||
config: Configuration,
|
||||
rustBackend: ZcashRustBackendWelding.Type
|
||||
) throws -> FigureNextBatchOperation.NextState {
|
||||
let info = try service.getInfo()
|
||||
|
||||
try CompactBlockProcessor.validateServerInfo(
|
||||
info,
|
||||
saplingActivation: config.saplingActivation,
|
||||
localNetwork: config.network,
|
||||
rustBackend: rustBackend
|
||||
)
|
||||
|
||||
// get latest block height
|
||||
let latestDownloadedBlockHeight: BlockHeight = max(config.walletBirthday, try downloader.lastDownloadedBlockHeight())
|
||||
|
||||
let latestBlockheight = try service.latestBlockHeight()
|
||||
|
||||
if latestDownloadedBlockHeight < latestBlockheight {
|
||||
return .processNewBlocks(
|
||||
range: CompactBlockProcessor.nextBatchBlockRange(
|
||||
latestHeight: latestBlockheight,
|
||||
latestDownloadedHeight: latestDownloadedBlockHeight,
|
||||
walletBirthday: config.walletBirthday
|
||||
) async throws -> NextState {
|
||||
let task = Task(priority: .userInitiated) {
|
||||
do {
|
||||
let info = try await service.getInfo()
|
||||
|
||||
try CompactBlockProcessor.validateServerInfo(
|
||||
info,
|
||||
saplingActivation: config.saplingActivation,
|
||||
localNetwork: config.network,
|
||||
rustBackend: rustBackend
|
||||
)
|
||||
)
|
||||
} else if latestBlockheight == latestDownloadedBlockHeight {
|
||||
return .finishProcessing(height: latestBlockheight)
|
||||
|
||||
// get latest block height
|
||||
let latestDownloadedBlockHeight: BlockHeight = max(config.walletBirthday, try downloader.lastDownloadedBlockHeight())
|
||||
|
||||
let latestBlockheight = try service.latestBlockHeight()
|
||||
|
||||
if latestDownloadedBlockHeight < latestBlockheight {
|
||||
return NextState.processNewBlocks(
|
||||
range: CompactBlockProcessor.nextBatchBlockRange(
|
||||
latestHeight: latestBlockheight,
|
||||
latestDownloadedHeight: latestDownloadedBlockHeight,
|
||||
walletBirthday: config.walletBirthday
|
||||
)
|
||||
)
|
||||
} else if latestBlockheight == latestDownloadedBlockHeight {
|
||||
return .finishProcessing(height: latestBlockheight)
|
||||
}
|
||||
|
||||
return .wait(latestHeight: latestBlockheight, latestDownloadHeight: latestBlockheight)
|
||||
} catch {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
return .wait(latestHeight: latestBlockheight, latestDownloadHeight: latestBlockheight)
|
||||
return try await task.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// CompactBlockProcessingOperation.swift
|
||||
// CompactBlockProcessing.swift
|
||||
// ZcashLightClientKit
|
||||
//
|
||||
// Created by Francisco Gindre on 10/15/19.
|
||||
|
@ -8,37 +8,108 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
class CompactBlockScanningOperation: ZcashOperation {
|
||||
override var isConcurrent: Bool { false }
|
||||
|
||||
override var isAsynchronous: Bool { false }
|
||||
|
||||
var rustBackend: ZcashRustBackendWelding.Type
|
||||
|
||||
private var cacheDb: URL
|
||||
private var dataDb: URL
|
||||
private var limit: UInt32
|
||||
private var network: NetworkType
|
||||
init(rustWelding: ZcashRustBackendWelding.Type, cacheDb: URL, dataDb: URL, limit: UInt32 = 0, networkType: NetworkType) {
|
||||
rustBackend = rustWelding
|
||||
self.cacheDb = cacheDb
|
||||
self.dataDb = dataDb
|
||||
self.limit = limit
|
||||
self.network = networkType
|
||||
super.init()
|
||||
}
|
||||
|
||||
override func main() {
|
||||
guard !shouldCancel() else {
|
||||
cancel()
|
||||
return
|
||||
extension CompactBlockProcessor {
|
||||
func compactBlockBatchScanning(range: CompactBlockRange) async throws {
|
||||
try Task.checkCancellation()
|
||||
|
||||
state = .scanning
|
||||
let batchSize = UInt32(config.scanningBatchSize)
|
||||
|
||||
do {
|
||||
if batchSize == 0 {
|
||||
let scanStartTime = Date()
|
||||
guard self.rustBackend.scanBlocks(dbCache: config.cacheDb, dbData: config.dataDb, limit: batchSize, networkType: config.network.networkType) else {
|
||||
let error: Error = rustBackend.lastError() ?? CompactBlockProcessorError.unknown
|
||||
LoggerProxy.debug("block scanning failed with error: \(String(describing: error))")
|
||||
throw error
|
||||
}
|
||||
let scanFinishTime = Date()
|
||||
NotificationCenter.default.mainThreadPostNotification(
|
||||
SDKMetrics.progressReportNotification(
|
||||
progress: BlockProgress(
|
||||
startHeight: range.lowerBound,
|
||||
targetHeight: range.upperBound,
|
||||
progressHeight: range.upperBound
|
||||
),
|
||||
start: scanStartTime,
|
||||
end: scanFinishTime,
|
||||
task: .scanBlocks
|
||||
)
|
||||
)
|
||||
let seconds = scanFinishTime.timeIntervalSinceReferenceDate - scanStartTime.timeIntervalSinceReferenceDate
|
||||
LoggerProxy.debug("Scanned \(range.count) blocks in \(seconds) seconds")
|
||||
} else {
|
||||
let scanStartHeight = try transactionRepository.lastScannedHeight()
|
||||
let targetScanHeight = range.upperBound
|
||||
|
||||
var scannedNewBlocks = false
|
||||
var lastScannedHeight = scanStartHeight
|
||||
|
||||
repeat {
|
||||
try Task.checkCancellation()
|
||||
|
||||
let previousScannedHeight = lastScannedHeight
|
||||
let scanStartTime = Date()
|
||||
guard self.rustBackend.scanBlocks(
|
||||
dbCache: config.cacheDb,
|
||||
dbData: config.dataDb,
|
||||
limit: batchSize,
|
||||
networkType: config.network.networkType
|
||||
) else {
|
||||
let error: Error = rustBackend.lastError() ?? CompactBlockProcessorError.unknown
|
||||
LoggerProxy.debug("block scanning failed with error: \(String(describing: error))")
|
||||
throw error
|
||||
}
|
||||
let scanFinishTime = Date()
|
||||
|
||||
lastScannedHeight = try transactionRepository.lastScannedHeight()
|
||||
|
||||
scannedNewBlocks = previousScannedHeight != lastScannedHeight
|
||||
if scannedNewBlocks {
|
||||
let progress = BlockProgress(startHeight: scanStartHeight, targetHeight: targetScanHeight, progressHeight: lastScannedHeight)
|
||||
notifyProgress(.scan(progress))
|
||||
NotificationCenter.default.mainThreadPostNotification(
|
||||
SDKMetrics.progressReportNotification(
|
||||
progress: progress,
|
||||
start: scanStartTime,
|
||||
end: scanFinishTime,
|
||||
task: .scanBlocks
|
||||
)
|
||||
)
|
||||
|
||||
let heightCount = lastScannedHeight - previousScannedHeight
|
||||
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 {
|
||||
state = .stopped
|
||||
LoggerProxy.debug("Warning: compactBlockBatchScanning cancelled")
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
LoggerProxy.debug("block scanning failed with error: \(String(describing: error))")
|
||||
throw error
|
||||
}
|
||||
self.startedHandler?()
|
||||
guard self.rustBackend.scanBlocks(dbCache: self.cacheDb, dbData: self.dataDb, limit: limit, networkType: network) else {
|
||||
self.error = self.rustBackend.lastError() ?? ZcashOperationError.unknown
|
||||
LoggerProxy.debug("block scanning failed with error: \(String(describing: self.error))")
|
||||
self.fail()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
extension CompactBlockProcessor {
|
||||
func compactBlockScanning(
|
||||
rustWelding: ZcashRustBackendWelding.Type,
|
||||
cacheDb: URL,
|
||||
dataDb: URL,
|
||||
limit: UInt32 = 0,
|
||||
networkType: NetworkType
|
||||
) throws {
|
||||
try Task.checkCancellation()
|
||||
|
||||
guard rustBackend.scanBlocks(dbCache: cacheDb, dbData: dataDb, limit: limit, networkType: networkType) else {
|
||||
let error: Error = rustBackend.lastError() ?? CompactBlockProcessorError.unknown
|
||||
LoggerProxy.debug("block scanning failed with error: \(String(describing: error))")
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -112,127 +183,3 @@ extension String.StringInterpolation {
|
|||
appendLiteral(literal)
|
||||
}
|
||||
}
|
||||
|
||||
class CompactBlockBatchScanningOperation: ZcashOperation {
|
||||
override var isConcurrent: Bool { false }
|
||||
override var isAsynchronous: Bool { false }
|
||||
|
||||
var rustBackend: ZcashRustBackendWelding.Type
|
||||
|
||||
private var cacheDb: URL
|
||||
private var dataDb: URL
|
||||
private var batchSize: UInt32
|
||||
private var blockRange: CompactBlockRange
|
||||
private var transactionRepository: TransactionRepository
|
||||
private var network: NetworkType
|
||||
|
||||
private weak var progressDelegate: CompactBlockProgressDelegate?
|
||||
|
||||
init(
|
||||
rustWelding: ZcashRustBackendWelding.Type,
|
||||
cacheDb: URL,
|
||||
dataDb: URL,
|
||||
transactionRepository: TransactionRepository,
|
||||
range: CompactBlockRange,
|
||||
batchSize: UInt32,
|
||||
networkType: NetworkType,
|
||||
progressDelegate: CompactBlockProgressDelegate? = nil
|
||||
) {
|
||||
rustBackend = rustWelding
|
||||
self.cacheDb = cacheDb
|
||||
self.dataDb = dataDb
|
||||
self.transactionRepository = transactionRepository
|
||||
self.blockRange = range
|
||||
self.batchSize = batchSize
|
||||
self.progressDelegate = progressDelegate
|
||||
self.network = networkType
|
||||
super.init()
|
||||
}
|
||||
|
||||
override func main() {
|
||||
guard !shouldCancel() else {
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
|
||||
self.startedHandler?()
|
||||
|
||||
do {
|
||||
if batchSize == 0 {
|
||||
let scanStartTime = Date()
|
||||
guard self.rustBackend.scanBlocks(dbCache: self.cacheDb, dbData: self.dataDb, limit: batchSize, networkType: network) else {
|
||||
self.scanFailed(self.rustBackend.lastError() ?? ZcashOperationError.unknown)
|
||||
return
|
||||
}
|
||||
let scanFinishTime = Date()
|
||||
NotificationCenter.default.post(
|
||||
SDKMetrics.progressReportNotification(
|
||||
progress: BlockProgress(
|
||||
startHeight: self.blockRange.lowerBound,
|
||||
targetHeight: self.blockRange.upperBound,
|
||||
progressHeight: self.blockRange.upperBound
|
||||
),
|
||||
start: scanStartTime,
|
||||
end: scanFinishTime,
|
||||
task: .scanBlocks
|
||||
)
|
||||
)
|
||||
let seconds = scanFinishTime.timeIntervalSinceReferenceDate - scanStartTime.timeIntervalSinceReferenceDate
|
||||
LoggerProxy.debug("Scanned \(blockRange.count) blocks in \(seconds) seconds")
|
||||
} else {
|
||||
let scanStartHeight = try transactionRepository.lastScannedHeight()
|
||||
let targetScanHeight = blockRange.upperBound
|
||||
|
||||
var scannedNewBlocks = false
|
||||
var lastScannedHeight = scanStartHeight
|
||||
|
||||
repeat {
|
||||
guard !shouldCancel() else {
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
let previousScannedHeight = lastScannedHeight
|
||||
let scanStartTime = Date()
|
||||
guard self.rustBackend.scanBlocks(
|
||||
dbCache: self.cacheDb,
|
||||
dbData: self.dataDb,
|
||||
limit: batchSize,
|
||||
networkType: network
|
||||
) else {
|
||||
self.scanFailed(self.rustBackend.lastError() ?? ZcashOperationError.unknown)
|
||||
return
|
||||
}
|
||||
let scanFinishTime = Date()
|
||||
|
||||
lastScannedHeight = try transactionRepository.lastScannedHeight()
|
||||
|
||||
scannedNewBlocks = previousScannedHeight != lastScannedHeight
|
||||
if scannedNewBlocks {
|
||||
let progress = BlockProgress(startHeight: scanStartHeight, targetHeight: targetScanHeight, progressHeight: lastScannedHeight)
|
||||
progressDelegate?.progressUpdated(.scan(progress))
|
||||
NotificationCenter.default.post(
|
||||
SDKMetrics.progressReportNotification(
|
||||
progress: progress,
|
||||
start: scanStartTime,
|
||||
end: scanFinishTime,
|
||||
task: .scanBlocks
|
||||
)
|
||||
)
|
||||
|
||||
let heightCount = lastScannedHeight - previousScannedHeight
|
||||
let seconds = scanFinishTime.timeIntervalSinceReferenceDate - scanStartTime.timeIntervalSinceReferenceDate
|
||||
LoggerProxy.debug("Scanned \(heightCount) blocks in \(seconds) seconds")
|
||||
}
|
||||
} while !self.isCancelled && scannedNewBlocks && lastScannedHeight < targetScanHeight
|
||||
}
|
||||
} catch {
|
||||
scanFailed(error)
|
||||
}
|
||||
}
|
||||
|
||||
func scanFailed(_ error: Error) {
|
||||
self.error = error
|
||||
LoggerProxy.debug("block scanning failed with error: \(String(describing: self.error))")
|
||||
self.fail()
|
||||
}
|
||||
}
|
|
@ -8,59 +8,58 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
enum CompactBlockValidationError: Error {
|
||||
case validationFailed(height: BlockHeight)
|
||||
case failedWithError(_ error: Error?)
|
||||
}
|
||||
class CompactBlockValidationOperation: ZcashOperation {
|
||||
override var isConcurrent: Bool { false }
|
||||
|
||||
override var isAsynchronous: Bool { false }
|
||||
|
||||
var rustBackend: ZcashRustBackendWelding.Type
|
||||
|
||||
private var cacheDb: URL
|
||||
private var dataDb: URL
|
||||
private var network: NetworkType
|
||||
|
||||
init(
|
||||
rustWelding: ZcashRustBackendWelding.Type,
|
||||
cacheDb: URL,
|
||||
dataDb: URL,
|
||||
networkType: NetworkType
|
||||
) {
|
||||
rustBackend = rustWelding
|
||||
self.cacheDb = cacheDb
|
||||
self.dataDb = dataDb
|
||||
self.network = networkType
|
||||
super.init()
|
||||
extension CompactBlockProcessor {
|
||||
enum CompactBlockValidationError: Error {
|
||||
case validationFailed(height: BlockHeight)
|
||||
case failedWithError(_ error: Error?)
|
||||
}
|
||||
|
||||
override func main() {
|
||||
guard !shouldCancel() else {
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
|
||||
self.startedHandler?()
|
||||
func compactBlockValidation() async throws {
|
||||
try Task.checkCancellation()
|
||||
|
||||
state = .validating
|
||||
|
||||
let result = self.rustBackend.validateCombinedChain(dbCache: cacheDb, dbData: dataDb, networkType: self.network)
|
||||
|
||||
switch result {
|
||||
case 0:
|
||||
let error = CompactBlockValidationError.failedWithError(rustBackend.lastError())
|
||||
self.error = error
|
||||
LoggerProxy.debug("block scanning failed with error: \(String(describing: self.error))")
|
||||
self.fail(error: error)
|
||||
let result = rustBackend.validateCombinedChain(dbCache: config.cacheDb, dbData: config.dataDb, networkType: config.network.networkType)
|
||||
|
||||
do {
|
||||
switch result {
|
||||
case 0:
|
||||
let error = CompactBlockValidationError.failedWithError(rustBackend.lastError())
|
||||
LoggerProxy.debug("block scanning failed with error: \(String(describing: error))")
|
||||
throw error
|
||||
|
||||
case ZcashRustBackendWeldingConstants.validChain:
|
||||
if Task.isCancelled {
|
||||
state = .stopped
|
||||
LoggerProxy.debug("Warning: compactBlockValidation cancelled")
|
||||
}
|
||||
LoggerProxy.debug("validateChainFinished")
|
||||
break
|
||||
|
||||
default:
|
||||
let error = CompactBlockValidationError.validationFailed(height: BlockHeight(result))
|
||||
LoggerProxy.debug("block scanning failed with error: \(String(describing: error))")
|
||||
throw error
|
||||
}
|
||||
} catch {
|
||||
guard let validationError = error as? CompactBlockValidationError else {
|
||||
LoggerProxy.error("Warning: compactBlockValidation returning generic error: \(error)")
|
||||
return
|
||||
}
|
||||
|
||||
case ZcashRustBackendWeldingConstants.validChain:
|
||||
break
|
||||
|
||||
default:
|
||||
let error = CompactBlockValidationError.validationFailed(height: BlockHeight(result))
|
||||
self.error = error
|
||||
LoggerProxy.debug("block scanning failed with error: \(String(describing: self.error))")
|
||||
self.fail(error: error)
|
||||
switch validationError {
|
||||
case .validationFailed(let height):
|
||||
LoggerProxy.debug("chain validation at height: \(height)")
|
||||
await validationFailed(at: height)
|
||||
case .failedWithError(let err):
|
||||
guard let validationFailure = err else {
|
||||
LoggerProxy.error("validation failed without a specific error")
|
||||
await self.fail(CompactBlockProcessorError.generalError(message: "validation failed without a specific error"))
|
||||
return
|
||||
}
|
||||
|
||||
throw validationFailure
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
//
|
||||
// FetchUnspentTxOutputs.swift
|
||||
// ZcashLightClientKit
|
||||
//
|
||||
// Created by Francisco Gindre on 6/2/21.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension CompactBlockProcessor {
|
||||
enum FetchUTXOError: Error {
|
||||
case clearingFailed(_ error: Error?)
|
||||
case fetchFailed(error: Error)
|
||||
}
|
||||
|
||||
func fetchUnspentTxOutputs(range: CompactBlockRange) async throws {
|
||||
try Task.checkCancellation()
|
||||
|
||||
state = .fetching
|
||||
|
||||
do {
|
||||
let tAddresses = try accountRepository.getAll().map({ $0.transparentAddress })
|
||||
do {
|
||||
for tAddress in tAddresses {
|
||||
guard try rustBackend.clearUtxos(
|
||||
dbData: config.dataDb,
|
||||
address: tAddress,
|
||||
sinceHeight: config.walletBirthday - 1,
|
||||
networkType: config.network.networkType
|
||||
) >= 0 else {
|
||||
throw rustBackend.lastError() ?? RustWeldingError.genericError(message: "attempted to clear utxos but -1 was returned")
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
throw FetchUTXOError.clearingFailed(error)
|
||||
}
|
||||
|
||||
var utxos: [UnspentTransactionOutputEntity] = []
|
||||
let stream: AsyncThrowingStream<UnspentTransactionOutputEntity, Error> = downloader.fetchUnspentTransactionOutputs(tAddresses: tAddresses, startHeight: config.walletBirthday)
|
||||
for try await transaction in stream {
|
||||
utxos.append(transaction)
|
||||
}
|
||||
|
||||
var refreshed: [UnspentTransactionOutputEntity] = []
|
||||
var skipped: [UnspentTransactionOutputEntity] = []
|
||||
|
||||
for utxo in utxos {
|
||||
do {
|
||||
try rustBackend.putUnspentTransparentOutput(
|
||||
dbData: config.dataDb,
|
||||
address: utxo.address,
|
||||
txid: utxo.txid.bytes,
|
||||
index: utxo.index,
|
||||
script: utxo.script.bytes,
|
||||
value: Int64(utxo.valueZat),
|
||||
height: utxo.height,
|
||||
networkType: config.network.networkType
|
||||
) ? refreshed.append(utxo) : skipped.append(utxo)
|
||||
} catch {
|
||||
LoggerProxy.error("failed to put utxo - error: \(error)")
|
||||
skipped.append(utxo)
|
||||
}
|
||||
}
|
||||
|
||||
let result = (inserted: refreshed, skipped: skipped)
|
||||
|
||||
NotificationCenter.default.mainThreadPost(
|
||||
name: .blockProcessorStoredUTXOs,
|
||||
object: self,
|
||||
userInfo: [CompactBlockProcessorNotificationKey.refreshedUTXOs: result]
|
||||
)
|
||||
|
||||
if Task.isCancelled {
|
||||
LoggerProxy.debug("Warning: fetchUnspentTxOutputs on range \(range) cancelled")
|
||||
} else {
|
||||
await processBatchFinished(range: range)
|
||||
}
|
||||
} catch {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,103 +0,0 @@
|
|||
//
|
||||
// FetchUnspentTxOutputsOperation.swift
|
||||
// ZcashLightClientKit
|
||||
//
|
||||
// Created by Francisco Gindre on 6/2/21.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class FetchUnspentTxOutputsOperation: ZcashOperation {
|
||||
enum FetchUTXOError: Error {
|
||||
case clearingFailed(_ error: Error?)
|
||||
case fetchFailed(error: Error)
|
||||
}
|
||||
|
||||
override var isConcurrent: Bool { false }
|
||||
override var isAsynchronous: Bool { false }
|
||||
|
||||
var fetchedUTXOsHandler: ((RefreshedUTXOs) -> Void)?
|
||||
|
||||
private var accountRepository: AccountRepository
|
||||
private var downloader: CompactBlockDownloading
|
||||
private var rustbackend: ZcashRustBackendWelding.Type
|
||||
private var startHeight: BlockHeight
|
||||
private var network: NetworkType
|
||||
private var dataDb: URL
|
||||
|
||||
init(
|
||||
accountRepository: AccountRepository,
|
||||
downloader: CompactBlockDownloading,
|
||||
rustbackend: ZcashRustBackendWelding.Type,
|
||||
dataDb: URL,
|
||||
startHeight: BlockHeight,
|
||||
networkType: NetworkType
|
||||
) {
|
||||
self.dataDb = dataDb
|
||||
self.accountRepository = accountRepository
|
||||
self.downloader = downloader
|
||||
self.rustbackend = rustbackend
|
||||
self.startHeight = startHeight
|
||||
self.network = networkType
|
||||
}
|
||||
|
||||
override func main() {
|
||||
guard !shouldCancel() else {
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
|
||||
self.startedHandler?()
|
||||
|
||||
do {
|
||||
let tAddresses = try accountRepository.getAll().map({ $0.transparentAddress })
|
||||
do {
|
||||
for tAddress in tAddresses {
|
||||
guard try self.rustbackend.clearUtxos(
|
||||
dbData: dataDb,
|
||||
address: tAddress,
|
||||
sinceHeight: startHeight - 1,
|
||||
networkType: network
|
||||
) >= 0 else {
|
||||
throw rustbackend.lastError() ?? RustWeldingError.genericError(message: "attempted to clear utxos but -1 was returned")
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
throw FetchUTXOError.clearingFailed(error)
|
||||
}
|
||||
|
||||
let utxos = try downloader.fetchUnspentTransactionOutputs(tAddresses: tAddresses, startHeight: startHeight)
|
||||
|
||||
let result = storeUTXOs(utxos, in: dataDb)
|
||||
|
||||
self.fetchedUTXOsHandler?(result)
|
||||
} catch {
|
||||
self.fail(error: error)
|
||||
}
|
||||
}
|
||||
|
||||
private func storeUTXOs(_ utxos: [UnspentTransactionOutputEntity], in dataDb: URL) -> RefreshedUTXOs {
|
||||
var refreshed: [UnspentTransactionOutputEntity] = []
|
||||
var skipped: [UnspentTransactionOutputEntity] = []
|
||||
|
||||
for utxo in utxos {
|
||||
do {
|
||||
try self.rustbackend.putUnspentTransparentOutput(
|
||||
dbData: dataDb,
|
||||
address: utxo.address,
|
||||
txid: utxo.txid.bytes,
|
||||
index: utxo.index,
|
||||
script: utxo.script.bytes,
|
||||
value: Int64(utxo.valueZat),
|
||||
height: utxo.height,
|
||||
networkType: network
|
||||
) ? refreshed.append(utxo) : skipped.append(utxo)
|
||||
} catch {
|
||||
LoggerProxy.error("failed to put utxo - error: \(error)")
|
||||
skipped.append(utxo)
|
||||
}
|
||||
}
|
||||
|
||||
return (inserted: refreshed, skipped: skipped)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
//
|
||||
// FigureNextBatch.swift
|
||||
// ZcashLightClientKit
|
||||
//
|
||||
// Created by Francisco Gindre on 6/17/21.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension CompactBlockProcessor {
|
||||
enum NextState {
|
||||
case finishProcessing(height: BlockHeight)
|
||||
case processNewBlocks(range: CompactBlockRange)
|
||||
case wait(latestHeight: BlockHeight, latestDownloadHeight: BlockHeight)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func figureNextBatch(
|
||||
downloader: CompactBlockDownloading
|
||||
) async throws -> NextState {
|
||||
try Task.checkCancellation()
|
||||
|
||||
do {
|
||||
return try await CompactBlockProcessor.NextStateHelper.nextStateAsync(
|
||||
service: service,
|
||||
downloader: downloader,
|
||||
config: config,
|
||||
rustBackend: rustBackend
|
||||
)
|
||||
} catch {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
//
|
||||
// FigureNextBatchOperation.swift
|
||||
// ZcashLightClientKit
|
||||
//
|
||||
// Created by Francisco Gindre on 6/17/21.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class FigureNextBatchOperation: ZcashOperation {
|
||||
enum NextState {
|
||||
case finishProcessing(height: BlockHeight)
|
||||
case processNewBlocks(range: CompactBlockRange)
|
||||
case wait(latestHeight: BlockHeight, latestDownloadHeight: BlockHeight)
|
||||
}
|
||||
|
||||
private var service: LightWalletService
|
||||
private var downloader: CompactBlockDownloading
|
||||
private var config: CompactBlockProcessor.Configuration
|
||||
private var rustBackend: ZcashRustBackendWelding.Type
|
||||
private(set) var result: NextState?
|
||||
|
||||
required init(
|
||||
downloader: CompactBlockDownloading,
|
||||
service: LightWalletService,
|
||||
config: CompactBlockProcessor.Configuration,
|
||||
rustBackend: ZcashRustBackendWelding.Type
|
||||
) {
|
||||
self.service = service
|
||||
self.config = config
|
||||
self.downloader = downloader
|
||||
self.rustBackend = rustBackend
|
||||
|
||||
super.init()
|
||||
|
||||
self.name = "Next Batch Operation"
|
||||
}
|
||||
|
||||
override func main() {
|
||||
guard !shouldCancel() else {
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
|
||||
self.startedHandler?()
|
||||
|
||||
do {
|
||||
result = try CompactBlockProcessor.NextStateHelper.nextState(
|
||||
service: self.service,
|
||||
downloader: self.downloader,
|
||||
config: self.config,
|
||||
rustBackend: self.rustBackend
|
||||
)
|
||||
} catch {
|
||||
self.fail(error: error)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
//
|
||||
// ZcashOperation.swift
|
||||
// ZcashLightClientKit
|
||||
//
|
||||
// Created by Francisco Gindre on 10/27/19.
|
||||
// Copyright © 2019 Electric Coin Company. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
typealias ZcashOperationCompletionBlock = (_ finished: Bool, _ cancelled: Bool) -> Void
|
||||
typealias ZcashOperationStartedBlock = () -> Void
|
||||
typealias ZcashOperationErrorBlock = (_ error: Error) -> Void
|
||||
|
||||
enum ZcashOperationError: Error {
|
||||
case unknown
|
||||
}
|
||||
|
||||
class ZcashOperation: Operation {
|
||||
var error: Error?
|
||||
var startedHandler: ZcashOperationStartedBlock?
|
||||
var errorHandler: ZcashOperationErrorBlock?
|
||||
var completionHandler: ZcashOperationCompletionBlock?
|
||||
var handlerDispatchQueue = DispatchQueue.main
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
|
||||
completionBlock = { [weak self] in
|
||||
guard let self = self, let handler = self.completionHandler else { return }
|
||||
|
||||
handler(self.isFinished, self.isCancelled)
|
||||
}
|
||||
}
|
||||
|
||||
convenience init(completionDispatchQueue: DispatchQueue = DispatchQueue.main) {
|
||||
self.init()
|
||||
self.handlerDispatchQueue = completionDispatchQueue
|
||||
}
|
||||
|
||||
func shouldCancel() -> Bool {
|
||||
self.error != nil || isCancelled || dependencyCancelled()
|
||||
}
|
||||
|
||||
func dependencyCancelled() -> Bool {
|
||||
self.dependencies.first { $0.isCancelled } != nil
|
||||
}
|
||||
|
||||
func fail(error: Error? = nil) {
|
||||
defer {
|
||||
self.cancel()
|
||||
}
|
||||
|
||||
if let error = error {
|
||||
self.error = error
|
||||
}
|
||||
LoggerProxy.debug("\(self) failed")
|
||||
|
||||
guard let errorHandler = self.errorHandler else {
|
||||
return
|
||||
}
|
||||
|
||||
self.handlerDispatchQueue.async { [weak self] in
|
||||
let error = error ?? (self?.error ?? ZcashOperationError.unknown)
|
||||
errorHandler(error)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -320,48 +320,35 @@ public class Initializer {
|
|||
FileManager.default.isReadableFile(atPath: self.outputParamsURL.path)
|
||||
}
|
||||
|
||||
func downloadParametersIfNeeded(result: @escaping (Result<Bool, Error>) -> Void) {
|
||||
@discardableResult
|
||||
func downloadParametersIfNeeded() async throws -> Bool {
|
||||
let spendParameterPresent = isSpendParameterPresent()
|
||||
let outputParameterPresent = isOutputParameterPresent()
|
||||
|
||||
if spendParameterPresent && outputParameterPresent {
|
||||
result(.success(true))
|
||||
return
|
||||
return true
|
||||
}
|
||||
|
||||
let outputURL = self.outputParamsURL
|
||||
let spendURL = self.spendParamsURL
|
||||
|
||||
if !outputParameterPresent {
|
||||
SaplingParameterDownloader.downloadOutputParameter(outputURL) { outputResult in
|
||||
switch outputResult {
|
||||
case .failure(let error):
|
||||
result(.failure(error))
|
||||
case .success:
|
||||
guard !spendParameterPresent else {
|
||||
result(.success(false))
|
||||
return
|
||||
}
|
||||
SaplingParameterDownloader.downloadSpendParameter(spendURL) { spendResult in
|
||||
switch spendResult {
|
||||
case .failure(let error):
|
||||
result(.failure(error))
|
||||
case .success:
|
||||
result(.success(false))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if !spendParameterPresent {
|
||||
SaplingParameterDownloader.downloadSpendParameter(spendURL) { spendResult in
|
||||
switch spendResult {
|
||||
case .failure(let error):
|
||||
result(.failure(error))
|
||||
case .success:
|
||||
result(.success(false))
|
||||
}
|
||||
do {
|
||||
if !outputParameterPresent && !spendParameterPresent {
|
||||
async let outputURLRequest = SaplingParameterDownloader.downloadOutputParameter(outputURL)
|
||||
async let spendURLRequest = SaplingParameterDownloader.downloadSpendParameter(spendURL)
|
||||
_ = try await [outputURLRequest, spendURLRequest]
|
||||
return false
|
||||
} else if !outputParameterPresent {
|
||||
try await SaplingParameterDownloader.downloadOutputParameter(outputURL)
|
||||
return false
|
||||
} else if !spendParameterPresent {
|
||||
try await SaplingParameterDownloader.downloadSpendParameter(spendURL)
|
||||
return false
|
||||
}
|
||||
} catch {
|
||||
throw error
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -28,28 +28,18 @@ protocol CompactBlockRepository {
|
|||
/**
|
||||
Gets the highest block that is currently stored.
|
||||
Non-Blocking
|
||||
|
||||
- Parameter result: closure resulting on either the latest height or an error
|
||||
*/
|
||||
func latestHeight(result: @escaping (Result<BlockHeight, Error>) -> Void)
|
||||
|
||||
/**
|
||||
Write the given blocks to this store, which may be anything from an in-memory cache to a DB.
|
||||
Blocking
|
||||
- Parameter blocks: the compact blocks that will be written to storage
|
||||
- Throws: an error when there's a failure
|
||||
*/
|
||||
func write(blocks: [ZcashCompactBlock]) throws
|
||||
|
||||
func latestHeightAsync() async throws -> BlockHeight
|
||||
|
||||
/**
|
||||
Write the given blocks to this store, which may be anything from an in-memory cache to a DB.
|
||||
Non-Blocking
|
||||
- Parameters:
|
||||
- Parameter blocks: array of blocks to be written to storage
|
||||
- Parameter completion: a closure that will be called after storing the blocks
|
||||
- Throws: an error when there's a failure
|
||||
*/
|
||||
func write(blocks: [ZcashCompactBlock], completion: ((Error?) -> Void)?)
|
||||
|
||||
func write(blocks: [ZcashCompactBlock]) async throws
|
||||
|
||||
/**
|
||||
Remove every block above and including the given height.
|
||||
|
||||
|
@ -66,10 +56,7 @@ protocol CompactBlockRepository {
|
|||
After this operation, the data store will look the same as one that has not yet stored the given block height.
|
||||
Meaning, if max height is 100 block and rewindTo(50) is called, then the highest block remaining will be 49.
|
||||
|
||||
- Parameters:
|
||||
- Parameter height: the height to rewind to
|
||||
- Parameter completion: a closure that will be called after storing the blocks
|
||||
|
||||
*/
|
||||
func rewind(to height: BlockHeight, completion: ((Error?) -> Void)?)
|
||||
func rewindAsync(to height: BlockHeight) async throws
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import Foundation
|
|||
import GRPC
|
||||
import NIO
|
||||
import NIOHPACK
|
||||
import NIOTransportServices
|
||||
|
||||
public typealias Channel = GRPC.GRPCChannel
|
||||
|
||||
|
@ -90,7 +91,8 @@ public extension BlockProgress {
|
|||
public class LightWalletGRPCService {
|
||||
let channel: Channel
|
||||
let connectionManager: ConnectionStatusManager
|
||||
let compactTxStreamer: CompactTxStreamerClient
|
||||
let compactTxStreamer: CompactTxStreamerNIOClient
|
||||
let compactTxStreamerAsync: CompactTxStreamerAsyncClient
|
||||
let singleCallTimeout: TimeLimit
|
||||
let streamingCallTimeout: TimeLimit
|
||||
|
||||
|
@ -126,8 +128,8 @@ public class LightWalletGRPCService {
|
|||
self.singleCallTimeout = TimeLimit.timeout(.milliseconds(singleCallTimeout))
|
||||
|
||||
let connectionBuilder = secure ?
|
||||
ClientConnection.usingPlatformAppropriateTLS(for: MultiThreadedEventLoopGroup(numberOfThreads: 1)) :
|
||||
ClientConnection.insecure(group: MultiThreadedEventLoopGroup(numberOfThreads: 1))
|
||||
ClientConnection.usingPlatformAppropriateTLS(for: NIOTSEventLoopGroup(loopCount: 1, defaultQoS: .default)) :
|
||||
ClientConnection.insecure(group: NIOTSEventLoopGroup(loopCount: 1, defaultQoS: .default))
|
||||
|
||||
let channel = connectionBuilder
|
||||
.withConnectivityStateDelegate(connectionManager, executingOn: queue)
|
||||
|
@ -135,7 +137,14 @@ public class LightWalletGRPCService {
|
|||
|
||||
self.channel = channel
|
||||
|
||||
compactTxStreamer = CompactTxStreamerClient(
|
||||
compactTxStreamer = CompactTxStreamerNIOClient(
|
||||
channel: self.channel,
|
||||
defaultCallOptions: Self.callOptions(
|
||||
timeLimit: self.singleCallTimeout
|
||||
)
|
||||
)
|
||||
|
||||
compactTxStreamerAsync = CompactTxStreamerAsyncClient(
|
||||
channel: self.channel,
|
||||
defaultCallOptions: Self.callOptions(
|
||||
timeLimit: self.singleCallTimeout
|
||||
|
@ -146,6 +155,7 @@ public class LightWalletGRPCService {
|
|||
deinit {
|
||||
_ = channel.close()
|
||||
_ = compactTxStreamer.channel.close()
|
||||
_ = compactTxStreamerAsync.channel.close()
|
||||
}
|
||||
|
||||
func stop() {
|
||||
|
@ -155,7 +165,7 @@ public class LightWalletGRPCService {
|
|||
func blockRange(startHeight: BlockHeight, endHeight: BlockHeight? = nil, result: @escaping (CompactBlock) -> Void) throws -> ServerStreamingCall<BlockRange, CompactBlock> {
|
||||
compactTxStreamer.getBlockRange(BlockRange(startHeight: startHeight, endHeight: endHeight), handler: result)
|
||||
}
|
||||
|
||||
|
||||
func latestBlock() throws -> BlockID {
|
||||
try compactTxStreamer.getLatestBlock(ChainSpec()).response.wait()
|
||||
}
|
||||
|
@ -179,211 +189,67 @@ public class LightWalletGRPCService {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - LightWalletService
|
||||
|
||||
extension LightWalletGRPCService: LightWalletService {
|
||||
@discardableResult
|
||||
public func blockStream(
|
||||
startHeight: BlockHeight,
|
||||
endHeight: BlockHeight,
|
||||
result: @escaping (Result<GRPCResult, LightWalletServiceError>) -> Void,
|
||||
handler: @escaping (ZcashCompactBlock) -> Void,
|
||||
progress: @escaping (BlockProgress) -> Void
|
||||
) -> CancellableCall {
|
||||
let future = compactTxStreamer.getBlockRange(
|
||||
BlockRange(
|
||||
startHeight: startHeight,
|
||||
endHeight: endHeight
|
||||
),
|
||||
callOptions: Self.callOptions(timeLimit: self.streamingCallTimeout),
|
||||
handler: { compactBlock in
|
||||
handler(ZcashCompactBlock(compactBlock: compactBlock))
|
||||
progress(
|
||||
BlockProgress(
|
||||
startHeight: startHeight,
|
||||
targetHeight: endHeight,
|
||||
progressHeight: BlockHeight(compactBlock.height)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
future.status.whenComplete { completionResult in
|
||||
switch completionResult {
|
||||
case .success(let status):
|
||||
switch status.code {
|
||||
case .ok:
|
||||
result(.success(GRPCResult.success))
|
||||
default:
|
||||
result(.failure(LightWalletServiceError.mapCode(status)))
|
||||
}
|
||||
case .failure(let error):
|
||||
result(.failure(LightWalletServiceError.genericError(error: error)))
|
||||
}
|
||||
}
|
||||
return future
|
||||
}
|
||||
|
||||
public func getInfo() throws -> LightWalletdInfo {
|
||||
try compactTxStreamer.getLightdInfo(Empty()).response.wait()
|
||||
}
|
||||
|
||||
public func getInfo(result: @escaping (Result<LightWalletdInfo, LightWalletServiceError>) -> Void) {
|
||||
compactTxStreamer.getLightdInfo(Empty()).response.whenComplete { completionResult in
|
||||
switch completionResult {
|
||||
case .success(let info):
|
||||
result(.success(info))
|
||||
case .failure(let error):
|
||||
result(.failure(error.mapToServiceError()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func closeConnection() {
|
||||
_ = channel.close()
|
||||
}
|
||||
|
||||
public func fetchTransaction(txId: Data) throws -> TransactionEntity {
|
||||
var txFilter = TxFilter()
|
||||
txFilter.hash = txId
|
||||
|
||||
do {
|
||||
let rawTx = try compactTxStreamer.getTransaction(txFilter).response.wait()
|
||||
|
||||
return TransactionBuilder.createTransactionEntity(txId: txId, rawTransaction: rawTx)
|
||||
} catch {
|
||||
throw error.mapToServiceError()
|
||||
}
|
||||
}
|
||||
|
||||
public func fetchTransaction(txId: Data, result: @escaping (Result<TransactionEntity, LightWalletServiceError>) -> Void) {
|
||||
var txFilter = TxFilter()
|
||||
txFilter.hash = txId
|
||||
|
||||
compactTxStreamer.getTransaction(txFilter).response.whenComplete { response in
|
||||
switch response {
|
||||
case .failure(let error):
|
||||
result(.failure(error.mapToServiceError()))
|
||||
case .success(let rawTx):
|
||||
result(.success(TransactionBuilder.createTransactionEntity(txId: txId, rawTransaction: rawTx)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func submit(spendTransaction: Data, result: @escaping (Result<LightWalletServiceResponse, LightWalletServiceError>) -> Void) {
|
||||
do {
|
||||
let transaction = try RawTransaction(serializedData: spendTransaction)
|
||||
let response = self.compactTxStreamer.sendTransaction(transaction).response
|
||||
|
||||
response.whenComplete { responseResult in
|
||||
switch responseResult {
|
||||
case .failure(let error):
|
||||
result(.failure(LightWalletServiceError.sentFailed(error: error)))
|
||||
case .success(let success):
|
||||
result(.success(success))
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
result(.failure(error.mapToServiceError()))
|
||||
}
|
||||
}
|
||||
|
||||
public func submit(spendTransaction: Data) throws -> LightWalletServiceResponse {
|
||||
let rawTx = RawTransaction.with { raw in
|
||||
raw.data = spendTransaction
|
||||
}
|
||||
do {
|
||||
return try compactTxStreamer.sendTransaction(rawTx).response.wait()
|
||||
} catch {
|
||||
throw error.mapToServiceError()
|
||||
}
|
||||
}
|
||||
|
||||
public func blockRange(_ range: CompactBlockRange) throws -> [ZcashCompactBlock] {
|
||||
var blocks: [CompactBlock] = []
|
||||
|
||||
let response = compactTxStreamer.getBlockRange(
|
||||
range.blockRange(),
|
||||
handler: { blocks.append($0) }
|
||||
)
|
||||
|
||||
let status = try response.status.wait()
|
||||
|
||||
switch status.code {
|
||||
case .ok:
|
||||
return blocks.asZcashCompactBlocks()
|
||||
default:
|
||||
throw LightWalletServiceError.mapCode(status)
|
||||
}
|
||||
}
|
||||
|
||||
public func latestBlockHeight(result: @escaping (Result<BlockHeight, LightWalletServiceError>) -> Void) {
|
||||
let response = compactTxStreamer.getLatestBlock(ChainSpec()).response
|
||||
|
||||
response.whenSuccessBlocking(onto: queue) { blockID in
|
||||
guard let blockHeight = Int(exactly: blockID.height) else {
|
||||
result(.failure(LightWalletServiceError.generalError(message: "error creating blockheight from BlockID \(blockID)")))
|
||||
return
|
||||
}
|
||||
result(.success(blockHeight))
|
||||
}
|
||||
|
||||
response.whenFailureBlocking(onto: queue) { error in
|
||||
result(.failure(error.mapToServiceError()))
|
||||
}
|
||||
}
|
||||
|
||||
public func blockRange(_ range: CompactBlockRange, result: @escaping (Result<[ZcashCompactBlock], LightWalletServiceError>) -> Void) {
|
||||
queue.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
var blocks: [CompactBlock] = []
|
||||
let response = self.compactTxStreamer.getBlockRange(range.blockRange(), handler: { blocks.append($0) })
|
||||
|
||||
do {
|
||||
let status = try response.status.wait()
|
||||
switch status.code {
|
||||
case .ok:
|
||||
result(.success(blocks.asZcashCompactBlocks()))
|
||||
|
||||
default:
|
||||
result(.failure(.mapCode(status)))
|
||||
}
|
||||
} catch {
|
||||
result(.failure(error.mapToServiceError()))
|
||||
}
|
||||
}
|
||||
public func getInfo() async throws -> LightWalletdInfo {
|
||||
try await compactTxStreamerAsync.getLightdInfo(Empty())
|
||||
}
|
||||
|
||||
public func latestBlockHeight() throws -> BlockHeight {
|
||||
guard let height = try? latestBlock().compactBlockHeight() else {
|
||||
guard let height = try? compactTxStreamer.getLatestBlock(ChainSpec()).response.wait().compactBlockHeight() else {
|
||||
throw LightWalletServiceError.timeOut
|
||||
}
|
||||
return height
|
||||
}
|
||||
|
||||
public func fetchUTXOs(for tAddress: String, height: BlockHeight) throws -> [UnspentTransactionOutputEntity] {
|
||||
let arg = GetAddressUtxosArg.with { utxoArgs in
|
||||
utxoArgs.addresses = [tAddress]
|
||||
utxoArgs.startHeight = UInt64(height)
|
||||
|
||||
public func latestBlockHeightAsync() async throws -> BlockHeight {
|
||||
let blockID = try await compactTxStreamerAsync.getLatestBlock(ChainSpec())
|
||||
guard let blockHeight = Int(exactly: blockID.height) else {
|
||||
throw LightWalletServiceError.generalError(message: "error creating blockheight from BlockID \(blockID)")
|
||||
}
|
||||
do {
|
||||
return try self.compactTxStreamer.getAddressUtxos(arg).response.wait().addressUtxos.map { reply in
|
||||
UTXO(
|
||||
id: nil,
|
||||
address: tAddress,
|
||||
prevoutTxId: reply.txid,
|
||||
prevoutIndex: Int(reply.index),
|
||||
script: reply.script,
|
||||
valueZat: Int(reply.valueZat),
|
||||
height: Int(reply.height),
|
||||
spentInTx: nil
|
||||
)
|
||||
return blockHeight
|
||||
}
|
||||
|
||||
public func blockRange(_ range: CompactBlockRange) -> AsyncThrowingStream<ZcashCompactBlock, Error> {
|
||||
let stream = compactTxStreamerAsync.getBlockRange(range.blockRange())
|
||||
|
||||
return AsyncThrowingStream { continuation in
|
||||
Task {
|
||||
do {
|
||||
for try await block in stream {
|
||||
continuation.yield(ZcashCompactBlock(compactBlock: block))
|
||||
}
|
||||
continuation.finish(throwing: nil)
|
||||
} catch {
|
||||
continuation.finish(throwing: error.mapToServiceError())
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
throw error.mapToServiceError()
|
||||
}
|
||||
}
|
||||
|
||||
public func fetchUTXOs(for tAddress: String, height: BlockHeight, result: @escaping (Result<[UnspentTransactionOutputEntity], LightWalletServiceError>) -> Void) {
|
||||
public func submit(spendTransaction: Data) async throws -> LightWalletServiceResponse {
|
||||
do {
|
||||
let transaction = RawTransaction.with { $0.data = spendTransaction }
|
||||
return try await compactTxStreamerAsync.sendTransaction(transaction)
|
||||
} catch {
|
||||
throw LightWalletServiceError.sentFailed(error: error)
|
||||
}
|
||||
}
|
||||
|
||||
public func fetchTransaction(txId: Data) async throws -> TransactionEntity {
|
||||
var txFilter = TxFilter()
|
||||
txFilter.hash = txId
|
||||
|
||||
let rawTx = try await compactTxStreamerAsync.getTransaction(txFilter)
|
||||
return TransactionBuilder.createTransactionEntity(txId: txId, rawTransaction: rawTx)
|
||||
}
|
||||
|
||||
public func fetchUTXOs(
|
||||
for tAddress: String,
|
||||
height: BlockHeight,
|
||||
result: @escaping (Result<[UnspentTransactionOutputEntity], LightWalletServiceError>
|
||||
) -> Void) {
|
||||
queue.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
let arg = GetAddressUtxosArg.with { utxoArgs in
|
||||
|
@ -420,81 +286,81 @@ extension LightWalletGRPCService: LightWalletService {
|
|||
}
|
||||
}
|
||||
|
||||
public func fetchUTXOs(for tAddresses: [String], height: BlockHeight) throws -> [UnspentTransactionOutputEntity] {
|
||||
public func fetchUTXOs(
|
||||
for tAddress: String,
|
||||
height: BlockHeight
|
||||
) -> AsyncThrowingStream<UnspentTransactionOutputEntity, Error> {
|
||||
return fetchUTXOs(for: [tAddress], height: height)
|
||||
}
|
||||
|
||||
public func fetchUTXOs(
|
||||
for tAddresses: [String],
|
||||
height: BlockHeight
|
||||
) -> AsyncThrowingStream<UnspentTransactionOutputEntity, Error> {
|
||||
guard !tAddresses.isEmpty else {
|
||||
return [] // FIXME: throw a real error
|
||||
return AsyncThrowingStream { _ in }
|
||||
}
|
||||
|
||||
var utxos: [UnspentTransactionOutputEntity] = []
|
||||
|
||||
let arg = GetAddressUtxosArg.with { utxoArgs in
|
||||
let args = GetAddressUtxosArg.with { utxoArgs in
|
||||
utxoArgs.addresses = tAddresses
|
||||
utxoArgs.startHeight = UInt64(height)
|
||||
}
|
||||
utxos.append(
|
||||
contentsOf:
|
||||
try self.compactTxStreamer.getAddressUtxos(arg).response.wait().addressUtxos.map { reply in
|
||||
UTXO(
|
||||
id: nil,
|
||||
address: reply.address,
|
||||
prevoutTxId: reply.txid,
|
||||
prevoutIndex: Int(reply.index),
|
||||
script: reply.script,
|
||||
valueZat: Int(reply.valueZat),
|
||||
height: Int(reply.height),
|
||||
spentInTx: nil
|
||||
)
|
||||
let stream = compactTxStreamerAsync.getAddressUtxosStream(args)
|
||||
|
||||
return AsyncThrowingStream { continuation in
|
||||
Task {
|
||||
do {
|
||||
for try await reply in stream {
|
||||
continuation.yield(
|
||||
UTXO(
|
||||
id: nil,
|
||||
address: reply.address,
|
||||
prevoutTxId: reply.txid,
|
||||
prevoutIndex: Int(reply.index),
|
||||
script: reply.script,
|
||||
valueZat: Int(reply.valueZat),
|
||||
height: Int(reply.height),
|
||||
spentInTx: nil
|
||||
)
|
||||
)
|
||||
}
|
||||
continuation.finish(throwing: nil)
|
||||
} catch {
|
||||
continuation.finish(throwing: error.mapToServiceError())
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return utxos
|
||||
}
|
||||
}
|
||||
|
||||
public func fetchUTXOs(
|
||||
for tAddresses: [String],
|
||||
height: BlockHeight,
|
||||
result: @escaping (Result<[UnspentTransactionOutputEntity], LightWalletServiceError>) -> Void
|
||||
) {
|
||||
guard !tAddresses.isEmpty else {
|
||||
return result(.success([])) // FIXME: throw a real error
|
||||
}
|
||||
|
||||
var utxos: [UnspentTransactionOutputEntity] = []
|
||||
self.queue.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
let args = GetAddressUtxosArg.with { utxoArgs in
|
||||
utxoArgs.addresses = tAddresses
|
||||
utxoArgs.startHeight = UInt64(height)
|
||||
}
|
||||
do {
|
||||
let response = try self.compactTxStreamer.getAddressUtxosStream(args) { reply in
|
||||
utxos.append(
|
||||
UTXO(
|
||||
id: nil,
|
||||
address: reply.address,
|
||||
prevoutTxId: reply.txid,
|
||||
prevoutIndex: Int(reply.index),
|
||||
script: reply.script,
|
||||
valueZat: Int(reply.valueZat),
|
||||
height: Int(reply.height),
|
||||
spentInTx: nil
|
||||
)
|
||||
)
|
||||
}
|
||||
.status
|
||||
.wait()
|
||||
public func blockStream(
|
||||
startHeight: BlockHeight,
|
||||
endHeight: BlockHeight
|
||||
) -> AsyncThrowingStream<ZcashCompactBlock, Error> {
|
||||
let stream = compactTxStreamerAsync.getBlockRange(
|
||||
BlockRange(
|
||||
startHeight: startHeight,
|
||||
endHeight: endHeight
|
||||
),
|
||||
callOptions: Self.callOptions(timeLimit: self.streamingCallTimeout)
|
||||
)
|
||||
|
||||
switch response.code {
|
||||
case .ok:
|
||||
result(.success(utxos))
|
||||
default:
|
||||
result(.failure(.mapCode(response)))
|
||||
return AsyncThrowingStream { continuation in
|
||||
Task {
|
||||
do {
|
||||
for try await compactBlock in stream {
|
||||
continuation.yield(ZcashCompactBlock(compactBlock: compactBlock))
|
||||
}
|
||||
continuation.finish(throwing: nil)
|
||||
} catch {
|
||||
continuation.finish(throwing: error.mapToServiceError())
|
||||
}
|
||||
} catch {
|
||||
result(.failure(error.mapToServiceError()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func closeConnection() {
|
||||
_ = channel.close()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Extensions
|
||||
|
@ -551,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: [
|
||||
|
|
|
@ -102,113 +102,39 @@ public protocol LightWalletServiceResponse {
|
|||
extension SendResponse: LightWalletServiceResponse {}
|
||||
|
||||
public protocol LightWalletService {
|
||||
/**
|
||||
returns the info for this lightwalletd server (blocking)
|
||||
*/
|
||||
func getInfo() throws -> LightWalletdInfo
|
||||
|
||||
/**
|
||||
returns the info for this lightwalletd server
|
||||
*/
|
||||
func getInfo(result: @escaping (Result<LightWalletdInfo, LightWalletServiceError>) -> Void)
|
||||
/// Returns the info for this lightwalletd server
|
||||
func getInfo() async throws -> LightWalletdInfo
|
||||
|
||||
/**
|
||||
Return the latest block height known to the service.
|
||||
|
||||
- Parameter result: a result containing the height or an Error
|
||||
*/
|
||||
func latestBlockHeight(result: @escaping (Result<BlockHeight, LightWalletServiceError>) -> Void)
|
||||
|
||||
/**
|
||||
Return the latest block height known to the service.
|
||||
|
||||
- Parameter result: a result containing the height or an Error
|
||||
*/
|
||||
/// Return the latest block height known to the service.
|
||||
/// Blocking API
|
||||
func latestBlockHeight() throws -> BlockHeight
|
||||
|
||||
/**
|
||||
Return the given range of blocks.
|
||||
|
||||
- Parameter range: the inclusive range to fetch.
|
||||
For instance if 1..5 is given, then every block in that will be fetched, including 1 and 5.
|
||||
Non blocking
|
||||
*/
|
||||
func blockRange(_ range: CompactBlockRange, result: @escaping (Result<[ZcashCompactBlock], LightWalletServiceError>) -> Void )
|
||||
/// Return the latest block height known to the service.
|
||||
func latestBlockHeightAsync() async throws -> BlockHeight
|
||||
|
||||
/// Return the given range of blocks.
|
||||
/// - Parameter range: the inclusive range to fetch.
|
||||
/// For instance if 1..5 is given, then every block in that will be fetched, including 1 and 5.
|
||||
func blockRange(_ range: CompactBlockRange) -> AsyncThrowingStream<ZcashCompactBlock, Error>
|
||||
|
||||
/**
|
||||
Return the given range of blocks.
|
||||
|
||||
- Parameter range: the inclusive range to fetch.
|
||||
For instance if 1..5 is given, then every block in that will be fetched, including 1 and 5.
|
||||
blocking
|
||||
*/
|
||||
func blockRange(_ range: CompactBlockRange) throws -> [ZcashCompactBlock]
|
||||
/// Submits a raw transaction over lightwalletd. Non-Blocking
|
||||
/// - Parameter spendTransaction: data representing the transaction to be sent
|
||||
func submit(spendTransaction: Data) async throws -> LightWalletServiceResponse
|
||||
|
||||
/// Gets a transaction by id
|
||||
/// - Parameter txId: data representing the transaction ID
|
||||
/// - Throws: LightWalletServiceError
|
||||
/// - Returns: LightWalletServiceResponse
|
||||
func fetchTransaction(txId: Data) async throws -> TransactionEntity
|
||||
|
||||
func fetchUTXOs(for tAddress: String, height: BlockHeight) -> AsyncThrowingStream<UnspentTransactionOutputEntity, Error>
|
||||
|
||||
func fetchUTXOs(for tAddresses: [String], height: BlockHeight) -> AsyncThrowingStream<UnspentTransactionOutputEntity, Error>
|
||||
|
||||
@discardableResult
|
||||
func blockStream(
|
||||
startHeight: BlockHeight,
|
||||
endHeight: BlockHeight,
|
||||
result: @escaping (Result<GRPCResult, LightWalletServiceError>) -> Void,
|
||||
handler: @escaping (ZcashCompactBlock) -> Void,
|
||||
progress: @escaping (BlockProgress) -> Void
|
||||
) -> CancellableCall
|
||||
endHeight: BlockHeight
|
||||
) -> AsyncThrowingStream<ZcashCompactBlock, Error>
|
||||
|
||||
/**
|
||||
Submits a raw transaction over lightwalletd. Non-Blocking
|
||||
- Parameter spendTransaction: data representing the transaction to be sent
|
||||
- Parameter result: escaping closure that takes a result containing either LightWalletServiceResponse or LightWalletServiceError
|
||||
*/
|
||||
func submit(spendTransaction: Data, result: @escaping(Result<LightWalletServiceResponse, LightWalletServiceError>) -> Void)
|
||||
|
||||
/**
|
||||
Submits a raw transaction over lightwalletd. Blocking
|
||||
- Parameter spendTransaction: data representing the transaction to be sent
|
||||
- Throws: LightWalletServiceError
|
||||
- Returns: LightWalletServiceResponse
|
||||
*/
|
||||
func submit(spendTransaction: Data) throws -> LightWalletServiceResponse
|
||||
|
||||
/**
|
||||
Gets a transaction by id
|
||||
- Parameter txId: data representing the transaction ID
|
||||
- Throws: LightWalletServiceError
|
||||
- Returns: LightWalletServiceResponse
|
||||
*/
|
||||
func fetchTransaction(txId: Data) throws -> TransactionEntity
|
||||
|
||||
/**
|
||||
Gets a transaction by id
|
||||
- Parameter txId: data representing the transaction ID
|
||||
- Parameter result: handler for the result
|
||||
- Throws: LightWalletServiceError
|
||||
- Returns: LightWalletServiceResponse
|
||||
*/
|
||||
func fetchTransaction(
|
||||
txId: Data,
|
||||
result: @escaping (Result<TransactionEntity, LightWalletServiceError>) -> Void
|
||||
)
|
||||
|
||||
func fetchUTXOs(
|
||||
for tAddress: String,
|
||||
height: BlockHeight
|
||||
) throws -> [UnspentTransactionOutputEntity]
|
||||
|
||||
func fetchUTXOs(
|
||||
for tAddress: String,
|
||||
height: BlockHeight,
|
||||
result: @escaping(Result<[UnspentTransactionOutputEntity], LightWalletServiceError>) -> Void
|
||||
)
|
||||
|
||||
func fetchUTXOs(
|
||||
for tAddresses: [String],
|
||||
height: BlockHeight
|
||||
) throws -> [UnspentTransactionOutputEntity]
|
||||
|
||||
func fetchUTXOs(
|
||||
for tAddresses: [String],
|
||||
height: BlockHeight,
|
||||
result: @escaping(Result<[UnspentTransactionOutputEntity], LightWalletServiceError>) -> Void
|
||||
)
|
||||
|
||||
func closeConnection()
|
||||
}
|
||||
|
|
|
@ -22,11 +22,15 @@
|
|||
//
|
||||
import GRPC
|
||||
import NIO
|
||||
import NIOConcurrencyHelpers
|
||||
import SwiftProtobuf
|
||||
|
||||
|
||||
/// Usage: instantiate CompactTxStreamerClient, then call methods of this protocol to make API calls.
|
||||
/// Usage: instantiate `CompactTxStreamerClient`, then call methods of this protocol to make API calls.
|
||||
internal protocol CompactTxStreamerClientProtocol: GRPCClient {
|
||||
var serviceName: String { get }
|
||||
var interceptors: CompactTxStreamerClientInterceptorFactoryProtocol? { get }
|
||||
|
||||
func getLatestBlock(
|
||||
_ request: ChainSpec,
|
||||
callOptions: CallOptions?
|
||||
|
@ -99,10 +103,12 @@ internal protocol CompactTxStreamerClientProtocol: GRPCClient {
|
|||
_ request: Duration,
|
||||
callOptions: CallOptions?
|
||||
) -> UnaryCall<Duration, PingResponse>
|
||||
|
||||
}
|
||||
|
||||
extension CompactTxStreamerClientProtocol {
|
||||
internal var serviceName: String {
|
||||
return "cash.z.wallet.sdk.rpc.CompactTxStreamer"
|
||||
}
|
||||
|
||||
/// Return the height of the tip of the best chain
|
||||
///
|
||||
|
@ -115,9 +121,10 @@ extension CompactTxStreamerClientProtocol {
|
|||
callOptions: CallOptions? = nil
|
||||
) -> UnaryCall<ChainSpec, BlockID> {
|
||||
return self.makeUnaryCall(
|
||||
path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetLatestBlock",
|
||||
path: CompactTxStreamerClientMetadata.Methods.getLatestBlock.path,
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makeGetLatestBlockInterceptors() ?? []
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -132,9 +139,10 @@ extension CompactTxStreamerClientProtocol {
|
|||
callOptions: CallOptions? = nil
|
||||
) -> UnaryCall<BlockID, CompactBlock> {
|
||||
return self.makeUnaryCall(
|
||||
path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetBlock",
|
||||
path: CompactTxStreamerClientMetadata.Methods.getBlock.path,
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makeGetBlockInterceptors() ?? []
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -151,9 +159,10 @@ extension CompactTxStreamerClientProtocol {
|
|||
handler: @escaping (CompactBlock) -> Void
|
||||
) -> ServerStreamingCall<BlockRange, CompactBlock> {
|
||||
return self.makeServerStreamingCall(
|
||||
path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetBlockRange",
|
||||
path: CompactTxStreamerClientMetadata.Methods.getBlockRange.path,
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makeGetBlockRangeInterceptors() ?? [],
|
||||
handler: handler
|
||||
)
|
||||
}
|
||||
|
@ -169,9 +178,10 @@ extension CompactTxStreamerClientProtocol {
|
|||
callOptions: CallOptions? = nil
|
||||
) -> UnaryCall<TxFilter, RawTransaction> {
|
||||
return self.makeUnaryCall(
|
||||
path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetTransaction",
|
||||
path: CompactTxStreamerClientMetadata.Methods.getTransaction.path,
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makeGetTransactionInterceptors() ?? []
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -186,9 +196,10 @@ extension CompactTxStreamerClientProtocol {
|
|||
callOptions: CallOptions? = nil
|
||||
) -> UnaryCall<RawTransaction, SendResponse> {
|
||||
return self.makeUnaryCall(
|
||||
path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/SendTransaction",
|
||||
path: CompactTxStreamerClientMetadata.Methods.sendTransaction.path,
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makeSendTransactionInterceptors() ?? []
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -205,9 +216,10 @@ extension CompactTxStreamerClientProtocol {
|
|||
handler: @escaping (RawTransaction) -> Void
|
||||
) -> ServerStreamingCall<TransparentAddressBlockFilter, RawTransaction> {
|
||||
return self.makeServerStreamingCall(
|
||||
path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetTaddressTxids",
|
||||
path: CompactTxStreamerClientMetadata.Methods.getTaddressTxids.path,
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makeGetTaddressTxidsInterceptors() ?? [],
|
||||
handler: handler
|
||||
)
|
||||
}
|
||||
|
@ -223,9 +235,10 @@ extension CompactTxStreamerClientProtocol {
|
|||
callOptions: CallOptions? = nil
|
||||
) -> UnaryCall<AddressList, Balance> {
|
||||
return self.makeUnaryCall(
|
||||
path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetTaddressBalance",
|
||||
path: CompactTxStreamerClientMetadata.Methods.getTaddressBalance.path,
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makeGetTaddressBalanceInterceptors() ?? []
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -241,8 +254,9 @@ extension CompactTxStreamerClientProtocol {
|
|||
callOptions: CallOptions? = nil
|
||||
) -> ClientStreamingCall<Address, Balance> {
|
||||
return self.makeClientStreamingCall(
|
||||
path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetTaddressBalanceStream",
|
||||
callOptions: callOptions ?? self.defaultCallOptions
|
||||
path: CompactTxStreamerClientMetadata.Methods.getTaddressBalanceStream.path,
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makeGetTaddressBalanceStreamInterceptors() ?? []
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -267,9 +281,10 @@ extension CompactTxStreamerClientProtocol {
|
|||
handler: @escaping (CompactTx) -> Void
|
||||
) -> ServerStreamingCall<Exclude, CompactTx> {
|
||||
return self.makeServerStreamingCall(
|
||||
path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetMempoolTx",
|
||||
path: CompactTxStreamerClientMetadata.Methods.getMempoolTx.path,
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makeGetMempoolTxInterceptors() ?? [],
|
||||
handler: handler
|
||||
)
|
||||
}
|
||||
|
@ -288,9 +303,10 @@ extension CompactTxStreamerClientProtocol {
|
|||
callOptions: CallOptions? = nil
|
||||
) -> UnaryCall<BlockID, TreeState> {
|
||||
return self.makeUnaryCall(
|
||||
path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetTreeState",
|
||||
path: CompactTxStreamerClientMetadata.Methods.getTreeState.path,
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makeGetTreeStateInterceptors() ?? []
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -305,9 +321,10 @@ extension CompactTxStreamerClientProtocol {
|
|||
callOptions: CallOptions? = nil
|
||||
) -> UnaryCall<GetAddressUtxosArg, GetAddressUtxosReplyList> {
|
||||
return self.makeUnaryCall(
|
||||
path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetAddressUtxos",
|
||||
path: CompactTxStreamerClientMetadata.Methods.getAddressUtxos.path,
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makeGetAddressUtxosInterceptors() ?? []
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -324,9 +341,10 @@ extension CompactTxStreamerClientProtocol {
|
|||
handler: @escaping (GetAddressUtxosReply) -> Void
|
||||
) -> ServerStreamingCall<GetAddressUtxosArg, GetAddressUtxosReply> {
|
||||
return self.makeServerStreamingCall(
|
||||
path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetAddressUtxosStream",
|
||||
path: CompactTxStreamerClientMetadata.Methods.getAddressUtxosStream.path,
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makeGetAddressUtxosStreamInterceptors() ?? [],
|
||||
handler: handler
|
||||
)
|
||||
}
|
||||
|
@ -342,9 +360,10 @@ extension CompactTxStreamerClientProtocol {
|
|||
callOptions: CallOptions? = nil
|
||||
) -> UnaryCall<Empty, LightdInfo> {
|
||||
return self.makeUnaryCall(
|
||||
path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetLightdInfo",
|
||||
path: CompactTxStreamerClientMetadata.Methods.getLightdInfo.path,
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makeGetLightdInfoInterceptors() ?? []
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -359,25 +378,679 @@ extension CompactTxStreamerClientProtocol {
|
|||
callOptions: CallOptions? = nil
|
||||
) -> UnaryCall<Duration, PingResponse> {
|
||||
return self.makeUnaryCall(
|
||||
path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/Ping",
|
||||
path: CompactTxStreamerClientMetadata.Methods.ping.path,
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makePingInterceptors() ?? []
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#if compiler(>=5.6)
|
||||
@available(*, deprecated)
|
||||
extension CompactTxStreamerClient: @unchecked Sendable {}
|
||||
#endif // compiler(>=5.6)
|
||||
|
||||
@available(*, deprecated, renamed: "CompactTxStreamerNIOClient")
|
||||
internal final class CompactTxStreamerClient: CompactTxStreamerClientProtocol {
|
||||
private let lock = Lock()
|
||||
private var _defaultCallOptions: CallOptions
|
||||
private var _interceptors: CompactTxStreamerClientInterceptorFactoryProtocol?
|
||||
internal let channel: GRPCChannel
|
||||
internal var defaultCallOptions: CallOptions
|
||||
internal var defaultCallOptions: CallOptions {
|
||||
get { self.lock.withLock { return self._defaultCallOptions } }
|
||||
set { self.lock.withLockVoid { self._defaultCallOptions = newValue } }
|
||||
}
|
||||
internal var interceptors: CompactTxStreamerClientInterceptorFactoryProtocol? {
|
||||
get { self.lock.withLock { return self._interceptors } }
|
||||
set { self.lock.withLockVoid { self._interceptors = newValue } }
|
||||
}
|
||||
|
||||
/// Creates a client for the cash.z.wallet.sdk.rpc.CompactTxStreamer service.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - channel: `GRPCChannel` to the service host.
|
||||
/// - defaultCallOptions: Options to use for each service call if the user doesn't provide them.
|
||||
internal init(channel: GRPCChannel, defaultCallOptions: CallOptions = CallOptions()) {
|
||||
/// - interceptors: A factory providing interceptors for each RPC.
|
||||
internal init(
|
||||
channel: GRPCChannel,
|
||||
defaultCallOptions: CallOptions = CallOptions(),
|
||||
interceptors: CompactTxStreamerClientInterceptorFactoryProtocol? = nil
|
||||
) {
|
||||
self.channel = channel
|
||||
self.defaultCallOptions = defaultCallOptions
|
||||
self._defaultCallOptions = defaultCallOptions
|
||||
self._interceptors = interceptors
|
||||
}
|
||||
}
|
||||
|
||||
internal struct CompactTxStreamerNIOClient: CompactTxStreamerClientProtocol {
|
||||
internal var channel: GRPCChannel
|
||||
internal var defaultCallOptions: CallOptions
|
||||
internal var interceptors: CompactTxStreamerClientInterceptorFactoryProtocol?
|
||||
|
||||
/// Creates a client for the cash.z.wallet.sdk.rpc.CompactTxStreamer service.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - channel: `GRPCChannel` to the service host.
|
||||
/// - defaultCallOptions: Options to use for each service call if the user doesn't provide them.
|
||||
/// - interceptors: A factory providing interceptors for each RPC.
|
||||
internal init(
|
||||
channel: GRPCChannel,
|
||||
defaultCallOptions: CallOptions = CallOptions(),
|
||||
interceptors: CompactTxStreamerClientInterceptorFactoryProtocol? = nil
|
||||
) {
|
||||
self.channel = channel
|
||||
self.defaultCallOptions = defaultCallOptions
|
||||
self.interceptors = interceptors
|
||||
}
|
||||
}
|
||||
|
||||
#if compiler(>=5.6)
|
||||
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
|
||||
internal protocol CompactTxStreamerAsyncClientProtocol: GRPCClient {
|
||||
static var serviceDescriptor: GRPCServiceDescriptor { get }
|
||||
var interceptors: CompactTxStreamerClientInterceptorFactoryProtocol? { get }
|
||||
|
||||
func makeGetLatestBlockCall(
|
||||
_ request: ChainSpec,
|
||||
callOptions: CallOptions?
|
||||
) -> GRPCAsyncUnaryCall<ChainSpec, BlockID>
|
||||
|
||||
func makeGetBlockCall(
|
||||
_ request: BlockID,
|
||||
callOptions: CallOptions?
|
||||
) -> GRPCAsyncUnaryCall<BlockID, CompactBlock>
|
||||
|
||||
func makeGetBlockRangeCall(
|
||||
_ request: BlockRange,
|
||||
callOptions: CallOptions?
|
||||
) -> GRPCAsyncServerStreamingCall<BlockRange, CompactBlock>
|
||||
|
||||
func makeGetTransactionCall(
|
||||
_ request: TxFilter,
|
||||
callOptions: CallOptions?
|
||||
) -> GRPCAsyncUnaryCall<TxFilter, RawTransaction>
|
||||
|
||||
func makeSendTransactionCall(
|
||||
_ request: RawTransaction,
|
||||
callOptions: CallOptions?
|
||||
) -> GRPCAsyncUnaryCall<RawTransaction, SendResponse>
|
||||
|
||||
func makeGetTaddressTxidsCall(
|
||||
_ request: TransparentAddressBlockFilter,
|
||||
callOptions: CallOptions?
|
||||
) -> GRPCAsyncServerStreamingCall<TransparentAddressBlockFilter, RawTransaction>
|
||||
|
||||
func makeGetTaddressBalanceCall(
|
||||
_ request: AddressList,
|
||||
callOptions: CallOptions?
|
||||
) -> GRPCAsyncUnaryCall<AddressList, Balance>
|
||||
|
||||
func makeGetTaddressBalanceStreamCall(
|
||||
callOptions: CallOptions?
|
||||
) -> GRPCAsyncClientStreamingCall<Address, Balance>
|
||||
|
||||
func makeGetMempoolTxCall(
|
||||
_ request: Exclude,
|
||||
callOptions: CallOptions?
|
||||
) -> GRPCAsyncServerStreamingCall<Exclude, CompactTx>
|
||||
|
||||
func makeGetTreeStateCall(
|
||||
_ request: BlockID,
|
||||
callOptions: CallOptions?
|
||||
) -> GRPCAsyncUnaryCall<BlockID, TreeState>
|
||||
|
||||
func makeGetAddressUtxosCall(
|
||||
_ request: GetAddressUtxosArg,
|
||||
callOptions: CallOptions?
|
||||
) -> GRPCAsyncUnaryCall<GetAddressUtxosArg, GetAddressUtxosReplyList>
|
||||
|
||||
func makeGetAddressUtxosStreamCall(
|
||||
_ request: GetAddressUtxosArg,
|
||||
callOptions: CallOptions?
|
||||
) -> GRPCAsyncServerStreamingCall<GetAddressUtxosArg, GetAddressUtxosReply>
|
||||
|
||||
func makeGetLightdInfoCall(
|
||||
_ request: Empty,
|
||||
callOptions: CallOptions?
|
||||
) -> GRPCAsyncUnaryCall<Empty, LightdInfo>
|
||||
|
||||
func makePingCall(
|
||||
_ request: Duration,
|
||||
callOptions: CallOptions?
|
||||
) -> GRPCAsyncUnaryCall<Duration, PingResponse>
|
||||
}
|
||||
|
||||
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
|
||||
extension CompactTxStreamerAsyncClientProtocol {
|
||||
internal static var serviceDescriptor: GRPCServiceDescriptor {
|
||||
return CompactTxStreamerClientMetadata.serviceDescriptor
|
||||
}
|
||||
|
||||
internal var interceptors: CompactTxStreamerClientInterceptorFactoryProtocol? {
|
||||
return nil
|
||||
}
|
||||
|
||||
internal func makeGetLatestBlockCall(
|
||||
_ request: ChainSpec,
|
||||
callOptions: CallOptions? = nil
|
||||
) -> GRPCAsyncUnaryCall<ChainSpec, BlockID> {
|
||||
return self.makeAsyncUnaryCall(
|
||||
path: CompactTxStreamerClientMetadata.Methods.getLatestBlock.path,
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makeGetLatestBlockInterceptors() ?? []
|
||||
)
|
||||
}
|
||||
|
||||
internal func makeGetBlockCall(
|
||||
_ request: BlockID,
|
||||
callOptions: CallOptions? = nil
|
||||
) -> GRPCAsyncUnaryCall<BlockID, CompactBlock> {
|
||||
return self.makeAsyncUnaryCall(
|
||||
path: CompactTxStreamerClientMetadata.Methods.getBlock.path,
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makeGetBlockInterceptors() ?? []
|
||||
)
|
||||
}
|
||||
|
||||
internal func makeGetBlockRangeCall(
|
||||
_ request: BlockRange,
|
||||
callOptions: CallOptions? = nil
|
||||
) -> GRPCAsyncServerStreamingCall<BlockRange, CompactBlock> {
|
||||
return self.makeAsyncServerStreamingCall(
|
||||
path: CompactTxStreamerClientMetadata.Methods.getBlockRange.path,
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makeGetBlockRangeInterceptors() ?? []
|
||||
)
|
||||
}
|
||||
|
||||
internal func makeGetTransactionCall(
|
||||
_ request: TxFilter,
|
||||
callOptions: CallOptions? = nil
|
||||
) -> GRPCAsyncUnaryCall<TxFilter, RawTransaction> {
|
||||
return self.makeAsyncUnaryCall(
|
||||
path: CompactTxStreamerClientMetadata.Methods.getTransaction.path,
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makeGetTransactionInterceptors() ?? []
|
||||
)
|
||||
}
|
||||
|
||||
internal func makeSendTransactionCall(
|
||||
_ request: RawTransaction,
|
||||
callOptions: CallOptions? = nil
|
||||
) -> GRPCAsyncUnaryCall<RawTransaction, SendResponse> {
|
||||
return self.makeAsyncUnaryCall(
|
||||
path: CompactTxStreamerClientMetadata.Methods.sendTransaction.path,
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makeSendTransactionInterceptors() ?? []
|
||||
)
|
||||
}
|
||||
|
||||
internal func makeGetTaddressTxidsCall(
|
||||
_ request: TransparentAddressBlockFilter,
|
||||
callOptions: CallOptions? = nil
|
||||
) -> GRPCAsyncServerStreamingCall<TransparentAddressBlockFilter, RawTransaction> {
|
||||
return self.makeAsyncServerStreamingCall(
|
||||
path: CompactTxStreamerClientMetadata.Methods.getTaddressTxids.path,
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makeGetTaddressTxidsInterceptors() ?? []
|
||||
)
|
||||
}
|
||||
|
||||
internal func makeGetTaddressBalanceCall(
|
||||
_ request: AddressList,
|
||||
callOptions: CallOptions? = nil
|
||||
) -> GRPCAsyncUnaryCall<AddressList, Balance> {
|
||||
return self.makeAsyncUnaryCall(
|
||||
path: CompactTxStreamerClientMetadata.Methods.getTaddressBalance.path,
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makeGetTaddressBalanceInterceptors() ?? []
|
||||
)
|
||||
}
|
||||
|
||||
internal func makeGetTaddressBalanceStreamCall(
|
||||
callOptions: CallOptions? = nil
|
||||
) -> GRPCAsyncClientStreamingCall<Address, Balance> {
|
||||
return self.makeAsyncClientStreamingCall(
|
||||
path: CompactTxStreamerClientMetadata.Methods.getTaddressBalanceStream.path,
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makeGetTaddressBalanceStreamInterceptors() ?? []
|
||||
)
|
||||
}
|
||||
|
||||
internal func makeGetMempoolTxCall(
|
||||
_ request: Exclude,
|
||||
callOptions: CallOptions? = nil
|
||||
) -> GRPCAsyncServerStreamingCall<Exclude, CompactTx> {
|
||||
return self.makeAsyncServerStreamingCall(
|
||||
path: CompactTxStreamerClientMetadata.Methods.getMempoolTx.path,
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makeGetMempoolTxInterceptors() ?? []
|
||||
)
|
||||
}
|
||||
|
||||
internal func makeGetTreeStateCall(
|
||||
_ request: BlockID,
|
||||
callOptions: CallOptions? = nil
|
||||
) -> GRPCAsyncUnaryCall<BlockID, TreeState> {
|
||||
return self.makeAsyncUnaryCall(
|
||||
path: CompactTxStreamerClientMetadata.Methods.getTreeState.path,
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makeGetTreeStateInterceptors() ?? []
|
||||
)
|
||||
}
|
||||
|
||||
internal func makeGetAddressUtxosCall(
|
||||
_ request: GetAddressUtxosArg,
|
||||
callOptions: CallOptions? = nil
|
||||
) -> GRPCAsyncUnaryCall<GetAddressUtxosArg, GetAddressUtxosReplyList> {
|
||||
return self.makeAsyncUnaryCall(
|
||||
path: CompactTxStreamerClientMetadata.Methods.getAddressUtxos.path,
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makeGetAddressUtxosInterceptors() ?? []
|
||||
)
|
||||
}
|
||||
|
||||
internal func makeGetAddressUtxosStreamCall(
|
||||
_ request: GetAddressUtxosArg,
|
||||
callOptions: CallOptions? = nil
|
||||
) -> GRPCAsyncServerStreamingCall<GetAddressUtxosArg, GetAddressUtxosReply> {
|
||||
return self.makeAsyncServerStreamingCall(
|
||||
path: CompactTxStreamerClientMetadata.Methods.getAddressUtxosStream.path,
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makeGetAddressUtxosStreamInterceptors() ?? []
|
||||
)
|
||||
}
|
||||
|
||||
internal func makeGetLightdInfoCall(
|
||||
_ request: Empty,
|
||||
callOptions: CallOptions? = nil
|
||||
) -> GRPCAsyncUnaryCall<Empty, LightdInfo> {
|
||||
return self.makeAsyncUnaryCall(
|
||||
path: CompactTxStreamerClientMetadata.Methods.getLightdInfo.path,
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makeGetLightdInfoInterceptors() ?? []
|
||||
)
|
||||
}
|
||||
|
||||
internal func makePingCall(
|
||||
_ request: Duration,
|
||||
callOptions: CallOptions? = nil
|
||||
) -> GRPCAsyncUnaryCall<Duration, PingResponse> {
|
||||
return self.makeAsyncUnaryCall(
|
||||
path: CompactTxStreamerClientMetadata.Methods.ping.path,
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makePingInterceptors() ?? []
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
|
||||
extension CompactTxStreamerAsyncClientProtocol {
|
||||
internal func getLatestBlock(
|
||||
_ request: ChainSpec,
|
||||
callOptions: CallOptions? = nil
|
||||
) async throws -> BlockID {
|
||||
return try await self.performAsyncUnaryCall(
|
||||
path: CompactTxStreamerClientMetadata.Methods.getLatestBlock.path,
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makeGetLatestBlockInterceptors() ?? []
|
||||
)
|
||||
}
|
||||
|
||||
internal func getBlock(
|
||||
_ request: BlockID,
|
||||
callOptions: CallOptions? = nil
|
||||
) async throws -> CompactBlock {
|
||||
return try await self.performAsyncUnaryCall(
|
||||
path: CompactTxStreamerClientMetadata.Methods.getBlock.path,
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makeGetBlockInterceptors() ?? []
|
||||
)
|
||||
}
|
||||
|
||||
internal func getBlockRange(
|
||||
_ request: BlockRange,
|
||||
callOptions: CallOptions? = nil
|
||||
) -> GRPCAsyncResponseStream<CompactBlock> {
|
||||
return self.performAsyncServerStreamingCall(
|
||||
path: CompactTxStreamerClientMetadata.Methods.getBlockRange.path,
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makeGetBlockRangeInterceptors() ?? []
|
||||
)
|
||||
}
|
||||
|
||||
internal func getTransaction(
|
||||
_ request: TxFilter,
|
||||
callOptions: CallOptions? = nil
|
||||
) async throws -> RawTransaction {
|
||||
return try await self.performAsyncUnaryCall(
|
||||
path: CompactTxStreamerClientMetadata.Methods.getTransaction.path,
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makeGetTransactionInterceptors() ?? []
|
||||
)
|
||||
}
|
||||
|
||||
internal func sendTransaction(
|
||||
_ request: RawTransaction,
|
||||
callOptions: CallOptions? = nil
|
||||
) async throws -> SendResponse {
|
||||
return try await self.performAsyncUnaryCall(
|
||||
path: CompactTxStreamerClientMetadata.Methods.sendTransaction.path,
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makeSendTransactionInterceptors() ?? []
|
||||
)
|
||||
}
|
||||
|
||||
internal func getTaddressTxids(
|
||||
_ request: TransparentAddressBlockFilter,
|
||||
callOptions: CallOptions? = nil
|
||||
) -> GRPCAsyncResponseStream<RawTransaction> {
|
||||
return self.performAsyncServerStreamingCall(
|
||||
path: CompactTxStreamerClientMetadata.Methods.getTaddressTxids.path,
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makeGetTaddressTxidsInterceptors() ?? []
|
||||
)
|
||||
}
|
||||
|
||||
internal func getTaddressBalance(
|
||||
_ request: AddressList,
|
||||
callOptions: CallOptions? = nil
|
||||
) async throws -> Balance {
|
||||
return try await self.performAsyncUnaryCall(
|
||||
path: CompactTxStreamerClientMetadata.Methods.getTaddressBalance.path,
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makeGetTaddressBalanceInterceptors() ?? []
|
||||
)
|
||||
}
|
||||
|
||||
internal func getTaddressBalanceStream<RequestStream>(
|
||||
_ requests: RequestStream,
|
||||
callOptions: CallOptions? = nil
|
||||
) async throws -> Balance where RequestStream: Sequence, RequestStream.Element == Address {
|
||||
return try await self.performAsyncClientStreamingCall(
|
||||
path: CompactTxStreamerClientMetadata.Methods.getTaddressBalanceStream.path,
|
||||
requests: requests,
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makeGetTaddressBalanceStreamInterceptors() ?? []
|
||||
)
|
||||
}
|
||||
|
||||
internal func getTaddressBalanceStream<RequestStream>(
|
||||
_ requests: RequestStream,
|
||||
callOptions: CallOptions? = nil
|
||||
) async throws -> Balance where RequestStream: AsyncSequence & Sendable, RequestStream.Element == Address {
|
||||
return try await self.performAsyncClientStreamingCall(
|
||||
path: CompactTxStreamerClientMetadata.Methods.getTaddressBalanceStream.path,
|
||||
requests: requests,
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makeGetTaddressBalanceStreamInterceptors() ?? []
|
||||
)
|
||||
}
|
||||
|
||||
internal func getMempoolTx(
|
||||
_ request: Exclude,
|
||||
callOptions: CallOptions? = nil
|
||||
) -> GRPCAsyncResponseStream<CompactTx> {
|
||||
return self.performAsyncServerStreamingCall(
|
||||
path: CompactTxStreamerClientMetadata.Methods.getMempoolTx.path,
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makeGetMempoolTxInterceptors() ?? []
|
||||
)
|
||||
}
|
||||
|
||||
internal func getTreeState(
|
||||
_ request: BlockID,
|
||||
callOptions: CallOptions? = nil
|
||||
) async throws -> TreeState {
|
||||
return try await self.performAsyncUnaryCall(
|
||||
path: CompactTxStreamerClientMetadata.Methods.getTreeState.path,
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makeGetTreeStateInterceptors() ?? []
|
||||
)
|
||||
}
|
||||
|
||||
internal func getAddressUtxos(
|
||||
_ request: GetAddressUtxosArg,
|
||||
callOptions: CallOptions? = nil
|
||||
) async throws -> GetAddressUtxosReplyList {
|
||||
return try await self.performAsyncUnaryCall(
|
||||
path: CompactTxStreamerClientMetadata.Methods.getAddressUtxos.path,
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makeGetAddressUtxosInterceptors() ?? []
|
||||
)
|
||||
}
|
||||
|
||||
internal func getAddressUtxosStream(
|
||||
_ request: GetAddressUtxosArg,
|
||||
callOptions: CallOptions? = nil
|
||||
) -> GRPCAsyncResponseStream<GetAddressUtxosReply> {
|
||||
return self.performAsyncServerStreamingCall(
|
||||
path: CompactTxStreamerClientMetadata.Methods.getAddressUtxosStream.path,
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makeGetAddressUtxosStreamInterceptors() ?? []
|
||||
)
|
||||
}
|
||||
|
||||
internal func getLightdInfo(
|
||||
_ request: Empty,
|
||||
callOptions: CallOptions? = nil
|
||||
) async throws -> LightdInfo {
|
||||
return try await self.performAsyncUnaryCall(
|
||||
path: CompactTxStreamerClientMetadata.Methods.getLightdInfo.path,
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makeGetLightdInfoInterceptors() ?? []
|
||||
)
|
||||
}
|
||||
|
||||
internal func ping(
|
||||
_ request: Duration,
|
||||
callOptions: CallOptions? = nil
|
||||
) async throws -> PingResponse {
|
||||
return try await self.performAsyncUnaryCall(
|
||||
path: CompactTxStreamerClientMetadata.Methods.ping.path,
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
interceptors: self.interceptors?.makePingInterceptors() ?? []
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
|
||||
internal struct CompactTxStreamerAsyncClient: CompactTxStreamerAsyncClientProtocol {
|
||||
internal var channel: GRPCChannel
|
||||
internal var defaultCallOptions: CallOptions
|
||||
internal var interceptors: CompactTxStreamerClientInterceptorFactoryProtocol?
|
||||
|
||||
internal init(
|
||||
channel: GRPCChannel,
|
||||
defaultCallOptions: CallOptions = CallOptions(),
|
||||
interceptors: CompactTxStreamerClientInterceptorFactoryProtocol? = nil
|
||||
) {
|
||||
self.channel = channel
|
||||
self.defaultCallOptions = defaultCallOptions
|
||||
self.interceptors = interceptors
|
||||
}
|
||||
}
|
||||
|
||||
#endif // compiler(>=5.6)
|
||||
|
||||
internal protocol CompactTxStreamerClientInterceptorFactoryProtocol: GRPCSendable {
|
||||
|
||||
/// - Returns: Interceptors to use when invoking 'getLatestBlock'.
|
||||
func makeGetLatestBlockInterceptors() -> [ClientInterceptor<ChainSpec, BlockID>]
|
||||
|
||||
/// - Returns: Interceptors to use when invoking 'getBlock'.
|
||||
func makeGetBlockInterceptors() -> [ClientInterceptor<BlockID, CompactBlock>]
|
||||
|
||||
/// - Returns: Interceptors to use when invoking 'getBlockRange'.
|
||||
func makeGetBlockRangeInterceptors() -> [ClientInterceptor<BlockRange, CompactBlock>]
|
||||
|
||||
/// - Returns: Interceptors to use when invoking 'getTransaction'.
|
||||
func makeGetTransactionInterceptors() -> [ClientInterceptor<TxFilter, RawTransaction>]
|
||||
|
||||
/// - Returns: Interceptors to use when invoking 'sendTransaction'.
|
||||
func makeSendTransactionInterceptors() -> [ClientInterceptor<RawTransaction, SendResponse>]
|
||||
|
||||
/// - Returns: Interceptors to use when invoking 'getTaddressTxids'.
|
||||
func makeGetTaddressTxidsInterceptors() -> [ClientInterceptor<TransparentAddressBlockFilter, RawTransaction>]
|
||||
|
||||
/// - Returns: Interceptors to use when invoking 'getTaddressBalance'.
|
||||
func makeGetTaddressBalanceInterceptors() -> [ClientInterceptor<AddressList, Balance>]
|
||||
|
||||
/// - Returns: Interceptors to use when invoking 'getTaddressBalanceStream'.
|
||||
func makeGetTaddressBalanceStreamInterceptors() -> [ClientInterceptor<Address, Balance>]
|
||||
|
||||
/// - Returns: Interceptors to use when invoking 'getMempoolTx'.
|
||||
func makeGetMempoolTxInterceptors() -> [ClientInterceptor<Exclude, CompactTx>]
|
||||
|
||||
/// - Returns: Interceptors to use when invoking 'getTreeState'.
|
||||
func makeGetTreeStateInterceptors() -> [ClientInterceptor<BlockID, TreeState>]
|
||||
|
||||
/// - Returns: Interceptors to use when invoking 'getAddressUtxos'.
|
||||
func makeGetAddressUtxosInterceptors() -> [ClientInterceptor<GetAddressUtxosArg, GetAddressUtxosReplyList>]
|
||||
|
||||
/// - Returns: Interceptors to use when invoking 'getAddressUtxosStream'.
|
||||
func makeGetAddressUtxosStreamInterceptors() -> [ClientInterceptor<GetAddressUtxosArg, GetAddressUtxosReply>]
|
||||
|
||||
/// - Returns: Interceptors to use when invoking 'getLightdInfo'.
|
||||
func makeGetLightdInfoInterceptors() -> [ClientInterceptor<Empty, LightdInfo>]
|
||||
|
||||
/// - Returns: Interceptors to use when invoking 'ping'.
|
||||
func makePingInterceptors() -> [ClientInterceptor<Duration, PingResponse>]
|
||||
}
|
||||
|
||||
internal enum CompactTxStreamerClientMetadata {
|
||||
internal static let serviceDescriptor = GRPCServiceDescriptor(
|
||||
name: "CompactTxStreamer",
|
||||
fullName: "cash.z.wallet.sdk.rpc.CompactTxStreamer",
|
||||
methods: [
|
||||
CompactTxStreamerClientMetadata.Methods.getLatestBlock,
|
||||
CompactTxStreamerClientMetadata.Methods.getBlock,
|
||||
CompactTxStreamerClientMetadata.Methods.getBlockRange,
|
||||
CompactTxStreamerClientMetadata.Methods.getTransaction,
|
||||
CompactTxStreamerClientMetadata.Methods.sendTransaction,
|
||||
CompactTxStreamerClientMetadata.Methods.getTaddressTxids,
|
||||
CompactTxStreamerClientMetadata.Methods.getTaddressBalance,
|
||||
CompactTxStreamerClientMetadata.Methods.getTaddressBalanceStream,
|
||||
CompactTxStreamerClientMetadata.Methods.getMempoolTx,
|
||||
CompactTxStreamerClientMetadata.Methods.getTreeState,
|
||||
CompactTxStreamerClientMetadata.Methods.getAddressUtxos,
|
||||
CompactTxStreamerClientMetadata.Methods.getAddressUtxosStream,
|
||||
CompactTxStreamerClientMetadata.Methods.getLightdInfo,
|
||||
CompactTxStreamerClientMetadata.Methods.ping,
|
||||
]
|
||||
)
|
||||
|
||||
internal enum Methods {
|
||||
internal static let getLatestBlock = GRPCMethodDescriptor(
|
||||
name: "GetLatestBlock",
|
||||
path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetLatestBlock",
|
||||
type: GRPCCallType.unary
|
||||
)
|
||||
|
||||
internal static let getBlock = GRPCMethodDescriptor(
|
||||
name: "GetBlock",
|
||||
path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetBlock",
|
||||
type: GRPCCallType.unary
|
||||
)
|
||||
|
||||
internal static let getBlockRange = GRPCMethodDescriptor(
|
||||
name: "GetBlockRange",
|
||||
path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetBlockRange",
|
||||
type: GRPCCallType.serverStreaming
|
||||
)
|
||||
|
||||
internal static let getTransaction = GRPCMethodDescriptor(
|
||||
name: "GetTransaction",
|
||||
path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetTransaction",
|
||||
type: GRPCCallType.unary
|
||||
)
|
||||
|
||||
internal static let sendTransaction = GRPCMethodDescriptor(
|
||||
name: "SendTransaction",
|
||||
path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/SendTransaction",
|
||||
type: GRPCCallType.unary
|
||||
)
|
||||
|
||||
internal static let getTaddressTxids = GRPCMethodDescriptor(
|
||||
name: "GetTaddressTxids",
|
||||
path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetTaddressTxids",
|
||||
type: GRPCCallType.serverStreaming
|
||||
)
|
||||
|
||||
internal static let getTaddressBalance = GRPCMethodDescriptor(
|
||||
name: "GetTaddressBalance",
|
||||
path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetTaddressBalance",
|
||||
type: GRPCCallType.unary
|
||||
)
|
||||
|
||||
internal static let getTaddressBalanceStream = GRPCMethodDescriptor(
|
||||
name: "GetTaddressBalanceStream",
|
||||
path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetTaddressBalanceStream",
|
||||
type: GRPCCallType.clientStreaming
|
||||
)
|
||||
|
||||
internal static let getMempoolTx = GRPCMethodDescriptor(
|
||||
name: "GetMempoolTx",
|
||||
path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetMempoolTx",
|
||||
type: GRPCCallType.serverStreaming
|
||||
)
|
||||
|
||||
internal static let getTreeState = GRPCMethodDescriptor(
|
||||
name: "GetTreeState",
|
||||
path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetTreeState",
|
||||
type: GRPCCallType.unary
|
||||
)
|
||||
|
||||
internal static let getAddressUtxos = GRPCMethodDescriptor(
|
||||
name: "GetAddressUtxos",
|
||||
path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetAddressUtxos",
|
||||
type: GRPCCallType.unary
|
||||
)
|
||||
|
||||
internal static let getAddressUtxosStream = GRPCMethodDescriptor(
|
||||
name: "GetAddressUtxosStream",
|
||||
path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetAddressUtxosStream",
|
||||
type: GRPCCallType.serverStreaming
|
||||
)
|
||||
|
||||
internal static let getLightdInfo = GRPCMethodDescriptor(
|
||||
name: "GetLightdInfo",
|
||||
path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetLightdInfo",
|
||||
type: GRPCCallType.unary
|
||||
)
|
||||
|
||||
internal static let ping = GRPCMethodDescriptor(
|
||||
name: "Ping",
|
||||
path: "/cash.z.wallet.sdk.rpc.CompactTxStreamer/Ping",
|
||||
type: GRPCCallType.unary
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ struct BlockID {
|
|||
|
||||
var height: UInt64 = 0
|
||||
|
||||
var hash: Data = SwiftProtobuf.Internal.emptyData
|
||||
var hash: Data = Data()
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
|
@ -95,7 +95,7 @@ struct TxFilter {
|
|||
var index: UInt64 = 0
|
||||
|
||||
/// transaction ID (hash, txid)
|
||||
var hash: Data = SwiftProtobuf.Internal.emptyData
|
||||
var hash: Data = Data()
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
|
@ -112,7 +112,7 @@ struct RawTransaction {
|
|||
// methods supported on all messages.
|
||||
|
||||
/// exact data returned by Zcash 'getrawtransaction'
|
||||
var data: Data = SwiftProtobuf.Internal.emptyData
|
||||
var data: Data = Data()
|
||||
|
||||
/// height that the transaction was mined (or -1)
|
||||
var height: UInt64 = 0
|
||||
|
@ -367,11 +367,11 @@ struct GetAddressUtxosReply {
|
|||
|
||||
var address: String = String()
|
||||
|
||||
var txid: Data = SwiftProtobuf.Internal.emptyData
|
||||
var txid: Data = Data()
|
||||
|
||||
var index: Int32 = 0
|
||||
|
||||
var script: Data = SwiftProtobuf.Internal.emptyData
|
||||
var script: Data = Data()
|
||||
|
||||
var valueZat: Int64 = 0
|
||||
|
||||
|
@ -394,6 +394,28 @@ struct GetAddressUtxosReplyList {
|
|||
init() {}
|
||||
}
|
||||
|
||||
#if swift(>=5.5) && canImport(_Concurrency)
|
||||
extension BlockID: @unchecked Sendable {}
|
||||
extension BlockRange: @unchecked Sendable {}
|
||||
extension TxFilter: @unchecked Sendable {}
|
||||
extension RawTransaction: @unchecked Sendable {}
|
||||
extension SendResponse: @unchecked Sendable {}
|
||||
extension ChainSpec: @unchecked Sendable {}
|
||||
extension Empty: @unchecked Sendable {}
|
||||
extension LightdInfo: @unchecked Sendable {}
|
||||
extension TransparentAddressBlockFilter: @unchecked Sendable {}
|
||||
extension Duration: @unchecked Sendable {}
|
||||
extension PingResponse: @unchecked Sendable {}
|
||||
extension Address: @unchecked Sendable {}
|
||||
extension AddressList: @unchecked Sendable {}
|
||||
extension Balance: @unchecked Sendable {}
|
||||
extension Exclude: @unchecked Sendable {}
|
||||
extension TreeState: @unchecked Sendable {}
|
||||
extension GetAddressUtxosArg: @unchecked Sendable {}
|
||||
extension GetAddressUtxosReply: @unchecked Sendable {}
|
||||
extension GetAddressUtxosReplyList: @unchecked Sendable {}
|
||||
#endif // swift(>=5.5) && canImport(_Concurrency)
|
||||
|
||||
// MARK: - Code below here is support for the SwiftProtobuf runtime.
|
||||
|
||||
fileprivate let _protobuf_package = "cash.z.wallet.sdk.rpc"
|
||||
|
@ -407,9 +429,12 @@ extension BlockID: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBa
|
|||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every case branch when no optimizations are
|
||||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch fieldNumber {
|
||||
case 1: try decoder.decodeSingularUInt64Field(value: &self.height)
|
||||
case 2: try decoder.decodeSingularBytesField(value: &self.hash)
|
||||
case 1: try { try decoder.decodeSingularUInt64Field(value: &self.height) }()
|
||||
case 2: try { try decoder.decodeSingularBytesField(value: &self.hash) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
@ -442,21 +467,28 @@ extension BlockRange: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio
|
|||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every case branch when no optimizations are
|
||||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch fieldNumber {
|
||||
case 1: try decoder.decodeSingularMessageField(value: &self._start)
|
||||
case 2: try decoder.decodeSingularMessageField(value: &self._end)
|
||||
case 1: try { try decoder.decodeSingularMessageField(value: &self._start) }()
|
||||
case 2: try { try decoder.decodeSingularMessageField(value: &self._end) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
||||
if let v = self._start {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every if/case branch local when no optimizations
|
||||
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
|
||||
// https://github.com/apple/swift-protobuf/issues/1182
|
||||
try { if let v = self._start {
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 1)
|
||||
}
|
||||
if let v = self._end {
|
||||
} }()
|
||||
try { if let v = self._end {
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 2)
|
||||
}
|
||||
} }()
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
|
@ -478,19 +510,26 @@ extension TxFilter: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB
|
|||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every case branch when no optimizations are
|
||||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch fieldNumber {
|
||||
case 1: try decoder.decodeSingularMessageField(value: &self._block)
|
||||
case 2: try decoder.decodeSingularUInt64Field(value: &self.index)
|
||||
case 3: try decoder.decodeSingularBytesField(value: &self.hash)
|
||||
case 1: try { try decoder.decodeSingularMessageField(value: &self._block) }()
|
||||
case 2: try { try decoder.decodeSingularUInt64Field(value: &self.index) }()
|
||||
case 3: try { try decoder.decodeSingularBytesField(value: &self.hash) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
||||
if let v = self._block {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every if/case branch local when no optimizations
|
||||
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
|
||||
// https://github.com/apple/swift-protobuf/issues/1182
|
||||
try { if let v = self._block {
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 1)
|
||||
}
|
||||
} }()
|
||||
if self.index != 0 {
|
||||
try visitor.visitSingularUInt64Field(value: self.index, fieldNumber: 2)
|
||||
}
|
||||
|
@ -518,9 +557,12 @@ extension RawTransaction: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement
|
|||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every case branch when no optimizations are
|
||||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch fieldNumber {
|
||||
case 1: try decoder.decodeSingularBytesField(value: &self.data)
|
||||
case 2: try decoder.decodeSingularUInt64Field(value: &self.height)
|
||||
case 1: try { try decoder.decodeSingularBytesField(value: &self.data) }()
|
||||
case 2: try { try decoder.decodeSingularUInt64Field(value: &self.height) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
@ -553,9 +595,12 @@ extension SendResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every case branch when no optimizations are
|
||||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch fieldNumber {
|
||||
case 1: try decoder.decodeSingularInt32Field(value: &self.errorCode)
|
||||
case 2: try decoder.decodeSingularStringField(value: &self.errorMessage)
|
||||
case 1: try { try decoder.decodeSingularInt32Field(value: &self.errorCode) }()
|
||||
case 2: try { try decoder.decodeSingularStringField(value: &self.errorMessage) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
@ -638,21 +683,24 @@ extension LightdInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio
|
|||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every case branch when no optimizations are
|
||||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch fieldNumber {
|
||||
case 1: try decoder.decodeSingularStringField(value: &self.version)
|
||||
case 2: try decoder.decodeSingularStringField(value: &self.vendor)
|
||||
case 3: try decoder.decodeSingularBoolField(value: &self.taddrSupport)
|
||||
case 4: try decoder.decodeSingularStringField(value: &self.chainName)
|
||||
case 5: try decoder.decodeSingularUInt64Field(value: &self.saplingActivationHeight)
|
||||
case 6: try decoder.decodeSingularStringField(value: &self.consensusBranchID)
|
||||
case 7: try decoder.decodeSingularUInt64Field(value: &self.blockHeight)
|
||||
case 8: try decoder.decodeSingularStringField(value: &self.gitCommit)
|
||||
case 9: try decoder.decodeSingularStringField(value: &self.branch)
|
||||
case 10: try decoder.decodeSingularStringField(value: &self.buildDate)
|
||||
case 11: try decoder.decodeSingularStringField(value: &self.buildUser)
|
||||
case 12: try decoder.decodeSingularUInt64Field(value: &self.estimatedHeight)
|
||||
case 13: try decoder.decodeSingularStringField(value: &self.zcashdBuild)
|
||||
case 14: try decoder.decodeSingularStringField(value: &self.zcashdSubversion)
|
||||
case 1: try { try decoder.decodeSingularStringField(value: &self.version) }()
|
||||
case 2: try { try decoder.decodeSingularStringField(value: &self.vendor) }()
|
||||
case 3: try { try decoder.decodeSingularBoolField(value: &self.taddrSupport) }()
|
||||
case 4: try { try decoder.decodeSingularStringField(value: &self.chainName) }()
|
||||
case 5: try { try decoder.decodeSingularUInt64Field(value: &self.saplingActivationHeight) }()
|
||||
case 6: try { try decoder.decodeSingularStringField(value: &self.consensusBranchID) }()
|
||||
case 7: try { try decoder.decodeSingularUInt64Field(value: &self.blockHeight) }()
|
||||
case 8: try { try decoder.decodeSingularStringField(value: &self.gitCommit) }()
|
||||
case 9: try { try decoder.decodeSingularStringField(value: &self.branch) }()
|
||||
case 10: try { try decoder.decodeSingularStringField(value: &self.buildDate) }()
|
||||
case 11: try { try decoder.decodeSingularStringField(value: &self.buildUser) }()
|
||||
case 12: try { try decoder.decodeSingularUInt64Field(value: &self.estimatedHeight) }()
|
||||
case 13: try { try decoder.decodeSingularStringField(value: &self.zcashdBuild) }()
|
||||
case 14: try { try decoder.decodeSingularStringField(value: &self.zcashdSubversion) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
@ -733,21 +781,28 @@ extension TransparentAddressBlockFilter: SwiftProtobuf.Message, SwiftProtobuf._M
|
|||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every case branch when no optimizations are
|
||||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch fieldNumber {
|
||||
case 1: try decoder.decodeSingularStringField(value: &self.address)
|
||||
case 2: try decoder.decodeSingularMessageField(value: &self._range)
|
||||
case 1: try { try decoder.decodeSingularStringField(value: &self.address) }()
|
||||
case 2: try { try decoder.decodeSingularMessageField(value: &self._range) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every if/case branch local when no optimizations
|
||||
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
|
||||
// https://github.com/apple/swift-protobuf/issues/1182
|
||||
if !self.address.isEmpty {
|
||||
try visitor.visitSingularStringField(value: self.address, fieldNumber: 1)
|
||||
}
|
||||
if let v = self._range {
|
||||
try { if let v = self._range {
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 2)
|
||||
}
|
||||
} }()
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
|
@ -767,8 +822,11 @@ extension Duration: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB
|
|||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every case branch when no optimizations are
|
||||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch fieldNumber {
|
||||
case 1: try decoder.decodeSingularInt64Field(value: &self.intervalUs)
|
||||
case 1: try { try decoder.decodeSingularInt64Field(value: &self.intervalUs) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
@ -797,9 +855,12 @@ extension PingResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat
|
|||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every case branch when no optimizations are
|
||||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch fieldNumber {
|
||||
case 1: try decoder.decodeSingularInt64Field(value: &self.entry)
|
||||
case 2: try decoder.decodeSingularInt64Field(value: &self.exit)
|
||||
case 1: try { try decoder.decodeSingularInt64Field(value: &self.entry) }()
|
||||
case 2: try { try decoder.decodeSingularInt64Field(value: &self.exit) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
@ -831,8 +892,11 @@ extension Address: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBa
|
|||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every case branch when no optimizations are
|
||||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch fieldNumber {
|
||||
case 1: try decoder.decodeSingularStringField(value: &self.address)
|
||||
case 1: try { try decoder.decodeSingularStringField(value: &self.address) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
@ -860,8 +924,11 @@ extension AddressList: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati
|
|||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every case branch when no optimizations are
|
||||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch fieldNumber {
|
||||
case 1: try decoder.decodeRepeatedStringField(value: &self.addresses)
|
||||
case 1: try { try decoder.decodeRepeatedStringField(value: &self.addresses) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
@ -889,8 +956,11 @@ extension Balance: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBa
|
|||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every case branch when no optimizations are
|
||||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch fieldNumber {
|
||||
case 1: try decoder.decodeSingularInt64Field(value: &self.valueZat)
|
||||
case 1: try { try decoder.decodeSingularInt64Field(value: &self.valueZat) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
@ -918,8 +988,11 @@ extension Exclude: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBa
|
|||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every case branch when no optimizations are
|
||||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch fieldNumber {
|
||||
case 1: try decoder.decodeRepeatedBytesField(value: &self.txid)
|
||||
case 1: try { try decoder.decodeRepeatedBytesField(value: &self.txid) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
@ -951,12 +1024,15 @@ extension TreeState: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation
|
|||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every case branch when no optimizations are
|
||||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch fieldNumber {
|
||||
case 1: try decoder.decodeSingularStringField(value: &self.network)
|
||||
case 2: try decoder.decodeSingularUInt64Field(value: &self.height)
|
||||
case 3: try decoder.decodeSingularStringField(value: &self.hash)
|
||||
case 4: try decoder.decodeSingularUInt32Field(value: &self.time)
|
||||
case 5: try decoder.decodeSingularStringField(value: &self.tree)
|
||||
case 1: try { try decoder.decodeSingularStringField(value: &self.network) }()
|
||||
case 2: try { try decoder.decodeSingularUInt64Field(value: &self.height) }()
|
||||
case 3: try { try decoder.decodeSingularStringField(value: &self.hash) }()
|
||||
case 4: try { try decoder.decodeSingularUInt32Field(value: &self.time) }()
|
||||
case 5: try { try decoder.decodeSingularStringField(value: &self.tree) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
@ -1002,10 +1078,13 @@ extension GetAddressUtxosArg: SwiftProtobuf.Message, SwiftProtobuf._MessageImple
|
|||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every case branch when no optimizations are
|
||||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch fieldNumber {
|
||||
case 1: try decoder.decodeRepeatedStringField(value: &self.addresses)
|
||||
case 2: try decoder.decodeSingularUInt64Field(value: &self.startHeight)
|
||||
case 3: try decoder.decodeSingularUInt32Field(value: &self.maxEntries)
|
||||
case 1: try { try decoder.decodeRepeatedStringField(value: &self.addresses) }()
|
||||
case 2: try { try decoder.decodeSingularUInt64Field(value: &self.startHeight) }()
|
||||
case 3: try { try decoder.decodeSingularUInt32Field(value: &self.maxEntries) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
@ -1046,13 +1125,16 @@ extension GetAddressUtxosReply: SwiftProtobuf.Message, SwiftProtobuf._MessageImp
|
|||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every case branch when no optimizations are
|
||||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch fieldNumber {
|
||||
case 1: try decoder.decodeSingularBytesField(value: &self.txid)
|
||||
case 2: try decoder.decodeSingularInt32Field(value: &self.index)
|
||||
case 3: try decoder.decodeSingularBytesField(value: &self.script)
|
||||
case 4: try decoder.decodeSingularInt64Field(value: &self.valueZat)
|
||||
case 5: try decoder.decodeSingularUInt64Field(value: &self.height)
|
||||
case 6: try decoder.decodeSingularStringField(value: &self.address)
|
||||
case 1: try { try decoder.decodeSingularBytesField(value: &self.txid) }()
|
||||
case 2: try { try decoder.decodeSingularInt32Field(value: &self.index) }()
|
||||
case 3: try { try decoder.decodeSingularBytesField(value: &self.script) }()
|
||||
case 4: try { try decoder.decodeSingularInt64Field(value: &self.valueZat) }()
|
||||
case 5: try { try decoder.decodeSingularUInt64Field(value: &self.height) }()
|
||||
case 6: try { try decoder.decodeSingularStringField(value: &self.address) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
@ -1100,8 +1182,11 @@ extension GetAddressUtxosReplyList: SwiftProtobuf.Message, SwiftProtobuf._Messag
|
|||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every case branch when no optimizations are
|
||||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch fieldNumber {
|
||||
case 1: try decoder.decodeRepeatedMessageField(value: &self.addressUtxos)
|
||||
case 1: try { try decoder.decodeRepeatedMessageField(value: &self.addressUtxos) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
|
|
@ -80,7 +80,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() throws
|
||||
func prepare() async throws
|
||||
|
||||
///Starts this synchronizer within the given scope.
|
||||
///
|
||||
|
@ -95,54 +95,34 @@ 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 getShieldedAddress(accountIndex: Int) -> SaplingShieldedAddress?
|
||||
func getShieldedAddress(accountIndex: Int) async -> SaplingShieldedAddress?
|
||||
|
||||
|
||||
/// 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?
|
||||
|
||||
/// Sends zatoshi.
|
||||
/// - Parameter spendingKey: the key that allows spends to occur.
|
||||
/// - Parameter zatoshi: the amount of zatoshi to send.
|
||||
/// - Parameter toAddress: the recipient's address.
|
||||
/// - Parameter memo: the optional memo to include as part of the transaction.
|
||||
/// - Parameter accountIndex: the optional account id to use. By default, the first account is used.
|
||||
@available(*, deprecated, message: "This function will be removed soon, use the one reveiving a `Zatoshi` value instead")
|
||||
// swiftlint:disable:next function_parameter_count
|
||||
func sendToAddress(
|
||||
spendingKey: String,
|
||||
zatoshi: Int64,
|
||||
toAddress: String,
|
||||
memo: String?,
|
||||
from accountIndex: Int,
|
||||
resultBlock: @escaping (_ result: Result<PendingTransactionEntity, Error>) -> Void
|
||||
)
|
||||
|
||||
|
||||
func getTransparentAddress(accountIndex: Int) async -> TransparentAddress?
|
||||
|
||||
/// Sends zatoshi.
|
||||
/// - Parameter spendingKey: the key that allows spends to occur.
|
||||
/// - Parameter zatoshi: the amount to send in Zatoshi.
|
||||
/// - Parameter toAddress: the recipient's address.
|
||||
/// - Parameter memo: the optional memo to include as part of the transaction.
|
||||
/// - Parameter accountIndex: the optional account id to use. By default, the first account is used.
|
||||
// swiftlint:disable:next function_parameter_count
|
||||
func sendToAddress(
|
||||
spendingKey: String,
|
||||
zatoshi: Zatoshi,
|
||||
toAddress: String,
|
||||
memo: String?,
|
||||
from accountIndex: Int,
|
||||
resultBlock: @escaping (_ result: Result<PendingTransactionEntity, Error>) -> Void
|
||||
)
|
||||
from accountIndex: Int
|
||||
) async throws -> PendingTransactionEntity
|
||||
|
||||
/// Sends zatoshi.
|
||||
/// Shields zatoshi.
|
||||
/// - Parameter spendingKey: the key that allows spends to occur.
|
||||
/// - Parameter transparentSecretKey: the key that allows to spend transaprent funds
|
||||
/// - Parameter memo: the optional memo to include as part of the transaction.
|
||||
|
@ -151,9 +131,8 @@ public protocol Synchronizer {
|
|||
spendingKey: String,
|
||||
transparentSecretKey: String,
|
||||
memo: String?,
|
||||
from accountIndex: Int,
|
||||
resultBlock: @escaping (_ result: Result<PendingTransactionEntity, Error>) -> Void
|
||||
)
|
||||
from accountIndex: Int
|
||||
) async throws -> PendingTransactionEntity
|
||||
|
||||
/// Attempts to cancel a transaction that is about to be sent. Typically, cancellation is only
|
||||
/// an option if the transaction has not yet been submitted to the server.
|
||||
|
@ -188,7 +167,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
|
||||
|
@ -197,14 +176,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: String, from height: BlockHeight, result: @escaping (Result<RefreshedUTXOs, Error>) -> Void)
|
||||
func refreshUTXOs(address: String, from height: BlockHeight) async throws -> RefreshedUTXOs
|
||||
|
||||
/// Returns the last stored unshielded balance
|
||||
func getTransparentBalance(accountIndex: Int) throws -> WalletBalance
|
||||
func getTransparentBalance(accountIndex: Int) async throws -> WalletBalance
|
||||
|
||||
|
||||
/// Returns the shielded total balance (includes verified and unverified balance)
|
||||
|
@ -228,7 +207,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 {
|
||||
|
|
|
@ -151,17 +151,19 @@ public class SDKSynchronizer: Synchronizer {
|
|||
|
||||
deinit {
|
||||
NotificationCenter.default.removeObserver(self)
|
||||
self.blockProcessor.stop()
|
||||
Task { [blockProcessor] in
|
||||
await blockProcessor.stop()
|
||||
}
|
||||
}
|
||||
|
||||
public func initialize() throws {
|
||||
public func initialize() async throws {
|
||||
try self.initializer.initialize()
|
||||
try self.blockProcessor.setStartHeight(initializer.walletBirthday)
|
||||
try await self.blockProcessor.setStartHeight(initializer.walletBirthday)
|
||||
}
|
||||
|
||||
public func prepare() throws {
|
||||
public func prepare() async throws {
|
||||
try self.initializer.initialize()
|
||||
try self.blockProcessor.setStartHeight(initializer.walletBirthday)
|
||||
try await self.blockProcessor.setStartHeight(initializer.walletBirthday)
|
||||
self.status = .disconnected
|
||||
}
|
||||
|
||||
|
@ -177,10 +179,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -192,8 +192,10 @@ public class SDKSynchronizer: Synchronizer {
|
|||
return
|
||||
}
|
||||
|
||||
blockProcessor.stop(cancelTasks: true)
|
||||
self.status = .stopped
|
||||
Task(priority: .high) {
|
||||
await blockProcessor.stop()
|
||||
self.status = .stopped
|
||||
}
|
||||
}
|
||||
|
||||
private func subscribeToProcessorNotifications(_ processor: CompactBlockProcessor) {
|
||||
|
@ -314,7 +316,7 @@ public class SDKSynchronizer: Synchronizer {
|
|||
}
|
||||
|
||||
let currentState = ConnectionState(current)
|
||||
NotificationCenter.default.post(
|
||||
NotificationCenter.default.mainThreadPost(
|
||||
name: .synchronizerConnectionStateChanged,
|
||||
object: self,
|
||||
userInfo: [
|
||||
|
@ -336,7 +338,7 @@ public class SDKSynchronizer: Synchronizer {
|
|||
return
|
||||
}
|
||||
|
||||
NotificationCenter.default.post(
|
||||
NotificationCenter.default.mainThreadPost(
|
||||
name: .synchronizerFoundTransactions,
|
||||
object: self,
|
||||
userInfo: [
|
||||
|
@ -455,117 +457,70 @@ public class SDKSynchronizer: Synchronizer {
|
|||
}
|
||||
|
||||
// MARK: Synchronizer methods
|
||||
|
||||
@available(*, deprecated, message: "This function will be removed soon, use the one reveiving a `Zatoshi` value instead")
|
||||
public func sendToAddress(
|
||||
spendingKey: String,
|
||||
zatoshi: Int64,
|
||||
toAddress: String,
|
||||
memo: String?,
|
||||
from accountIndex: Int,
|
||||
resultBlock: @escaping (Result<PendingTransactionEntity, Error>) -> Void
|
||||
) {
|
||||
sendToAddress(
|
||||
spendingKey: spendingKey,
|
||||
zatoshi: Zatoshi(zatoshi),
|
||||
toAddress: toAddress,
|
||||
memo: memo,
|
||||
from: accountIndex,
|
||||
resultBlock: resultBlock
|
||||
)
|
||||
}
|
||||
|
||||
// swiftlint:disable:next function_parameter_count
|
||||
|
||||
public func sendToAddress(
|
||||
spendingKey: String,
|
||||
zatoshi: Zatoshi,
|
||||
toAddress: String,
|
||||
memo: String?,
|
||||
from accountIndex: Int,
|
||||
resultBlock: @escaping (Result<PendingTransactionEntity, Error>) -> Void
|
||||
) {
|
||||
initializer.downloadParametersIfNeeded { downloadResult in
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
switch downloadResult {
|
||||
case .success:
|
||||
self?.createToAddress(
|
||||
spendingKey: spendingKey,
|
||||
zatoshi: zatoshi,
|
||||
toAddress: toAddress,
|
||||
memo: memo,
|
||||
from: accountIndex,
|
||||
resultBlock: resultBlock
|
||||
)
|
||||
case .failure(let error):
|
||||
resultBlock(.failure(SynchronizerError.parameterMissing(underlyingError: error)))
|
||||
}
|
||||
}
|
||||
from accountIndex: Int
|
||||
) async throws -> PendingTransactionEntity {
|
||||
do {
|
||||
try await initializer.downloadParametersIfNeeded()
|
||||
} catch {
|
||||
throw SynchronizerError.parameterMissing(underlyingError: error)
|
||||
}
|
||||
|
||||
return try await createToAddress(
|
||||
spendingKey: spendingKey,
|
||||
zatoshi: zatoshi,
|
||||
toAddress: toAddress,
|
||||
memo: memo,
|
||||
from: accountIndex
|
||||
)
|
||||
}
|
||||
|
||||
public func shieldFunds(
|
||||
spendingKey: String,
|
||||
transparentSecretKey: String,
|
||||
memo: String?,
|
||||
from accountIndex: Int,
|
||||
resultBlock: @escaping (Result<PendingTransactionEntity, Error>) -> Void
|
||||
) {
|
||||
from accountIndex: Int
|
||||
) async throws -> PendingTransactionEntity {
|
||||
// let's see if there are funds to shield
|
||||
let derivationTool = DerivationTool(networkType: self.network.networkType)
|
||||
|
||||
do {
|
||||
let tAddr = try derivationTool.deriveTransparentAddressFromPrivateKey(transparentSecretKey)
|
||||
let tBalance = try utxoRepository.balance(address: tAddr, latestHeight: self.latestDownloadedHeight())
|
||||
let tBalance = try await utxoRepository.balance(address: tAddr, latestHeight: self.latestDownloadedHeight())
|
||||
|
||||
// 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 {
|
||||
resultBlock(.failure(ShieldFundsError.insuficientTransparentFunds))
|
||||
return
|
||||
throw ShieldFundsError.insuficientTransparentFunds
|
||||
}
|
||||
let viewingKey = try derivationTool.deriveViewingKey(spendingKey: spendingKey)
|
||||
let zAddr = try derivationTool.deriveShieldedAddress(viewingKey: viewingKey)
|
||||
|
||||
let shieldingSpend = try transactionManager.initSpend(zatoshi: tBalance.verified, toAddress: zAddr, memo: memo, from: 0)
|
||||
|
||||
transactionManager.encodeShieldingTransaction(
|
||||
let transaction = try await transactionManager.encodeShieldingTransaction(
|
||||
spendingKey: spendingKey,
|
||||
tsk: transparentSecretKey,
|
||||
pendingTransaction: shieldingSpend
|
||||
) { [weak self] result in
|
||||
guard let self = self else { return }
|
||||
|
||||
switch result {
|
||||
case .success(let transaction):
|
||||
self.transactionManager.submit(pendingTransaction: transaction) { submitResult in
|
||||
switch submitResult {
|
||||
case .success(let submittedTx):
|
||||
resultBlock(.success(submittedTx))
|
||||
case .failure(let submissionError):
|
||||
DispatchQueue.main.async {
|
||||
resultBlock(.failure(submissionError))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case .failure(let error):
|
||||
resultBlock(.failure(error))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return try await transactionManager.submit(pendingTransaction: transaction)
|
||||
} catch {
|
||||
resultBlock(.failure(error))
|
||||
return
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// swiftlint:disable:next function_parameter_count
|
||||
|
||||
func createToAddress(
|
||||
spendingKey: String,
|
||||
zatoshi: Zatoshi,
|
||||
toAddress: String,
|
||||
memo: String?,
|
||||
from accountIndex: Int,
|
||||
resultBlock: @escaping (Result<PendingTransactionEntity, Error>) -> Void
|
||||
) {
|
||||
from accountIndex: Int
|
||||
) async throws -> PendingTransactionEntity {
|
||||
do {
|
||||
let spend = try transactionManager.initSpend(
|
||||
zatoshi: zatoshi,
|
||||
|
@ -574,27 +529,14 @@ public class SDKSynchronizer: Synchronizer {
|
|||
from: accountIndex
|
||||
)
|
||||
|
||||
transactionManager.encode(spendingKey: spendingKey, pendingTransaction: spend) { [weak self] result in
|
||||
guard let self = self else { return }
|
||||
switch result {
|
||||
case .success(let transaction):
|
||||
self.transactionManager.submit(pendingTransaction: transaction) { submitResult in
|
||||
switch submitResult {
|
||||
case .success(let submittedTx):
|
||||
resultBlock(.success(submittedTx))
|
||||
case .failure(let submissionError):
|
||||
DispatchQueue.main.async {
|
||||
resultBlock(.failure(submissionError))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case .failure(let error):
|
||||
resultBlock(.failure(error))
|
||||
}
|
||||
}
|
||||
let transaction = try await transactionManager.encode(
|
||||
spendingKey: spendingKey,
|
||||
pendingTransaction: spend
|
||||
)
|
||||
let submittedTx = try await transactionManager.submit(pendingTransaction: transaction)
|
||||
return submittedTx
|
||||
} catch {
|
||||
resultBlock(.failure(error))
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -626,43 +568,47 @@ 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) {
|
||||
blockProcessor.downloader.latestBlockHeight(result: result)
|
||||
}
|
||||
|
||||
public func latestHeight() throws -> BlockHeight {
|
||||
try blockProcessor.downloader.latestBlockHeight()
|
||||
}
|
||||
|
||||
public func latestUTXOs(address: String, result: @escaping (Result<[UnspentTransactionOutputEntity], Error>) -> Void) {
|
||||
guard initializer.isValidTransparentAddress(address) else {
|
||||
result(.failure(SynchronizerError.generalError(message: "invalid t-address")))
|
||||
return
|
||||
}
|
||||
|
||||
initializer.lightWalletService.fetchUTXOs(for: address, height: network.constants.saplingActivationHeight) { [weak self] fetchResult in
|
||||
guard let self = self else { return }
|
||||
switch fetchResult {
|
||||
case .success(let utxos):
|
||||
do {
|
||||
try self.utxoRepository.clearAll(address: address)
|
||||
try self.utxoRepository.store(utxos: utxos)
|
||||
result(.success(utxos))
|
||||
} catch {
|
||||
result(.failure(SynchronizerError.generalError(message: "\(error)")))
|
||||
}
|
||||
case .failure(let error):
|
||||
result(.failure(SynchronizerError.connectionFailed(message: error)))
|
||||
Task {
|
||||
do {
|
||||
let latestBlockHeight = try await blockProcessor.downloader.latestBlockHeightAsync()
|
||||
result(.success(latestBlockHeight))
|
||||
} catch {
|
||||
result(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func latestHeight() async throws -> BlockHeight {
|
||||
try await blockProcessor.downloader.latestBlockHeight()
|
||||
}
|
||||
|
||||
public func latestUTXOs(address: String) async throws -> [UnspentTransactionOutputEntity] {
|
||||
guard initializer.isValidTransparentAddress(address) else {
|
||||
throw SynchronizerError.generalError(message: "invalid t-address")
|
||||
}
|
||||
|
||||
let stream = initializer.lightWalletService.fetchUTXOs(for: address, height: network.constants.saplingActivationHeight)
|
||||
|
||||
do {
|
||||
var utxos: [UnspentTransactionOutputEntity] = []
|
||||
for try await transactionEntity in stream {
|
||||
utxos.append(transactionEntity)
|
||||
}
|
||||
try self.utxoRepository.clearAll(address: address)
|
||||
try self.utxoRepository.store(utxos: utxos)
|
||||
return utxos
|
||||
} catch {
|
||||
throw SynchronizerError.generalError(message: "\(error)")
|
||||
}
|
||||
}
|
||||
|
||||
public func refreshUTXOs(address: String, from height: BlockHeight, result: @escaping (Result<RefreshedUTXOs, Error>) -> Void) {
|
||||
self.blockProcessor.refreshUTXOs(tAddress: address, startHeight: height, result: result)
|
||||
public func refreshUTXOs(address: String, from height: BlockHeight) async throws -> RefreshedUTXOs {
|
||||
try await blockProcessor.refreshUTXOs(tAddress: address, startHeight: height)
|
||||
}
|
||||
@available(*, deprecated, message: "This function will be removed soon, use the one returning a `Zatoshi` value instead")
|
||||
public func getShieldedBalance(accountIndex: Int = 0) -> Int64 {
|
||||
|
@ -682,34 +628,34 @@ public class SDKSynchronizer: Synchronizer {
|
|||
initializer.getVerifiedBalance(account: accountIndex)
|
||||
}
|
||||
|
||||
public func getShieldedAddress(accountIndex: Int) -> SaplingShieldedAddress? {
|
||||
blockProcessor.getShieldedAddress(accountIndex: accountIndex)
|
||||
public func getShieldedAddress(accountIndex: Int) async -> SaplingShieldedAddress? {
|
||||
await blockProcessor.getShieldedAddress(accountIndex: accountIndex)
|
||||
}
|
||||
|
||||
public func getUnifiedAddress(accountIndex: Int) -> UnifiedAddress? {
|
||||
blockProcessor.getUnifiedAddres(accountIndex: accountIndex)
|
||||
public func getUnifiedAddress(accountIndex: Int) async -> UnifiedAddress? {
|
||||
await blockProcessor.getUnifiedAddres(accountIndex: accountIndex)
|
||||
}
|
||||
|
||||
public func getTransparentAddress(accountIndex: Int) -> TransparentAddress? {
|
||||
blockProcessor.getTransparentAddress(accountIndex: accountIndex)
|
||||
public func getTransparentAddress(accountIndex: Int) async -> TransparentAddress? {
|
||||
await blockProcessor.getTransparentAddress(accountIndex: accountIndex)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the last stored unshielded balance
|
||||
*/
|
||||
public func getTransparentBalance(address: String) throws -> WalletBalance {
|
||||
public func getTransparentBalance(address: String) async throws -> WalletBalance {
|
||||
do {
|
||||
return try self.blockProcessor.utxoCacheBalance(tAddress: address)
|
||||
return try await self.blockProcessor.utxoCacheBalance(tAddress: address)
|
||||
} catch {
|
||||
throw SynchronizerError.uncategorized(underlyingError: error)
|
||||
}
|
||||
}
|
||||
|
||||
public func rewind(_ policy: RewindPolicy) throws {
|
||||
public func rewind(_ policy: RewindPolicy) async throws {
|
||||
self.stop()
|
||||
|
||||
var height: BlockHeight?
|
||||
|
@ -719,7 +665,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):
|
||||
|
@ -733,7 +679,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)
|
||||
|
@ -747,11 +693,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:
|
||||
|
@ -765,38 +711,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)
|
||||
}
|
||||
|
@ -838,7 +786,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]
|
||||
|
@ -878,6 +826,7 @@ public class SDKSynchronizer: Synchronizer {
|
|||
return SynchronizerError.lightwalletdValidationFailed(underlyingError: compactBlockProcessorError)
|
||||
case .saplingActivationMismatch:
|
||||
return SynchronizerError.lightwalletdValidationFailed(underlyingError: compactBlockProcessorError)
|
||||
case .unknown: break
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -887,7 +836,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)]
|
||||
|
|
|
@ -67,167 +67,129 @@ class PersistentTransactionManager: OutboundTransactionManager {
|
|||
func encodeShieldingTransaction(
|
||||
spendingKey: String,
|
||||
tsk: String,
|
||||
pendingTransaction: PendingTransactionEntity,
|
||||
result: @escaping (Result<PendingTransactionEntity, Error>) -> Void
|
||||
) {
|
||||
queue.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
let derivationTool = DerivationTool(networkType: self.network)
|
||||
|
||||
guard
|
||||
let viewingKey = try? derivationTool.deriveViewingKey(spendingKey: spendingKey),
|
||||
let zAddr = try? derivationTool.deriveShieldedAddress(viewingKey: viewingKey)
|
||||
else {
|
||||
result(
|
||||
.failure(
|
||||
TransactionManagerError.shieldingEncodingFailed(
|
||||
pendingTransaction,
|
||||
reason: "There was an error Deriving your keys"
|
||||
)
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
pendingTransaction: PendingTransactionEntity
|
||||
) async throws -> PendingTransactionEntity {
|
||||
let derivationTool = DerivationTool(networkType: self.network)
|
||||
|
||||
guard
|
||||
let viewingKey = try? derivationTool.deriveViewingKey(spendingKey: spendingKey),
|
||||
let zAddr = try? derivationTool.deriveShieldedAddress(viewingKey: viewingKey)
|
||||
else {
|
||||
throw TransactionManagerError.shieldingEncodingFailed(
|
||||
pendingTransaction,
|
||||
reason: "There was an error Deriving your keys"
|
||||
)
|
||||
}
|
||||
|
||||
guard pendingTransaction.toAddress == zAddr else {
|
||||
throw TransactionManagerError.shieldingEncodingFailed(
|
||||
pendingTransaction,
|
||||
reason: """
|
||||
the recipient address does not match your
|
||||
derived shielded address. Shielding transactions
|
||||
addresses must match the ones derived from your keys.
|
||||
This is a serious error. We are not letting you encode
|
||||
this shielding transaction because it can lead to loss
|
||||
of funds
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
do {
|
||||
let encodedTransaction = try await self.encoder.createShieldingTransaction(
|
||||
spendingKey: spendingKey,
|
||||
tSecretKey: tsk,
|
||||
memo: pendingTransaction.memo?.asZcashTransactionMemo(),
|
||||
from: pendingTransaction.accountIndex
|
||||
)
|
||||
let transaction = try self.encoder.expandEncodedTransaction(encodedTransaction)
|
||||
|
||||
guard pendingTransaction.toAddress == zAddr else {
|
||||
result(
|
||||
.failure(
|
||||
TransactionManagerError.shieldingEncodingFailed(
|
||||
pendingTransaction,
|
||||
reason: """
|
||||
the recipient address does not match your
|
||||
derived shielded address. Shielding transactions
|
||||
addresses must match the ones derived from your keys.
|
||||
This is a serious error. We are not letting you encode
|
||||
this shielding transaction because it can lead to loss
|
||||
of funds
|
||||
"""
|
||||
)
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
do {
|
||||
let encodedTransaction = try self.encoder.createShieldingTransaction(
|
||||
spendingKey: spendingKey,
|
||||
tSecretKey: tsk,
|
||||
memo: pendingTransaction.memo?.asZcashTransactionMemo(),
|
||||
from: pendingTransaction.accountIndex
|
||||
)
|
||||
let transaction = try self.encoder.expandEncodedTransaction(encodedTransaction)
|
||||
|
||||
var pending = pendingTransaction
|
||||
pending.encodeAttempts += 1
|
||||
pending.raw = encodedTransaction.raw
|
||||
pending.rawTransactionId = encodedTransaction.transactionId
|
||||
pending.expiryHeight = transaction.expiryHeight ?? BlockHeight.empty()
|
||||
pending.minedHeight = transaction.minedHeight ?? BlockHeight.empty()
|
||||
|
||||
try self.repository.update(pending)
|
||||
|
||||
result(.success(pending))
|
||||
} catch StorageError.updateFailed {
|
||||
DispatchQueue.main.async {
|
||||
result(.failure(TransactionManagerError.updateFailed(pendingTransaction)))
|
||||
}
|
||||
} catch {
|
||||
DispatchQueue.main.async {
|
||||
result(.failure(error))
|
||||
}
|
||||
}
|
||||
var pending = pendingTransaction
|
||||
pending.encodeAttempts += 1
|
||||
pending.raw = encodedTransaction.raw
|
||||
pending.rawTransactionId = encodedTransaction.transactionId
|
||||
pending.expiryHeight = transaction.expiryHeight ?? BlockHeight.empty()
|
||||
pending.minedHeight = transaction.minedHeight ?? BlockHeight.empty()
|
||||
|
||||
try self.repository.update(pending)
|
||||
|
||||
return pending
|
||||
} catch StorageError.updateFailed {
|
||||
throw TransactionManagerError.updateFailed(pendingTransaction)
|
||||
} catch {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
func encode(
|
||||
spendingKey: String,
|
||||
pendingTransaction: PendingTransactionEntity,
|
||||
result: @escaping (Result<PendingTransactionEntity, Error>) -> Void
|
||||
) {
|
||||
queue.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
pendingTransaction: PendingTransactionEntity
|
||||
) async throws -> PendingTransactionEntity {
|
||||
do {
|
||||
let encodedTransaction = try await self.encoder.createTransaction(
|
||||
spendingKey: spendingKey,
|
||||
zatoshi: pendingTransaction.intValue,
|
||||
to: pendingTransaction.toAddress,
|
||||
memo: pendingTransaction.memo?.asZcashTransactionMemo(),
|
||||
from: pendingTransaction.accountIndex
|
||||
)
|
||||
let transaction = try self.encoder.expandEncodedTransaction(encodedTransaction)
|
||||
|
||||
var pending = pendingTransaction
|
||||
pending.encodeAttempts += 1
|
||||
pending.raw = encodedTransaction.raw
|
||||
pending.rawTransactionId = encodedTransaction.transactionId
|
||||
pending.expiryHeight = transaction.expiryHeight ?? BlockHeight.empty()
|
||||
pending.minedHeight = transaction.minedHeight ?? BlockHeight.empty()
|
||||
|
||||
try self.repository.update(pending)
|
||||
|
||||
return pending
|
||||
} catch StorageError.updateFailed {
|
||||
throw TransactionManagerError.updateFailed(pendingTransaction)
|
||||
} catch {
|
||||
do {
|
||||
let encodedTransaction = try self.encoder.createTransaction(
|
||||
spendingKey: spendingKey,
|
||||
zatoshi: pendingTransaction.intValue,
|
||||
to: pendingTransaction.toAddress,
|
||||
memo: pendingTransaction.memo?.asZcashTransactionMemo(),
|
||||
from: pendingTransaction.accountIndex
|
||||
)
|
||||
let transaction = try self.encoder.expandEncodedTransaction(encodedTransaction)
|
||||
|
||||
var pending = pendingTransaction
|
||||
pending.encodeAttempts += 1
|
||||
pending.raw = encodedTransaction.raw
|
||||
pending.rawTransactionId = encodedTransaction.transactionId
|
||||
pending.expiryHeight = transaction.expiryHeight ?? BlockHeight.empty()
|
||||
pending.minedHeight = transaction.minedHeight ?? BlockHeight.empty()
|
||||
|
||||
try self.repository.update(pending)
|
||||
|
||||
result(.success(pending))
|
||||
} catch StorageError.updateFailed {
|
||||
DispatchQueue.main.async {
|
||||
result(.failure(TransactionManagerError.updateFailed(pendingTransaction)))
|
||||
}
|
||||
try self.updateOnFailure(transaction: pendingTransaction, error: error)
|
||||
} catch {
|
||||
do {
|
||||
try self.updateOnFailure(transaction: pendingTransaction, error: error)
|
||||
} catch {
|
||||
DispatchQueue.main.async {
|
||||
result(.failure(TransactionManagerError.updateFailed(pendingTransaction)))
|
||||
}
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
result(.failure(error))
|
||||
}
|
||||
throw TransactionManagerError.updateFailed(pendingTransaction)
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
func submit(
|
||||
pendingTransaction: PendingTransactionEntity,
|
||||
result: @escaping (Result<PendingTransactionEntity, Error>) -> Void
|
||||
) {
|
||||
pendingTransaction: PendingTransactionEntity
|
||||
) async throws -> PendingTransactionEntity {
|
||||
guard let txId = pendingTransaction.id else {
|
||||
result(.failure(TransactionManagerError.notPending(pendingTransaction)))// this transaction is not stored
|
||||
return
|
||||
throw TransactionManagerError.notPending(pendingTransaction) // this transaction is not stored
|
||||
}
|
||||
|
||||
queue.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
do {
|
||||
guard let storedTx = try self.repository.find(by: txId) else {
|
||||
result(.failure(TransactionManagerError.notPending(pendingTransaction)))
|
||||
return
|
||||
}
|
||||
|
||||
guard !storedTx.isCancelled else {
|
||||
LoggerProxy.debug("ignoring cancelled transaction \(storedTx)")
|
||||
result(.failure(TransactionManagerError.cancelled(storedTx)))
|
||||
return
|
||||
}
|
||||
|
||||
guard let raw = storedTx.raw else {
|
||||
LoggerProxy.debug("INCONSISTENCY: attempt to send pending transaction \(txId) that has not raw data")
|
||||
result(.failure(TransactionManagerError.internalInconsistency(storedTx)))
|
||||
return
|
||||
}
|
||||
|
||||
let response = try self.service.submit(spendTransaction: raw)
|
||||
let transaction = try self.update(transaction: storedTx, on: response)
|
||||
|
||||
guard response.errorCode >= 0 else {
|
||||
result(.failure(TransactionManagerError.submitFailed(transaction, errorCode: Int(response.errorCode))))
|
||||
return
|
||||
}
|
||||
|
||||
result(.success(transaction))
|
||||
} catch {
|
||||
try? self.updateOnFailure(transaction: pendingTransaction, error: error)
|
||||
result(.failure(error))
|
||||
do {
|
||||
guard let storedTx = try self.repository.find(by: txId) else {
|
||||
throw TransactionManagerError.notPending(pendingTransaction)
|
||||
}
|
||||
|
||||
guard !storedTx.isCancelled else {
|
||||
LoggerProxy.debug("ignoring cancelled transaction \(storedTx)")
|
||||
throw TransactionManagerError.cancelled(storedTx)
|
||||
}
|
||||
|
||||
guard let raw = storedTx.raw else {
|
||||
LoggerProxy.debug("INCONSISTENCY: attempt to send pending transaction \(txId) that has not raw data")
|
||||
throw TransactionManagerError.internalInconsistency(storedTx)
|
||||
}
|
||||
|
||||
let response = try await self.service.submit(spendTransaction: raw)
|
||||
let transaction = try self.update(transaction: storedTx, on: response)
|
||||
|
||||
guard response.errorCode >= 0 else {
|
||||
throw TransactionManagerError.submitFailed(transaction, errorCode: Int(response.errorCode))
|
||||
}
|
||||
|
||||
return transaction
|
||||
} catch {
|
||||
try? self.updateOnFailure(transaction: pendingTransaction, error: error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -39,31 +39,7 @@ protocol TransactionEncoder {
|
|||
to address: String,
|
||||
memo: String?,
|
||||
from accountIndex: Int
|
||||
) throws -> EncodedTransaction
|
||||
|
||||
/**
|
||||
Creates a transaction, throwing an exception whenever things are missing. When the provided wallet implementation
|
||||
doesn't throw an exception, we wrap the issue into a descriptive exception ourselves (rather than using
|
||||
double-bangs for things).
|
||||
Non-blocking
|
||||
|
||||
- Parameters:
|
||||
- Parameter spendingKey: a string containing the spending key
|
||||
- Parameter zatoshi: the amount to send in zatoshis
|
||||
- Parameter to: string containing the recipient address
|
||||
- Parameter memo: string containing the memo (optional)
|
||||
- Parameter accountIndex: index of the account that will be used to send the funds
|
||||
- Parameter result: a non escaping closure that receives a Result containing either an EncodedTransaction or a TransactionEncoderError
|
||||
*/
|
||||
// swiftlint:disable:next function_parameter_count
|
||||
func createTransaction(
|
||||
spendingKey: String,
|
||||
zatoshi: Int,
|
||||
to address: String,
|
||||
memo: String?,
|
||||
from accountIndex: Int,
|
||||
result: @escaping TransactionEncoderResultBlock
|
||||
)
|
||||
) async throws -> EncodedTransaction
|
||||
|
||||
/**
|
||||
Creates a transaction that will attempt to shield transparent funds that are present on the cacheDB .throwing an exception whenever things are missing. When the provided wallet implementation doesn't throw an exception, we wrap the issue into a descriptive exception ourselves (rather than using double-bangs for things).
|
||||
|
@ -82,29 +58,7 @@ protocol TransactionEncoder {
|
|||
tSecretKey: String,
|
||||
memo: String?,
|
||||
from accountIndex: Int
|
||||
) throws -> EncodedTransaction
|
||||
|
||||
/**
|
||||
Creates a transaction that will attempt to shield transparent funds that are present on the cacheDB .throwing an exception whenever things are missing. When the provided wallet implementation doesn't throw an exception, we wrap the issue into a descriptive exception ourselves (rather than using double-bangs for things).
|
||||
|
||||
Non-Blocking
|
||||
|
||||
- Parameters:
|
||||
- Parameter spendingKey: a string containing the spending key
|
||||
- Parameter tSecretKey: transparent secret key to spend the UTXOs
|
||||
- Parameter memo: string containing the memo (optional)
|
||||
- Parameter accountIndex: index of the account that will be used to send the funds
|
||||
|
||||
- Returns: a TransactionEncoderResultBlock
|
||||
*/
|
||||
|
||||
func createShieldingTransaction(
|
||||
spendingKey: String,
|
||||
tSecretKey: String,
|
||||
memo: String?,
|
||||
from accountIndex: Int,
|
||||
result: @escaping TransactionEncoderResultBlock
|
||||
)
|
||||
) async throws -> EncodedTransaction
|
||||
|
||||
/**
|
||||
Fetch the Transaction Entity from the encoded representation
|
||||
|
|
|
@ -16,12 +16,12 @@ transactions through to completion.
|
|||
protocol OutboundTransactionManager {
|
||||
func initSpend(zatoshi: Zatoshi, toAddress: String, memo: String?, from accountIndex: Int) throws -> PendingTransactionEntity
|
||||
|
||||
func encodeShieldingTransaction(spendingKey: String, tsk: String, pendingTransaction: PendingTransactionEntity, result: @escaping (Result<PendingTransactionEntity, Error>) -> Void)
|
||||
|
||||
func encode(spendingKey: String, pendingTransaction: PendingTransactionEntity, result: @escaping (Result<PendingTransactionEntity, Error>) -> Void)
|
||||
|
||||
func submit(pendingTransaction: PendingTransactionEntity, result: @escaping (Result<PendingTransactionEntity, Error>) -> Void)
|
||||
|
||||
func encodeShieldingTransaction(spendingKey: String, tsk: String, pendingTransaction: PendingTransactionEntity) async throws -> PendingTransactionEntity
|
||||
|
||||
func encode(spendingKey: String, pendingTransaction: PendingTransactionEntity) async throws -> PendingTransactionEntity
|
||||
|
||||
func submit(pendingTransaction: PendingTransactionEntity) async throws -> PendingTransactionEntity
|
||||
|
||||
func applyMinedHeight(pendingTransaction: PendingTransactionEntity, minedHeight: BlockHeight) throws -> PendingTransactionEntity
|
||||
|
||||
func monitorChanges(byId: Int, observer: Any) // check this out. code smell
|
||||
|
|
|
@ -10,7 +10,6 @@ import Foundation
|
|||
class WalletTransactionEncoder: TransactionEncoder {
|
||||
var rustBackend: ZcashRustBackendWelding.Type
|
||||
var repository: TransactionRepository
|
||||
var queue: DispatchQueue
|
||||
|
||||
private var outputParamsURL: URL
|
||||
private var spendParamsURL: URL
|
||||
|
@ -34,7 +33,6 @@ class WalletTransactionEncoder: TransactionEncoder {
|
|||
self.outputParamsURL = outputParams
|
||||
self.spendParamsURL = spendParams
|
||||
self.networkType = networkType
|
||||
self.queue = DispatchQueue(label: "wallet.transaction.encoder.serial.queue")
|
||||
}
|
||||
|
||||
convenience init(initializer: Initializer) {
|
||||
|
@ -55,7 +53,7 @@ class WalletTransactionEncoder: TransactionEncoder {
|
|||
to address: String,
|
||||
memo: String?,
|
||||
from accountIndex: Int
|
||||
) throws -> EncodedTransaction {
|
||||
) async throws -> EncodedTransaction {
|
||||
let txId = try createSpend(
|
||||
spendingKey: spendingKey,
|
||||
zatoshi: zatoshi,
|
||||
|
@ -77,35 +75,6 @@ class WalletTransactionEncoder: TransactionEncoder {
|
|||
throw TransactionEncoderError.notFound(transactionId: txId)
|
||||
}
|
||||
}
|
||||
|
||||
// swiftlint:disable:next function_parameter_count
|
||||
func createTransaction(
|
||||
spendingKey: String,
|
||||
zatoshi: Int,
|
||||
to address: String,
|
||||
memo: String?,
|
||||
from accountIndex: Int,
|
||||
result: @escaping TransactionEncoderResultBlock
|
||||
) {
|
||||
queue.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
do {
|
||||
result(
|
||||
.success(
|
||||
try self.createTransaction(
|
||||
spendingKey: spendingKey,
|
||||
zatoshi: zatoshi,
|
||||
to: address,
|
||||
memo: memo,
|
||||
from: accountIndex
|
||||
)
|
||||
)
|
||||
)
|
||||
} catch {
|
||||
result(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createSpend(spendingKey: String, zatoshi: Int, to address: String, memo: String?, from accountIndex: Int) throws -> Int {
|
||||
guard ensureParams(spend: self.spendParamsURL, output: self.spendParamsURL) else {
|
||||
|
@ -136,7 +105,7 @@ class WalletTransactionEncoder: TransactionEncoder {
|
|||
tSecretKey: String,
|
||||
memo: String?,
|
||||
from accountIndex: Int
|
||||
) throws -> EncodedTransaction {
|
||||
) async throws -> EncodedTransaction {
|
||||
let txId = try createShieldingSpend(
|
||||
spendingKey: spendingKey,
|
||||
tsk: tSecretKey,
|
||||
|
@ -158,18 +127,6 @@ class WalletTransactionEncoder: TransactionEncoder {
|
|||
}
|
||||
}
|
||||
|
||||
func createShieldingTransaction(
|
||||
spendingKey: String,
|
||||
tSecretKey: String,
|
||||
memo: String?,
|
||||
from accountIndex: Int,
|
||||
result: @escaping TransactionEncoderResultBlock
|
||||
) {
|
||||
queue.async {
|
||||
result(.failure(RustWeldingError.genericError(message: "not implemented")))
|
||||
}
|
||||
}
|
||||
|
||||
func createShieldingSpend(spendingKey: String, tsk: String, memo: String?, accountIndex: Int) throws -> Int {
|
||||
guard ensureParams(spend: self.spendParamsURL, output: self.spendParamsURL) else {
|
||||
throw TransactionEncoderError.missingParams
|
||||
|
|
|
@ -15,33 +15,46 @@ public enum SaplingParameterDownloader {
|
|||
case failed(error: Error)
|
||||
}
|
||||
|
||||
/**
|
||||
Download a Spend parameter from default host and stores it at given URL
|
||||
- Parameters:
|
||||
- at: The destination URL for the download
|
||||
- result: block to handle the download success or error
|
||||
*/
|
||||
public static func downloadSpendParameter(_ at: URL, result: @escaping (Result<URL, Error>) -> Void) {
|
||||
/// Download a Spend parameter from default host and stores it at given URL
|
||||
/// - Parameters:
|
||||
/// - at: The destination URL for the download
|
||||
@discardableResult
|
||||
public static func downloadSpendParameter(_ at: URL) async throws -> URL {
|
||||
guard let url = URL(string: spendParamsURLString) else {
|
||||
result(.failure(Errors.invalidURL(url: spendParamsURLString)))
|
||||
return
|
||||
throw Errors.invalidURL(url: spendParamsURLString)
|
||||
}
|
||||
|
||||
downloadFileWithRequest(URLRequest(url: url), at: at, result: result)
|
||||
return try await withCheckedThrowingContinuation { continuation in
|
||||
downloadFileWithRequest(URLRequest(url: url), at: at) { result in
|
||||
switch result {
|
||||
case .success(let outputResultURL):
|
||||
continuation.resume(returning: outputResultURL)
|
||||
case .failure(let outputResultError):
|
||||
continuation.resume(throwing: outputResultError)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
Download an Output parameter from default host and stores it at given URL
|
||||
- Parameters:
|
||||
- at: The destination URL for the download
|
||||
- result: block to handle the download success or error
|
||||
*/
|
||||
public static func downloadOutputParameter(_ at: URL, result: @escaping (Result<URL, Error>) -> Void) {
|
||||
|
||||
/// Download an Output parameter from default host and stores it at given URL
|
||||
/// - Parameters:
|
||||
/// - at: The destination URL for the download
|
||||
@discardableResult
|
||||
public static func downloadOutputParameter(_ at: URL) async throws -> URL {
|
||||
guard let url = URL(string: outputParamsURLString) else {
|
||||
result(.failure(Errors.invalidURL(url: outputParamsURLString)))
|
||||
return
|
||||
throw Errors.invalidURL(url: outputParamsURLString)
|
||||
}
|
||||
|
||||
downloadFileWithRequest(URLRequest(url: url), at: at, result: result)
|
||||
return try await withCheckedThrowingContinuation { continuation in
|
||||
downloadFileWithRequest(URLRequest(url: url), at: at) { result in
|
||||
switch result {
|
||||
case .success(let outputResultURL):
|
||||
continuation.resume(returning: outputResultURL)
|
||||
case .failure(let outputResultError):
|
||||
continuation.resume(throwing: outputResultError)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static func downloadFileWithRequest(_ request: URLRequest, at destination: URL, result: @escaping (Result<URL, Error>) -> Void) {
|
||||
|
@ -61,52 +74,39 @@ public enum SaplingParameterDownloader {
|
|||
|
||||
task.resume()
|
||||
}
|
||||
/**
|
||||
Downloads the parameters if not present and provides the resulting URLs for both parameters
|
||||
- Parameters:
|
||||
- spendURL: URL to check whether the parameter is already downloaded
|
||||
- outputURL: URL to check whether the parameter is already downloaded
|
||||
- result: block to handle success or error
|
||||
*/
|
||||
|
||||
/// Downloads the parameters if not present and provides the resulting URLs for both parameters
|
||||
/// - Parameters:
|
||||
/// - spendURL: URL to check whether the parameter is already downloaded
|
||||
/// - outputURL: URL to check whether the parameter is already downloaded
|
||||
public static func downloadParamsIfnotPresent(
|
||||
spendURL: URL,
|
||||
outputURL: URL,
|
||||
result: @escaping (Result<(spend: URL, output: URL), Error>) -> Void
|
||||
) {
|
||||
ensureSpendParameter(at: spendURL) { spendResult in
|
||||
switch spendResult {
|
||||
case .success(let spendResultURL):
|
||||
ensureOutputParameter(at: outputURL) { outputResult in
|
||||
switch outputResult {
|
||||
case .success(let outputResultURL):
|
||||
result(.success((spendResultURL, outputResultURL)))
|
||||
case .failure(let outputResultError):
|
||||
result(.failure(Errors.failed(error: outputResultError)))
|
||||
}
|
||||
}
|
||||
case .failure(let spendResultError):
|
||||
result(.failure(Errors.failed(error: spendResultError)))
|
||||
}
|
||||
outputURL: URL
|
||||
) async throws -> (spend: URL, output: URL) {
|
||||
do {
|
||||
async let spendResultURL = ensureSpendParameter(at: spendURL)
|
||||
async let outputResultURL = ensureOutputParameter(at: outputURL)
|
||||
|
||||
let results = try await [spendResultURL, outputResultURL]
|
||||
return (spend: results[0], output: results[1])
|
||||
} catch {
|
||||
throw Errors.failed(error: error)
|
||||
}
|
||||
}
|
||||
|
||||
static func ensureSpendParameter(at url: URL, result: @escaping (Result<URL, Error>) -> Void) {
|
||||
static func ensureSpendParameter(at url: URL) async throws -> URL {
|
||||
if isFilePresent(url: url) {
|
||||
DispatchQueue.global().async {
|
||||
result(.success(url))
|
||||
}
|
||||
return url
|
||||
} else {
|
||||
downloadSpendParameter(url, result: result)
|
||||
return try await downloadSpendParameter(url)
|
||||
}
|
||||
}
|
||||
|
||||
static func ensureOutputParameter(at url: URL, result: @escaping (Result<URL, Error>) -> Void) {
|
||||
static func ensureOutputParameter(at url: URL) async throws -> URL {
|
||||
if isFilePresent(url: url) {
|
||||
DispatchQueue.global().async {
|
||||
result(.success(url))
|
||||
}
|
||||
return url
|
||||
} else {
|
||||
downloadOutputParameter(url, result: result)
|
||||
return try await downloadOutputParameter(url)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import XCTest
|
|||
@testable import ZcashLightClientKit
|
||||
|
||||
// swiftlint:disable implicitly_unwrapped_optional force_unwrapping type_body_length
|
||||
//@MainActor
|
||||
class AdvancedReOrgTests: XCTestCase {
|
||||
// TODO: Parameterize this from environment?
|
||||
// swiftlint:disable:next line_length
|
||||
|
@ -82,7 +83,7 @@ class AdvancedReOrgTests: XCTestCase {
|
|||
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 +101,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 +139,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 +211,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 +249,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)
|
||||
|
@ -268,7 +305,7 @@ class AdvancedReOrgTests: XCTestCase {
|
|||
12. applyStaged(sentTx + 10)
|
||||
13. verify that there's no more pending transaction
|
||||
*/
|
||||
func testReorgChangesOutboundTxIndex() throws {
|
||||
func testReorgChangesOutboundTxIndex() async throws {
|
||||
try FakeChainBuilder.buildChain(darksideWallet: self.coordinator.service, branchID: branchID, chainName: chainName)
|
||||
let receivedTxHeight: BlockHeight = 663188
|
||||
var initialTotalBalance = Zatoshi(-1)
|
||||
|
@ -280,44 +317,51 @@ class AdvancedReOrgTests: XCTestCase {
|
|||
|
||||
sleep(2)
|
||||
let preTxExpectation = XCTestExpectation(description: "pre receive")
|
||||
|
||||
|
||||
/*
|
||||
3. sync up to received_Tx_height
|
||||
*/
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
initialTotalBalance = synchronizer.initializer.getBalance()
|
||||
preTxExpectation.fulfill()
|
||||
}, error: self.handleError)
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
initialTotalBalance = synchronizer.initializer.getBalance()
|
||||
continuation.resume()
|
||||
preTxExpectation.fulfill()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
|
||||
wait(for: [preTxExpectation], timeout: 5)
|
||||
|
||||
let sendExpectation = XCTestExpectation(description: "sendToAddress")
|
||||
var pendingEntity: PendingTransactionEntity?
|
||||
var error: Error?
|
||||
var testError: Error?
|
||||
let sendAmount = Zatoshi(10000)
|
||||
|
||||
/*
|
||||
4. create transaction
|
||||
*/
|
||||
coordinator.synchronizer.sendToAddress(
|
||||
spendingKey: coordinator.spendingKeys!.first!,
|
||||
zatoshi: sendAmount,
|
||||
toAddress: testRecipientAddress,
|
||||
memo: "test transaction",
|
||||
from: 0
|
||||
) { result in
|
||||
switch result {
|
||||
case .success(let pending):
|
||||
pendingEntity = pending
|
||||
case .failure(let e):
|
||||
error = e
|
||||
}
|
||||
do {
|
||||
let pendingTx = try await coordinator.synchronizer.sendToAddress(
|
||||
spendingKey: coordinator.spendingKeys!.first!,
|
||||
zatoshi: sendAmount,
|
||||
toAddress: testRecipientAddress,
|
||||
memo: "test transaction",
|
||||
from: 0
|
||||
)
|
||||
pendingEntity = pendingTx
|
||||
sendExpectation.fulfill()
|
||||
}
|
||||
wait(for: [sendExpectation], timeout: 12)
|
||||
|
||||
guard let pendingTx = pendingEntity else {
|
||||
} catch {
|
||||
testError = error
|
||||
XCTFail("error sending to address. Error: \(String(describing: error))")
|
||||
}
|
||||
|
||||
wait(for: [sendExpectation], timeout: 2)
|
||||
|
||||
guard let pendingTx = pendingEntity else {
|
||||
XCTFail("error sending to address. Error: \(String(describing: testError))")
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -349,15 +393,18 @@ class AdvancedReOrgTests: XCTestCase {
|
|||
*/
|
||||
let sentTxSyncExpectation = XCTestExpectation(description: "sent tx sync expectation")
|
||||
|
||||
try coordinator.sync(
|
||||
completion: { synchronizer in
|
||||
let pMinedHeight = synchronizer.pendingTransactions.first?.minedHeight
|
||||
XCTAssertEqual(pMinedHeight, sentTxHeight)
|
||||
|
||||
sentTxSyncExpectation.fulfill()
|
||||
},
|
||||
error: self.handleError
|
||||
)
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
let pMinedHeight = synchronizer.pendingTransactions.first?.minedHeight
|
||||
XCTAssertEqual(pMinedHeight, sentTxHeight)
|
||||
continuation.resume()
|
||||
sentTxSyncExpectation.fulfill()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
|
||||
wait(for: [sentTxSyncExpectation], timeout: 5)
|
||||
|
||||
|
@ -375,18 +422,22 @@ class AdvancedReOrgTests: XCTestCase {
|
|||
|
||||
sleep(2)
|
||||
let afterReOrgExpectation = XCTestExpectation(description: "after ReOrg Expectation")
|
||||
try coordinator.sync(
|
||||
completion: { synchronizer in
|
||||
/*
|
||||
11. verify that the sent tx is mined and balance is correct
|
||||
*/
|
||||
let pMinedHeight = synchronizer.pendingTransactions.first?.minedHeight
|
||||
XCTAssertEqual(pMinedHeight, sentTxHeight)
|
||||
XCTAssertEqual(initialTotalBalance - sendAmount - Zatoshi(1000), synchronizer.initializer.getBalance()) // fee change on this branch
|
||||
afterReOrgExpectation.fulfill()
|
||||
},
|
||||
error: self.handleError
|
||||
)
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
/*
|
||||
11. verify that the sent tx is mined and balance is correct
|
||||
*/
|
||||
let pMinedHeight = synchronizer.pendingTransactions.first?.minedHeight
|
||||
XCTAssertEqual(pMinedHeight, sentTxHeight)
|
||||
XCTAssertEqual(initialTotalBalance - sendAmount - Zatoshi(1000), synchronizer.initializer.getBalance()) // fee change on this branch
|
||||
continuation.resume()
|
||||
afterReOrgExpectation.fulfill()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
|
||||
wait(for: [afterReOrgExpectation], timeout: 5)
|
||||
|
||||
|
@ -400,10 +451,16 @@ class AdvancedReOrgTests: XCTestCase {
|
|||
13. verify that there's no more pending transaction
|
||||
*/
|
||||
let lastSyncExpectation = XCTestExpectation(description: "sync to confirmation")
|
||||
|
||||
try coordinator.sync(completion: { _ in
|
||||
lastSyncExpectation.fulfill()
|
||||
}, error: self.handleError)
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
lastSyncExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
|
||||
wait(for: [lastSyncExpectation], timeout: 5)
|
||||
|
||||
|
@ -676,7 +733,7 @@ class AdvancedReOrgTests: XCTestCase {
|
|||
15. verify that there's no pending transaction and that the tx is displayed on the sentTransactions collection
|
||||
|
||||
*/
|
||||
func testReOrgChangesOutboundTxMinedHeight() throws {
|
||||
func testReOrgChangesOutboundTxMinedHeight() async throws {
|
||||
hookToReOrgNotification()
|
||||
|
||||
/*
|
||||
|
@ -691,9 +748,16 @@ class AdvancedReOrgTests: XCTestCase {
|
|||
/*
|
||||
1a. sync to latest height
|
||||
*/
|
||||
try coordinator.sync(completion: { _ in
|
||||
firstSyncExpectation.fulfill()
|
||||
}, error: self.handleError)
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
firstSyncExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
|
||||
wait(for: [firstSyncExpectation], timeout: 5)
|
||||
|
||||
|
@ -706,22 +770,18 @@ class AdvancedReOrgTests: XCTestCase {
|
|||
/*
|
||||
2. send transaction to recipient address
|
||||
*/
|
||||
coordinator.synchronizer.sendToAddress(
|
||||
spendingKey: self.coordinator.spendingKeys!.first!,
|
||||
zatoshi: Zatoshi(20000),
|
||||
toAddress: self.testRecipientAddress,
|
||||
memo: "this is a test",
|
||||
from: 0,
|
||||
resultBlock: { result in
|
||||
switch result {
|
||||
case .failure(let e):
|
||||
self.handleError(e)
|
||||
case .success(let pendingTx):
|
||||
pendingEntity = pendingTx
|
||||
}
|
||||
sendExpectation.fulfill()
|
||||
}
|
||||
)
|
||||
do {
|
||||
let pendingTx = try await coordinator.synchronizer.sendToAddress(
|
||||
spendingKey: self.coordinator.spendingKeys!.first!,
|
||||
zatoshi: Zatoshi(20000),
|
||||
toAddress: self.testRecipientAddress,
|
||||
memo: "this is a test",
|
||||
from: 0)
|
||||
pendingEntity = pendingTx
|
||||
sendExpectation.fulfill()
|
||||
} catch {
|
||||
self.handleError(error)
|
||||
}
|
||||
|
||||
wait(for: [sendExpectation], timeout: 11)
|
||||
|
||||
|
@ -761,10 +821,17 @@ class AdvancedReOrgTests: XCTestCase {
|
|||
*/
|
||||
let secondSyncExpectation = XCTestExpectation(description: "after send expectation")
|
||||
|
||||
try coordinator.sync(completion: { _ in
|
||||
secondSyncExpectation.fulfill()
|
||||
}, error: self.handleError)
|
||||
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
secondSyncExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
|
||||
wait(for: [secondSyncExpectation], timeout: 5)
|
||||
|
||||
XCTAssertEqual(coordinator.synchronizer.pendingTransactions.count, 1)
|
||||
|
@ -799,10 +866,17 @@ class AdvancedReOrgTests: XCTestCase {
|
|||
self.expectedReorgHeight = sentTxHeight + 1
|
||||
let afterReorgExpectation = XCTestExpectation(description: "after reorg sync")
|
||||
|
||||
try coordinator.sync(completion: { _ in
|
||||
afterReorgExpectation.fulfill()
|
||||
}, error: self.handleError)
|
||||
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
afterReorgExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
|
||||
wait(for: [reorgExpectation, afterReorgExpectation], timeout: 5)
|
||||
|
||||
/*
|
||||
|
@ -827,10 +901,17 @@ class AdvancedReOrgTests: XCTestCase {
|
|||
/*
|
||||
11a. sync to latest height
|
||||
*/
|
||||
try coordinator.sync(completion: { _ in
|
||||
yetAnotherExpectation.fulfill()
|
||||
}, error: self.handleError)
|
||||
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
yetAnotherExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
|
||||
wait(for: [yetAnotherExpectation], timeout: 5)
|
||||
|
||||
/*
|
||||
|
@ -857,10 +938,17 @@ class AdvancedReOrgTests: XCTestCase {
|
|||
/*
|
||||
14. sync to latest height
|
||||
*/
|
||||
try coordinator.sync(completion: { _ in
|
||||
thisIsTheLastExpectationIPromess.fulfill()
|
||||
}, error: self.handleError)
|
||||
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
thisIsTheLastExpectationIPromess.fulfill()
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
|
||||
wait(for: [thisIsTheLastExpectationIPromess], timeout: 5)
|
||||
|
||||
/*
|
||||
|
@ -1034,7 +1122,7 @@ class AdvancedReOrgTests: XCTestCase {
|
|||
8. sync to latest height
|
||||
9. verify that there's an expired transaction as a pending transaction
|
||||
*/
|
||||
func testReOrgRemovesOutboundTxAndIsNeverMined() throws {
|
||||
func testReOrgRemovesOutboundTxAndIsNeverMined() async throws {
|
||||
hookToReOrgNotification()
|
||||
|
||||
/*
|
||||
|
@ -1051,10 +1139,17 @@ class AdvancedReOrgTests: XCTestCase {
|
|||
/*
|
||||
1a. sync to latest height
|
||||
*/
|
||||
try coordinator.sync(completion: { _ in
|
||||
firstSyncExpectation.fulfill()
|
||||
}, error: self.handleError)
|
||||
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
firstSyncExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
|
||||
wait(for: [firstSyncExpectation], timeout: 5)
|
||||
|
||||
sleep(1)
|
||||
|
@ -1066,22 +1161,18 @@ class AdvancedReOrgTests: XCTestCase {
|
|||
/*
|
||||
2. send transaction to recipient address
|
||||
*/
|
||||
coordinator.synchronizer.sendToAddress(
|
||||
spendingKey: self.coordinator.spendingKeys!.first!,
|
||||
zatoshi: Zatoshi(20000),
|
||||
toAddress: self.testRecipientAddress,
|
||||
memo: "this is a test",
|
||||
from: 0,
|
||||
resultBlock: { result in
|
||||
switch result {
|
||||
case .failure(let e):
|
||||
self.handleError(e)
|
||||
case .success(let pendingTx):
|
||||
pendingEntity = pendingTx
|
||||
}
|
||||
sendExpectation.fulfill()
|
||||
}
|
||||
)
|
||||
do {
|
||||
let pendingTx = try await coordinator.synchronizer.sendToAddress(
|
||||
spendingKey: self.coordinator.spendingKeys!.first!,
|
||||
zatoshi: Zatoshi(20000),
|
||||
toAddress: self.testRecipientAddress,
|
||||
memo: "this is a test",
|
||||
from: 0)
|
||||
pendingEntity = pendingTx
|
||||
sendExpectation.fulfill()
|
||||
} catch {
|
||||
self.handleError(error)
|
||||
}
|
||||
|
||||
wait(for: [sendExpectation], timeout: 11)
|
||||
|
||||
|
@ -1121,10 +1212,17 @@ class AdvancedReOrgTests: XCTestCase {
|
|||
*/
|
||||
let secondSyncExpectation = XCTestExpectation(description: "after send expectation")
|
||||
|
||||
try coordinator.sync(completion: { _ in
|
||||
secondSyncExpectation.fulfill()
|
||||
}, error: self.handleError)
|
||||
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
secondSyncExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
|
||||
wait(for: [secondSyncExpectation], timeout: 5)
|
||||
let extraBlocks = 25
|
||||
try coordinator.stageBlockCreate(height: sentTxHeight, count: extraBlocks, nonce: 5)
|
||||
|
@ -1134,10 +1232,17 @@ class AdvancedReOrgTests: XCTestCase {
|
|||
sleep(2)
|
||||
let reorgSyncExpectation = XCTestExpectation(description: "reorg sync expectation")
|
||||
|
||||
try coordinator.sync(completion: { _ in
|
||||
reorgSyncExpectation.fulfill()
|
||||
}, error: self.handleError)
|
||||
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
reorgSyncExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
|
||||
wait(for: [reorgExpectation, reorgSyncExpectation], timeout: 5)
|
||||
|
||||
guard let pendingTx = coordinator.synchronizer.pendingTransactions.first else {
|
||||
|
@ -1154,16 +1259,23 @@ class AdvancedReOrgTests: XCTestCase {
|
|||
|
||||
let lastSyncExpectation = XCTestExpectation(description: "last sync expectation")
|
||||
|
||||
try coordinator.sync(completion: { _ in
|
||||
lastSyncExpectation.fulfill()
|
||||
}, error: self.handleError)
|
||||
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
lastSyncExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
|
||||
wait(for: [lastSyncExpectation], timeout: 5)
|
||||
|
||||
XCTAssertEqual(coordinator.synchronizer.initializer.getBalance(), initialTotalBalance)
|
||||
}
|
||||
|
||||
func testLongSync() throws {
|
||||
func testLongSync() async throws {
|
||||
hookToReOrgNotification()
|
||||
|
||||
/*
|
||||
|
@ -1182,21 +1294,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?) {
|
||||
|
|
|
@ -42,7 +42,7 @@ class BalanceTests: XCTestCase {
|
|||
/**
|
||||
verify that when sending the maximum amount, the transactions are broadcasted properly
|
||||
*/
|
||||
func testMaxAmountSend() throws {
|
||||
func testMaxAmountSend() async throws {
|
||||
let notificationHandler = SDKSynchonizerListener()
|
||||
let foundTransactionsExpectation = XCTestExpectation(description: "found transactions expectation")
|
||||
let transactionMinedExpectation = XCTestExpectation(description: "transaction mined expectation")
|
||||
|
@ -57,9 +57,16 @@ class BalanceTests: XCTestCase {
|
|||
sleep(1)
|
||||
let firstSyncExpectation = XCTestExpectation(description: "first sync expectation")
|
||||
|
||||
try coordinator.sync(completion: { _ in
|
||||
firstSyncExpectation.fulfill()
|
||||
}, error: handleError)
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
firstSyncExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
|
||||
wait(for: [firstSyncExpectation], timeout: 12)
|
||||
// 2 check that there are no unconfirmed funds
|
||||
|
@ -79,22 +86,18 @@ class BalanceTests: XCTestCase {
|
|||
}
|
||||
|
||||
var pendingTx: PendingTransactionEntity?
|
||||
coordinator.synchronizer.sendToAddress(
|
||||
spendingKey: spendingKey,
|
||||
zatoshi: maxBalance,
|
||||
toAddress: testRecipientAddress,
|
||||
memo: "test send \(self.description) \(Date().description)",
|
||||
from: 0,
|
||||
resultBlock: { result in
|
||||
switch result {
|
||||
case .failure(let error):
|
||||
XCTFail("sendToAddress failed: \(error)")
|
||||
case .success(let transaction):
|
||||
pendingTx = transaction
|
||||
}
|
||||
self.sentTransactionExpectation.fulfill()
|
||||
}
|
||||
)
|
||||
do {
|
||||
let transaction = try await coordinator.synchronizer.sendToAddress(
|
||||
spendingKey: spendingKey,
|
||||
zatoshi: maxBalance,
|
||||
toAddress: testRecipientAddress,
|
||||
memo: "test send \(self.description) \(Date().description)",
|
||||
from: 0)
|
||||
pendingTx = transaction
|
||||
self.sentTransactionExpectation.fulfill()
|
||||
} catch {
|
||||
XCTFail("sendToAddress failed: \(error)")
|
||||
}
|
||||
|
||||
wait(for: [sentTransactionExpectation], timeout: 20)
|
||||
guard let pendingTx = pendingTx else {
|
||||
|
@ -133,23 +136,29 @@ class BalanceTests: XCTestCase {
|
|||
sleep(2) // add enhance breakpoint here
|
||||
let mineExpectation = XCTestExpectation(description: "mineTxExpectation")
|
||||
|
||||
try coordinator.sync(
|
||||
completion: { synchronizer in
|
||||
let pendingEntity = synchronizer.pendingTransactions.first(where: { $0.rawTransactionId == pendingTx.rawTransactionId })
|
||||
XCTAssertNotNil(pendingEntity, "pending transaction should have been mined by now")
|
||||
XCTAssertTrue(pendingEntity?.isMined ?? false)
|
||||
XCTAssertEqual(pendingEntity?.minedHeight, sentTxHeight)
|
||||
mineExpectation.fulfill()
|
||||
},
|
||||
error: { error in
|
||||
guard let error = error else {
|
||||
XCTFail("unknown error syncing after sending transaction")
|
||||
return
|
||||
}
|
||||
|
||||
XCTFail("Error: \(error)")
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(
|
||||
completion: { synchronizer in
|
||||
let pendingEntity = synchronizer.pendingTransactions.first(where: { $0.rawTransactionId == pendingTx.rawTransactionId })
|
||||
XCTAssertNotNil(pendingEntity, "pending transaction should have been mined by now")
|
||||
XCTAssertTrue(pendingEntity?.isMined ?? false)
|
||||
XCTAssertEqual(pendingEntity?.minedHeight, sentTxHeight)
|
||||
mineExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: { error in
|
||||
guard let error = error else {
|
||||
XCTFail("unknown error syncing after sending transaction")
|
||||
return
|
||||
}
|
||||
|
||||
XCTFail("Error: \(error)")
|
||||
}
|
||||
)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
wait(for: [mineExpectation, transactionMinedExpectation, foundTransactionsExpectation], timeout: 5)
|
||||
|
||||
|
@ -166,12 +175,17 @@ class BalanceTests: XCTestCase {
|
|||
notificationHandler.synchronizerMinedTransaction = { transaction in
|
||||
XCTFail("We shouldn't find any mined transactions at this point but found \(transaction)")
|
||||
}
|
||||
try coordinator.sync(completion: { _ in
|
||||
confirmExpectation.fulfill()
|
||||
}, error: { e in
|
||||
self.handleError(e)
|
||||
})
|
||||
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
confirmExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
|
||||
wait(for: [confirmExpectation], timeout: 5)
|
||||
|
||||
let confirmedPending = try coordinator.synchronizer.allPendingTransactions()
|
||||
|
@ -186,7 +200,7 @@ class BalanceTests: XCTestCase {
|
|||
/**
|
||||
verify that when sending the maximum amount minus one zatoshi, the transactions are broadcasted properly
|
||||
*/
|
||||
func testMaxAmountMinusOneSend() throws {
|
||||
func testMaxAmountMinusOneSend() async throws {
|
||||
let notificationHandler = SDKSynchonizerListener()
|
||||
let foundTransactionsExpectation = XCTestExpectation(description: "found transactions expectation")
|
||||
let transactionMinedExpectation = XCTestExpectation(description: "transaction mined expectation")
|
||||
|
@ -201,9 +215,16 @@ class BalanceTests: XCTestCase {
|
|||
sleep(1)
|
||||
let firstSyncExpectation = XCTestExpectation(description: "first sync expectation")
|
||||
|
||||
try coordinator.sync(completion: { _ in
|
||||
firstSyncExpectation.fulfill()
|
||||
}, error: handleError)
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
firstSyncExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
|
||||
wait(for: [firstSyncExpectation], timeout: 12)
|
||||
// 2 check that there are no unconfirmed funds
|
||||
|
@ -221,24 +242,20 @@ class BalanceTests: XCTestCase {
|
|||
XCTFail("failed to create spending keys")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
var pendingTx: PendingTransactionEntity?
|
||||
coordinator.synchronizer.sendToAddress(
|
||||
spendingKey: spendingKey,
|
||||
zatoshi: maxBalanceMinusOne,
|
||||
toAddress: testRecipientAddress,
|
||||
memo: "test send \(self.description) \(Date().description)",
|
||||
from: 0,
|
||||
resultBlock: { result in
|
||||
switch result {
|
||||
case .failure(let error):
|
||||
XCTFail("sendToAddress failed: \(error)")
|
||||
case .success(let transaction):
|
||||
pendingTx = transaction
|
||||
}
|
||||
self.sentTransactionExpectation.fulfill()
|
||||
}
|
||||
)
|
||||
do {
|
||||
let transaction = try await coordinator.synchronizer.sendToAddress(
|
||||
spendingKey: spendingKey,
|
||||
zatoshi: maxBalanceMinusOne,
|
||||
toAddress: testRecipientAddress,
|
||||
memo: "test send \(self.description) \(Date().description)",
|
||||
from: 0)
|
||||
pendingTx = transaction
|
||||
self.sentTransactionExpectation.fulfill()
|
||||
} catch {
|
||||
XCTFail("sendToAddress failed: \(error)")
|
||||
}
|
||||
|
||||
wait(for: [sentTransactionExpectation], timeout: 20)
|
||||
guard let pendingTx = pendingTx else {
|
||||
|
@ -277,20 +294,29 @@ class BalanceTests: XCTestCase {
|
|||
sleep(2) // add enhance breakpoint here
|
||||
let mineExpectation = XCTestExpectation(description: "mineTxExpectation")
|
||||
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
let pendingEntity = synchronizer.pendingTransactions.first(where: { $0.rawTransactionId == pendingTx.rawTransactionId })
|
||||
XCTAssertNotNil(pendingEntity, "pending transaction should have been mined by now")
|
||||
XCTAssertTrue(pendingEntity?.isMined ?? false)
|
||||
XCTAssertEqual(pendingEntity?.minedHeight, sentTxHeight)
|
||||
mineExpectation.fulfill()
|
||||
}, error: { error in
|
||||
guard let e = error else {
|
||||
XCTFail("unknown error syncing after sending transaction")
|
||||
return
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(
|
||||
completion: { synchronizer in
|
||||
let pendingEntity = synchronizer.pendingTransactions.first(where: { $0.rawTransactionId == pendingTx.rawTransactionId })
|
||||
XCTAssertNotNil(pendingEntity, "pending transaction should have been mined by now")
|
||||
XCTAssertTrue(pendingEntity?.isMined ?? false)
|
||||
XCTAssertEqual(pendingEntity?.minedHeight, sentTxHeight)
|
||||
mineExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: { error in
|
||||
guard let error = error else {
|
||||
XCTFail("unknown error syncing after sending transaction")
|
||||
return
|
||||
}
|
||||
|
||||
XCTFail("Error: \(error)")
|
||||
}
|
||||
)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
|
||||
XCTFail("Error: \(e)")
|
||||
})
|
||||
}
|
||||
|
||||
wait(for: [mineExpectation, transactionMinedExpectation, foundTransactionsExpectation], timeout: 5)
|
||||
|
||||
|
@ -307,11 +333,16 @@ class BalanceTests: XCTestCase {
|
|||
notificationHandler.synchronizerMinedTransaction = { transaction in
|
||||
XCTFail("We shouldn't find any mined transactions at this point but found \(transaction)")
|
||||
}
|
||||
try coordinator.sync(completion: { _ in
|
||||
confirmExpectation.fulfill()
|
||||
}, error: { e in
|
||||
self.handleError(e)
|
||||
})
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
confirmExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
|
||||
wait(for: [confirmExpectation], timeout: 5)
|
||||
|
||||
|
@ -328,7 +359,7 @@ class BalanceTests: XCTestCase {
|
|||
/**
|
||||
verify that when sending the a no change transaction, the transactions are broadcasted properly
|
||||
*/
|
||||
func testSingleNoteNoChangeTransaction() throws {
|
||||
func testSingleNoteNoChangeTransaction() async throws {
|
||||
let notificationHandler = SDKSynchonizerListener()
|
||||
let foundTransactionsExpectation = XCTestExpectation(description: "found transactions expectation")
|
||||
let transactionMinedExpectation = XCTestExpectation(description: "transaction mined expectation")
|
||||
|
@ -343,10 +374,16 @@ class BalanceTests: XCTestCase {
|
|||
sleep(1)
|
||||
let firstSyncExpectation = XCTestExpectation(description: "first sync expectation")
|
||||
|
||||
try coordinator.sync(completion: { _ in
|
||||
firstSyncExpectation.fulfill()
|
||||
}, error: handleError)
|
||||
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
firstSyncExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
wait(for: [firstSyncExpectation], timeout: 12)
|
||||
// 2 check that there are no unconfirmed funds
|
||||
|
||||
|
@ -364,22 +401,18 @@ class BalanceTests: XCTestCase {
|
|||
return
|
||||
}
|
||||
var pendingTx: PendingTransactionEntity?
|
||||
coordinator.synchronizer.sendToAddress(
|
||||
spendingKey: spendingKey,
|
||||
zatoshi: maxBalanceMinusOne,
|
||||
toAddress: testRecipientAddress,
|
||||
memo: "test send \(self.description) \(Date().description)",
|
||||
from: 0,
|
||||
resultBlock: { result in
|
||||
switch result {
|
||||
case .failure(let error):
|
||||
XCTFail("sendToAddress failed: \(error)")
|
||||
case .success(let transaction):
|
||||
pendingTx = transaction
|
||||
}
|
||||
self.sentTransactionExpectation.fulfill()
|
||||
}
|
||||
)
|
||||
do {
|
||||
let transaction = try await coordinator.synchronizer.sendToAddress(
|
||||
spendingKey: spendingKey,
|
||||
zatoshi: maxBalanceMinusOne,
|
||||
toAddress: testRecipientAddress,
|
||||
memo: "test send \(self.description) \(Date().description)",
|
||||
from: 0)
|
||||
pendingTx = transaction
|
||||
self.sentTransactionExpectation.fulfill()
|
||||
} catch {
|
||||
XCTFail("sendToAddress failed: \(error)")
|
||||
}
|
||||
|
||||
wait(for: [sentTransactionExpectation], timeout: 20)
|
||||
guard let pendingTx = pendingTx else {
|
||||
|
@ -418,20 +451,29 @@ class BalanceTests: XCTestCase {
|
|||
sleep(2) // add enhance breakpoint here
|
||||
let mineExpectation = XCTestExpectation(description: "mineTxExpectation")
|
||||
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
let pendingEntity = synchronizer.pendingTransactions.first(where: { $0.rawTransactionId == pendingTx.rawTransactionId })
|
||||
XCTAssertNotNil(pendingEntity, "pending transaction should have been mined by now")
|
||||
XCTAssertTrue(pendingEntity?.isMined ?? false)
|
||||
XCTAssertEqual(pendingEntity?.minedHeight, sentTxHeight)
|
||||
mineExpectation.fulfill()
|
||||
}, error: { error in
|
||||
guard let e = error else {
|
||||
XCTFail("unknown error syncing after sending transaction")
|
||||
return
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(
|
||||
completion: { synchronizer in
|
||||
let pendingEntity = synchronizer.pendingTransactions.first(where: { $0.rawTransactionId == pendingTx.rawTransactionId })
|
||||
XCTAssertNotNil(pendingEntity, "pending transaction should have been mined by now")
|
||||
XCTAssertTrue(pendingEntity?.isMined ?? false)
|
||||
XCTAssertEqual(pendingEntity?.minedHeight, sentTxHeight)
|
||||
mineExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: { error in
|
||||
guard let error = error else {
|
||||
XCTFail("unknown error syncing after sending transaction")
|
||||
return
|
||||
}
|
||||
|
||||
XCTFail("Error: \(error)")
|
||||
}
|
||||
)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
|
||||
XCTFail("Error: \(e)")
|
||||
})
|
||||
}
|
||||
|
||||
wait(for: [mineExpectation, transactionMinedExpectation, foundTransactionsExpectation], timeout: 5)
|
||||
|
||||
|
@ -448,11 +490,16 @@ class BalanceTests: XCTestCase {
|
|||
notificationHandler.synchronizerMinedTransaction = { transaction in
|
||||
XCTFail("We shouldn't find any mined transactions at this point but found \(transaction)")
|
||||
}
|
||||
try coordinator.sync(completion: { _ in
|
||||
confirmExpectation.fulfill()
|
||||
}, error: { e in
|
||||
self.handleError(e)
|
||||
})
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
confirmExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
|
||||
wait(for: [confirmExpectation], timeout: 5)
|
||||
|
||||
|
@ -483,14 +530,21 @@ class BalanceTests: XCTestCase {
|
|||
Error: previous available funds equals to current funds
|
||||
*/
|
||||
// swiftlint:disable cyclomatic_complexity
|
||||
func testVerifyAvailableBalanceDuringSend() throws {
|
||||
func testVerifyAvailableBalanceDuringSend() async throws {
|
||||
try FakeChainBuilder.buildChain(darksideWallet: coordinator.service, branchID: branchID, chainName: chainName)
|
||||
|
||||
try coordinator.applyStaged(blockheight: defaultLatestHeight)
|
||||
|
||||
try coordinator.sync(completion: { _ in
|
||||
self.syncedExpectation.fulfill()
|
||||
}, error: handleError)
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
self.syncedExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
|
||||
wait(for: [syncedExpectation], timeout: 60)
|
||||
|
||||
|
@ -507,27 +561,23 @@ class BalanceTests: XCTestCase {
|
|||
XCTAssertTrue(presendVerifiedBalance >= network.constants.defaultFee(for: defaultLatestHeight) + sendAmount)
|
||||
|
||||
var pendingTx: PendingTransactionEntity?
|
||||
coordinator.synchronizer.sendToAddress(
|
||||
spendingKey: spendingKey,
|
||||
zatoshi: sendAmount,
|
||||
toAddress: testRecipientAddress,
|
||||
memo: "test send \(self.description) \(Date().description)",
|
||||
from: 0,
|
||||
resultBlock: { result in
|
||||
switch result {
|
||||
case .failure(let error):
|
||||
/*
|
||||
balance should be the same as before sending if transaction failed
|
||||
*/
|
||||
XCTAssertEqual(self.coordinator.synchronizer.initializer.getVerifiedBalance(), presendVerifiedBalance)
|
||||
XCTFail("sendToAddress failed: \(error)")
|
||||
case .success(let transaction):
|
||||
do {
|
||||
let transaction = try await coordinator.synchronizer.sendToAddress(
|
||||
spendingKey: spendingKey,
|
||||
zatoshi: sendAmount,
|
||||
toAddress: testRecipientAddress,
|
||||
memo: "test send \(self.description) \(Date().description)",
|
||||
from: 0)
|
||||
pendingTx = transaction
|
||||
self.sentTransactionExpectation.fulfill()
|
||||
} catch {
|
||||
/*
|
||||
balance should be the same as before sending if transaction failed
|
||||
*/
|
||||
XCTAssertEqual(self.coordinator.synchronizer.initializer.getVerifiedBalance(), presendVerifiedBalance)
|
||||
XCTFail("sendToAddress failed: \(error)")
|
||||
|
||||
pendingTx = transaction
|
||||
}
|
||||
self.sentTransactionExpectation.fulfill()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
XCTAssertTrue(coordinator.synchronizer.initializer.getVerifiedBalance() > .zero)
|
||||
wait(for: [sentTransactionExpectation], timeout: 12)
|
||||
|
@ -548,16 +598,25 @@ class BalanceTests: XCTestCase {
|
|||
sleep(1)
|
||||
let mineExpectation = XCTestExpectation(description: "mineTxExpectation")
|
||||
|
||||
try coordinator.sync(completion: { _ in
|
||||
mineExpectation.fulfill()
|
||||
}, error: { error in
|
||||
guard let e = error else {
|
||||
XCTFail("unknown error syncing after sending transaction")
|
||||
return
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(
|
||||
completion: { synchronizer in
|
||||
mineExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: { error in
|
||||
guard let error else {
|
||||
XCTFail("unknown error syncing after sending transaction")
|
||||
return
|
||||
}
|
||||
|
||||
XCTFail("Error: \(error)")
|
||||
}
|
||||
)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
|
||||
XCTFail("Error: \(e)")
|
||||
})
|
||||
}
|
||||
|
||||
wait(for: [mineExpectation], timeout: 5)
|
||||
|
||||
|
@ -654,15 +713,22 @@ class BalanceTests: XCTestCase {
|
|||
Error: previous total balance funds equals to current total balance
|
||||
|
||||
*/
|
||||
func testVerifyTotalBalanceDuringSend() throws {
|
||||
func testVerifyTotalBalanceDuringSend() async throws {
|
||||
try FakeChainBuilder.buildChain(darksideWallet: coordinator.service, branchID: branchID, chainName: chainName)
|
||||
|
||||
try coordinator.applyStaged(blockheight: defaultLatestHeight)
|
||||
|
||||
sleep(2)
|
||||
try coordinator.sync(completion: { _ in
|
||||
self.syncedExpectation.fulfill()
|
||||
}, error: handleError)
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
self.syncedExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
|
||||
wait(for: [syncedExpectation], timeout: 5)
|
||||
|
||||
|
@ -677,33 +743,28 @@ class BalanceTests: XCTestCase {
|
|||
XCTAssertTrue(presendBalance >= network.constants.defaultFee(for: defaultLatestHeight) + sendAmount)
|
||||
var pendingTx: PendingTransactionEntity?
|
||||
|
||||
var error: Error?
|
||||
coordinator.synchronizer.sendToAddress(
|
||||
spendingKey: spendingKey,
|
||||
zatoshi: sendAmount,
|
||||
toAddress: testRecipientAddress,
|
||||
memo: "test send \(self.description) \(Date().description)",
|
||||
from: 0,
|
||||
resultBlock: { result in
|
||||
switch result {
|
||||
case .failure(let e):
|
||||
// balance should be the same as before sending if transaction failed
|
||||
error = e
|
||||
XCTFail("sendToAddress failed: \(e)")
|
||||
|
||||
case .success(let transaction):
|
||||
pendingTx = transaction
|
||||
}
|
||||
self.sentTransactionExpectation.fulfill()
|
||||
}
|
||||
)
|
||||
var testError: Error?
|
||||
do {
|
||||
let transaction = try await coordinator.synchronizer.sendToAddress(
|
||||
spendingKey: spendingKey,
|
||||
zatoshi: sendAmount,
|
||||
toAddress: testRecipientAddress,
|
||||
memo: "test send \(self.description) \(Date().description)",
|
||||
from: 0)
|
||||
pendingTx = transaction
|
||||
self.sentTransactionExpectation.fulfill()
|
||||
} catch {
|
||||
// balance should be the same as before sending if transaction failed
|
||||
testError = error
|
||||
XCTFail("sendToAddress failed: \(error)")
|
||||
}
|
||||
|
||||
XCTAssertTrue(coordinator.synchronizer.initializer.getVerifiedBalance() > .zero)
|
||||
wait(for: [sentTransactionExpectation], timeout: 12)
|
||||
|
||||
if let e = error {
|
||||
if let testError {
|
||||
XCTAssertEqual(self.coordinator.synchronizer.initializer.getVerifiedBalance(), presendBalance)
|
||||
XCTFail("error: \(e)")
|
||||
XCTFail("error: \(testError)")
|
||||
return
|
||||
}
|
||||
guard let transaction = pendingTx else {
|
||||
|
@ -733,16 +794,25 @@ class BalanceTests: XCTestCase {
|
|||
sleep(2)
|
||||
let mineExpectation = XCTestExpectation(description: "mineTxExpectation")
|
||||
|
||||
try coordinator.sync(completion: { _ in
|
||||
mineExpectation.fulfill()
|
||||
}, error: { error in
|
||||
guard let e = error else {
|
||||
XCTFail("unknown error syncing after sending transaction")
|
||||
return
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(
|
||||
completion: { synchronizer in
|
||||
mineExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: { error in
|
||||
guard let error else {
|
||||
XCTFail("unknown error syncing after sending transaction")
|
||||
return
|
||||
}
|
||||
|
||||
XCTFail("Error: \(error)")
|
||||
}
|
||||
)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
|
||||
XCTFail("Error: \(e)")
|
||||
})
|
||||
}
|
||||
|
||||
wait(for: [mineExpectation], timeout: 5)
|
||||
|
||||
|
@ -802,7 +872,7 @@ class BalanceTests: XCTestCase {
|
|||
There’s a change note of value (previous note value - sent amount)
|
||||
|
||||
*/
|
||||
func testVerifyChangeTransaction() throws {
|
||||
func testVerifyChangeTransaction() async throws {
|
||||
try FakeChainBuilder.buildSingleNoteChain(darksideWallet: coordinator.service, branchID: branchID, chainName: chainName)
|
||||
|
||||
try coordinator.applyStaged(blockheight: defaultLatestHeight)
|
||||
|
@ -814,9 +884,16 @@ class BalanceTests: XCTestCase {
|
|||
/*
|
||||
sync to current tip
|
||||
*/
|
||||
try coordinator.sync(completion: { _ in
|
||||
self.syncedExpectation.fulfill()
|
||||
}, error: self.handleError)
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
self.syncedExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
|
||||
wait(for: [syncedExpectation], timeout: 6)
|
||||
|
||||
|
@ -833,26 +910,18 @@ class BalanceTests: XCTestCase {
|
|||
*/
|
||||
let memo = "shielding is fun!"
|
||||
var pendingTx: PendingTransactionEntity?
|
||||
coordinator.synchronizer.sendToAddress(
|
||||
spendingKey: spendingKeys,
|
||||
zatoshi: sendAmount,
|
||||
toAddress: testRecipientAddress,
|
||||
memo: memo,
|
||||
from: 0,
|
||||
resultBlock: { sendResult in
|
||||
DispatchQueue.main.async {
|
||||
switch sendResult {
|
||||
case .failure(let sendError):
|
||||
XCTFail("error sending \(sendError)")
|
||||
|
||||
case .success(let transaction):
|
||||
pendingTx = transaction
|
||||
}
|
||||
|
||||
sendExpectation.fulfill()
|
||||
}
|
||||
}
|
||||
)
|
||||
do {
|
||||
let transaction = try await coordinator.synchronizer.sendToAddress(
|
||||
spendingKey: spendingKeys,
|
||||
zatoshi: sendAmount,
|
||||
toAddress: testRecipientAddress,
|
||||
memo: memo,
|
||||
from: 0)
|
||||
pendingTx = transaction
|
||||
sendExpectation.fulfill()
|
||||
} catch {
|
||||
XCTFail("error sending \(error)")
|
||||
}
|
||||
wait(for: [createToAddressExpectation], timeout: 30)
|
||||
|
||||
let syncToMinedheightExpectation = XCTestExpectation(description: "sync to mined height + 1")
|
||||
|
@ -875,89 +944,93 @@ class BalanceTests: XCTestCase {
|
|||
/*
|
||||
Sync to that block
|
||||
*/
|
||||
try coordinator.sync(
|
||||
completion: { synchronizer in
|
||||
let confirmedTx: ConfirmedTransactionEntity!
|
||||
do {
|
||||
confirmedTx = try synchronizer.allClearedTransactions().first(where: { confirmed -> Bool in
|
||||
confirmed.transactionEntity.transactionId == pendingTx?.transactionEntity.transactionId
|
||||
})
|
||||
} catch {
|
||||
XCTFail("Error retrieving cleared transactions")
|
||||
return
|
||||
}
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
let confirmedTx: ConfirmedTransactionEntity!
|
||||
do {
|
||||
confirmedTx = try synchronizer.allClearedTransactions().first(where: { confirmed -> Bool in
|
||||
confirmed.transactionEntity.transactionId == pendingTx?.transactionEntity.transactionId
|
||||
})
|
||||
} catch {
|
||||
XCTFail("Error retrieving cleared transactions")
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
There’s a sent transaction matching the amount sent to the given zAddr
|
||||
*/
|
||||
XCTAssertEqual(confirmedTx.value, self.sendAmount)
|
||||
XCTAssertEqual(confirmedTx.toAddress, self.testRecipientAddress)
|
||||
XCTAssertEqual(confirmedTx.memo?.asZcashTransactionMemo(), memo)
|
||||
/*
|
||||
There’s a sent transaction matching the amount sent to the given zAddr
|
||||
*/
|
||||
XCTAssertEqual(confirmedTx.value, self.sendAmount)
|
||||
XCTAssertEqual(confirmedTx.toAddress, self.testRecipientAddress)
|
||||
XCTAssertEqual(confirmedTx.memo?.asZcashTransactionMemo(), memo)
|
||||
|
||||
guard let transactionId = confirmedTx.rawTransactionId else {
|
||||
XCTFail("no raw transaction id")
|
||||
return
|
||||
}
|
||||
guard let transactionId = confirmedTx.rawTransactionId else {
|
||||
XCTFail("no raw transaction id")
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Find out what note was used
|
||||
*/
|
||||
let sentNotesRepo = SentNotesSQLDAO(
|
||||
dbProvider: SimpleConnectionProvider(
|
||||
path: synchronizer.initializer.dataDbURL.absoluteString,
|
||||
readonly: true
|
||||
/*
|
||||
Find out what note was used
|
||||
*/
|
||||
let sentNotesRepo = SentNotesSQLDAO(
|
||||
dbProvider: SimpleConnectionProvider(
|
||||
path: synchronizer.initializer.dataDbURL.absoluteString,
|
||||
readonly: true
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
guard let sentNote = try? sentNotesRepo.sentNote(byRawTransactionId: transactionId) else {
|
||||
XCTFail("Could not finde sent note with transaction Id \(transactionId)")
|
||||
return
|
||||
}
|
||||
guard let sentNote = try? sentNotesRepo.sentNote(byRawTransactionId: transactionId) else {
|
||||
XCTFail("Could not finde sent note with transaction Id \(transactionId)")
|
||||
return
|
||||
}
|
||||
|
||||
let receivedNotesRepo = ReceivedNotesSQLDAO(
|
||||
dbProvider: SimpleConnectionProvider(
|
||||
path: self.coordinator.synchronizer.initializer.dataDbURL.absoluteString,
|
||||
readonly: true
|
||||
let receivedNotesRepo = ReceivedNotesSQLDAO(
|
||||
dbProvider: SimpleConnectionProvider(
|
||||
path: self.coordinator.synchronizer.initializer.dataDbURL.absoluteString,
|
||||
readonly: true
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
/*
|
||||
get change note
|
||||
*/
|
||||
guard let receivedNote = try? receivedNotesRepo.receivedNote(byRawTransactionId: transactionId) else {
|
||||
XCTFail("Could not find received not with change for transaction Id \(transactionId)")
|
||||
return
|
||||
}
|
||||
/*
|
||||
get change note
|
||||
*/
|
||||
guard let receivedNote = try? receivedNotesRepo.receivedNote(byRawTransactionId: transactionId) else {
|
||||
XCTFail("Could not find received not with change for transaction Id \(transactionId)")
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
There’s a change note of value (previous note value - sent amount)
|
||||
*/
|
||||
XCTAssertEqual(
|
||||
previousVerifiedBalance - self.sendAmount - self.network.constants.defaultFee(for: self.defaultLatestHeight),
|
||||
Zatoshi(Int64(receivedNote.value))
|
||||
)
|
||||
/*
|
||||
There’s a change note of value (previous note value - sent amount)
|
||||
*/
|
||||
XCTAssertEqual(
|
||||
previousVerifiedBalance - self.sendAmount - self.network.constants.defaultFee(for: self.defaultLatestHeight),
|
||||
Zatoshi(Int64(receivedNote.value))
|
||||
)
|
||||
|
||||
/*
|
||||
Balance meets verified Balance and total balance criteria
|
||||
*/
|
||||
self.verifiedBalanceValidation(
|
||||
previousBalance: previousVerifiedBalance,
|
||||
spentNoteValue: Zatoshi(Int64(sentNote.value)),
|
||||
changeValue: Zatoshi(Int64(receivedNote.value)),
|
||||
sentAmount: self.sendAmount,
|
||||
currentVerifiedBalance: synchronizer.initializer.getVerifiedBalance()
|
||||
)
|
||||
/*
|
||||
Balance meets verified Balance and total balance criteria
|
||||
*/
|
||||
self.verifiedBalanceValidation(
|
||||
previousBalance: previousVerifiedBalance,
|
||||
spentNoteValue: Zatoshi(Int64(sentNote.value)),
|
||||
changeValue: Zatoshi(Int64(receivedNote.value)),
|
||||
sentAmount: self.sendAmount,
|
||||
currentVerifiedBalance: synchronizer.initializer.getVerifiedBalance()
|
||||
)
|
||||
|
||||
self.totalBalanceValidation(
|
||||
totalBalance: synchronizer.initializer.getBalance(),
|
||||
previousTotalbalance: previousTotalBalance,
|
||||
sentAmount: self.sendAmount
|
||||
)
|
||||
self.totalBalanceValidation(
|
||||
totalBalance: synchronizer.initializer.getBalance(),
|
||||
previousTotalbalance: previousTotalBalance,
|
||||
sentAmount: self.sendAmount
|
||||
)
|
||||
|
||||
syncToMinedheightExpectation.fulfill()
|
||||
},
|
||||
error: self.handleError
|
||||
)
|
||||
syncToMinedheightExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
|
||||
wait(for: [syncToMinedheightExpectation], timeout: 5)
|
||||
}
|
||||
|
@ -985,15 +1058,21 @@ class BalanceTests: XCTestCase {
|
|||
Verified Balance is equal to verified balance previously shown before sending the expired transaction
|
||||
|
||||
*/
|
||||
func testVerifyBalanceAfterExpiredTransaction() throws {
|
||||
func testVerifyBalanceAfterExpiredTransaction() async throws {
|
||||
try FakeChainBuilder.buildChain(darksideWallet: coordinator.service, branchID: branchID, chainName: chainName)
|
||||
|
||||
try coordinator.applyStaged(blockheight: self.defaultLatestHeight)
|
||||
sleep(2)
|
||||
try coordinator.sync(completion: { _ in
|
||||
self.syncedExpectation.fulfill()
|
||||
}, error: self.handleError)
|
||||
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
self.syncedExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
wait(for: [syncedExpectation], timeout: 5)
|
||||
|
||||
guard let spendingKey = coordinator.spendingKeys?.first else {
|
||||
|
@ -1005,24 +1084,20 @@ class BalanceTests: XCTestCase {
|
|||
let previousTotalBalance: Zatoshi = coordinator.synchronizer.initializer.getBalance()
|
||||
let sendExpectation = XCTestExpectation(description: "send expectation")
|
||||
var pendingTx: PendingTransactionEntity?
|
||||
coordinator.synchronizer.sendToAddress(
|
||||
spendingKey: spendingKey,
|
||||
zatoshi: sendAmount,
|
||||
toAddress: testRecipientAddress,
|
||||
memo: "test send \(self.description)",
|
||||
from: 0,
|
||||
resultBlock: { result in
|
||||
switch result {
|
||||
case .failure(let error):
|
||||
// balance should be the same as before sending if transaction failed
|
||||
XCTAssertEqual(self.coordinator.synchronizer.initializer.getVerifiedBalance(), previousVerifiedBalance)
|
||||
XCTAssertEqual(self.coordinator.synchronizer.initializer.getBalance(), previousTotalBalance)
|
||||
XCTFail("sendToAddress failed: \(error)")
|
||||
case .success(let pending):
|
||||
pendingTx = pending
|
||||
}
|
||||
}
|
||||
)
|
||||
do {
|
||||
let pending = try await coordinator.synchronizer.sendToAddress(
|
||||
spendingKey: spendingKey,
|
||||
zatoshi: sendAmount,
|
||||
toAddress: testRecipientAddress,
|
||||
memo: "test send \(self.description)",
|
||||
from: 0)
|
||||
pendingTx = pending
|
||||
} catch {
|
||||
// balance should be the same as before sending if transaction failed
|
||||
XCTAssertEqual(self.coordinator.synchronizer.initializer.getVerifiedBalance(), previousVerifiedBalance)
|
||||
XCTAssertEqual(self.coordinator.synchronizer.initializer.getBalance(), previousTotalBalance)
|
||||
XCTFail("sendToAddress failed: \(error)")
|
||||
}
|
||||
|
||||
wait(for: [sendExpectation], timeout: 12)
|
||||
|
||||
|
@ -1038,10 +1113,16 @@ class BalanceTests: XCTestCase {
|
|||
try coordinator.applyStaged(blockheight: expiryHeight + 1)
|
||||
|
||||
sleep(2)
|
||||
try coordinator.sync(completion: { _ in
|
||||
expirationSyncExpectation.fulfill()
|
||||
}, error: self.handleError)
|
||||
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
expirationSyncExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
wait(for: [expirationSyncExpectation], timeout: 5)
|
||||
|
||||
/*
|
||||
|
|
|
@ -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() {
|
||||
|
@ -40,61 +42,26 @@ class BlockDownloaderTests: XCTestCase {
|
|||
try? FileManager.default.removeItem(at: cacheDB)
|
||||
}
|
||||
|
||||
func testSmallDownloadAsync() {
|
||||
let expect = XCTestExpectation(description: self.description)
|
||||
expect.expectedFulfillmentCount = 3
|
||||
func testSmallDownloadAsync() async {
|
||||
let lowerRange: BlockHeight = self.network.constants.saplingActivationHeight
|
||||
let upperRange: BlockHeight = self.network.constants.saplingActivationHeight + 99
|
||||
|
||||
let range = CompactBlockRange(uncheckedBounds: (lowerRange, upperRange))
|
||||
downloader.downloadBlockRange(range) { error in
|
||||
expect.fulfill()
|
||||
XCTAssertNil(error)
|
||||
do {
|
||||
try await downloader.downloadBlockRange(range)
|
||||
|
||||
// check what was 'stored'
|
||||
self.storage.latestHeight { result in
|
||||
expect.fulfill()
|
||||
|
||||
XCTAssertTrue(self.validate(result: result, against: upperRange))
|
||||
|
||||
self.downloader.lastDownloadedBlockHeight { resultHeight in
|
||||
expect.fulfill()
|
||||
XCTAssertTrue(self.validate(result: resultHeight, against: upperRange))
|
||||
}
|
||||
}
|
||||
let latestHeight = try await self.storage.latestHeightAsync()
|
||||
XCTAssertEqual(latestHeight, upperRange)
|
||||
|
||||
let resultHeight = try await self.downloader.lastDownloadedBlockHeightAsync()
|
||||
XCTAssertEqual(resultHeight, upperRange)
|
||||
} catch {
|
||||
XCTFail("testSmallDownloadAsync() shouldn't fail")
|
||||
}
|
||||
|
||||
wait(for: [expect], timeout: 2)
|
||||
}
|
||||
|
||||
func testSmallDownload() {
|
||||
let lowerRange: BlockHeight = self.network.constants.saplingActivationHeight
|
||||
let upperRange: BlockHeight = self.network.constants.saplingActivationHeight + 99
|
||||
|
||||
let range = CompactBlockRange(uncheckedBounds: (lowerRange, upperRange))
|
||||
var latest: BlockHeight = 0
|
||||
|
||||
do {
|
||||
latest = try downloader.lastDownloadedBlockHeight()
|
||||
} catch {
|
||||
XCTFail(error.localizedDescription)
|
||||
}
|
||||
|
||||
XCTAssertEqual(latest, BlockHeight.empty())
|
||||
XCTAssertNoThrow(try downloader.downloadBlockRange(range))
|
||||
|
||||
var currentLatest: BlockHeight = 0
|
||||
do {
|
||||
currentLatest = try downloader.lastDownloadedBlockHeight()
|
||||
} catch {
|
||||
XCTFail("latest block failed")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertEqual(currentLatest, upperRange )
|
||||
}
|
||||
|
||||
func testFailure() {
|
||||
func testFailure() async {
|
||||
let awfulDownloader = CompactBlockDownloader(
|
||||
service: AwfulLightWalletService(
|
||||
latestBlockHeight: self.network.constants.saplingActivationHeight + 1000,
|
||||
|
@ -103,18 +70,16 @@ class BlockDownloaderTests: XCTestCase {
|
|||
storage: ZcashConsoleFakeStorage()
|
||||
)
|
||||
|
||||
let expect = XCTestExpectation(description: self.description)
|
||||
expect.expectedFulfillmentCount = 1
|
||||
let lowerRange: BlockHeight = self.network.constants.saplingActivationHeight
|
||||
let upperRange: BlockHeight = self.network.constants.saplingActivationHeight + 99
|
||||
|
||||
let range = CompactBlockRange(uncheckedBounds: (lowerRange, upperRange))
|
||||
|
||||
awfulDownloader.downloadBlockRange(range) { error in
|
||||
expect.fulfill()
|
||||
|
||||
do {
|
||||
try await awfulDownloader.downloadBlockRange(range)
|
||||
} catch {
|
||||
XCTAssertNotNil(error)
|
||||
}
|
||||
wait(for: [expect], timeout: 2)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ class NetworkUpgradeTests: XCTestCase {
|
|||
/**
|
||||
Given that a wallet had funds prior to activation it can spend them after activation
|
||||
*/
|
||||
func testSpendPriorFundsAfterActivation() throws {
|
||||
func testSpendPriorFundsAfterActivation() async throws {
|
||||
try FakeChainBuilder.buildChain(
|
||||
darksideWallet: coordinator.service,
|
||||
birthday: birthday,
|
||||
|
@ -58,10 +58,16 @@ class NetworkUpgradeTests: XCTestCase {
|
|||
try coordinator.applyStaged(blockheight: activationHeight - ZcashSDK.defaultStaleTolerance)
|
||||
sleep(5)
|
||||
|
||||
try coordinator.sync(completion: { _ in
|
||||
firstSyncExpectation.fulfill()
|
||||
}, error: self.handleError)
|
||||
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
firstSyncExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
wait(for: [firstSyncExpectation], timeout: 120)
|
||||
let verifiedBalance: Zatoshi = coordinator.synchronizer.initializer.getVerifiedBalance()
|
||||
guard verifiedBalance > network.constants.defaultFee(for: activationHeight) else {
|
||||
|
@ -79,22 +85,18 @@ class NetworkUpgradeTests: XCTestCase {
|
|||
/*
|
||||
send transaction to recipient address
|
||||
*/
|
||||
coordinator.synchronizer.sendToAddress(
|
||||
spendingKey: self.coordinator.spendingKeys!.first!,
|
||||
zatoshi: spendAmount,
|
||||
toAddress: self.testRecipientAddress,
|
||||
memo: "this is a test",
|
||||
from: 0,
|
||||
resultBlock: { result in
|
||||
switch result {
|
||||
case .failure(let e):
|
||||
self.handleError(e)
|
||||
case .success(let pendingTx):
|
||||
pendingEntity = pendingTx
|
||||
}
|
||||
sendExpectation.fulfill()
|
||||
}
|
||||
)
|
||||
do {
|
||||
let pendingTx = try await coordinator.synchronizer.sendToAddress(
|
||||
spendingKey: self.coordinator.spendingKeys!.first!,
|
||||
zatoshi: spendAmount,
|
||||
toAddress: self.testRecipientAddress,
|
||||
memo: "this is a test",
|
||||
from: 0)
|
||||
pendingEntity = pendingTx
|
||||
sendExpectation.fulfill()
|
||||
} catch {
|
||||
self.handleError(error)
|
||||
}
|
||||
|
||||
wait(for: [sendExpectation], timeout: 11)
|
||||
|
||||
|
@ -123,10 +125,17 @@ class NetworkUpgradeTests: XCTestCase {
|
|||
sleep(1)
|
||||
let afterSendExpectation = XCTestExpectation(description: "aftersend")
|
||||
|
||||
try coordinator.sync(completion: { _ in
|
||||
afterSendExpectation.fulfill()
|
||||
}, error: self.handleError)
|
||||
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
afterSendExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
|
||||
wait(for: [afterSendExpectation], timeout: 10)
|
||||
|
||||
XCTAssertEqual(coordinator.synchronizer.initializer.getVerifiedBalance(), verifiedBalance - spendAmount)
|
||||
|
@ -135,7 +144,7 @@ class NetworkUpgradeTests: XCTestCase {
|
|||
/**
|
||||
Given that a wallet receives funds after activation it can spend them when confirmed
|
||||
*/
|
||||
func testSpendPostActivationFundsAfterConfirmation() throws {
|
||||
func testSpendPostActivationFundsAfterConfirmation() async throws {
|
||||
try FakeChainBuilder.buildChainPostActivationFunds(
|
||||
darksideWallet: coordinator.service,
|
||||
birthday: birthday,
|
||||
|
@ -148,9 +157,16 @@ class NetworkUpgradeTests: XCTestCase {
|
|||
try coordinator.applyStaged(blockheight: activationHeight + 10)
|
||||
sleep(3)
|
||||
|
||||
try coordinator.sync(completion: { _ in
|
||||
firstSyncExpectation.fulfill()
|
||||
}, error: self.handleError)
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
firstSyncExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
|
||||
wait(for: [firstSyncExpectation], timeout: 120)
|
||||
guard try !coordinator.synchronizer.allReceivedTransactions().filter({ $0.minedHeight > activationHeight }).isEmpty else {
|
||||
|
@ -168,22 +184,18 @@ class NetworkUpgradeTests: XCTestCase {
|
|||
/*
|
||||
send transaction to recipient address
|
||||
*/
|
||||
coordinator.synchronizer.sendToAddress(
|
||||
spendingKey: self.coordinator.spendingKeys!.first!,
|
||||
zatoshi: spendAmount,
|
||||
toAddress: self.testRecipientAddress,
|
||||
memo: "this is a test",
|
||||
from: 0,
|
||||
resultBlock: { result in
|
||||
switch result {
|
||||
case .failure(let e):
|
||||
self.handleError(e)
|
||||
case .success(let pendingTx):
|
||||
pendingEntity = pendingTx
|
||||
}
|
||||
sendExpectation.fulfill()
|
||||
}
|
||||
)
|
||||
do {
|
||||
let pendingTx = try await coordinator.synchronizer.sendToAddress(
|
||||
spendingKey: self.coordinator.spendingKeys!.first!,
|
||||
zatoshi: spendAmount,
|
||||
toAddress: self.testRecipientAddress,
|
||||
memo: "this is a test",
|
||||
from: 0)
|
||||
pendingEntity = pendingTx
|
||||
sendExpectation.fulfill()
|
||||
} catch {
|
||||
self.handleError(error)
|
||||
}
|
||||
|
||||
wait(for: [sendExpectation], timeout: 11)
|
||||
|
||||
|
@ -197,9 +209,16 @@ class NetworkUpgradeTests: XCTestCase {
|
|||
|
||||
let afterSendExpectation = XCTestExpectation(description: "aftersend")
|
||||
|
||||
try coordinator.sync(completion: { _ in
|
||||
afterSendExpectation.fulfill()
|
||||
}, error: self.handleError)
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
afterSendExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
|
||||
wait(for: [afterSendExpectation], timeout: 10)
|
||||
}
|
||||
|
@ -207,7 +226,7 @@ class NetworkUpgradeTests: XCTestCase {
|
|||
/**
|
||||
Given that a wallet sends funds some between (activation - expiry_height) and activation, those funds are shown as sent if mined.
|
||||
*/
|
||||
func testSpendMinedSpendThatExpiresOnActivation() throws {
|
||||
func testSpendMinedSpendThatExpiresOnActivation() async throws {
|
||||
try FakeChainBuilder.buildChain(
|
||||
darksideWallet: coordinator.service,
|
||||
birthday: birthday,
|
||||
|
@ -222,9 +241,16 @@ class NetworkUpgradeTests: XCTestCase {
|
|||
try coordinator.applyStaged(blockheight: activationHeight - 10)
|
||||
sleep(3)
|
||||
|
||||
try coordinator.sync(completion: { _ in
|
||||
firstSyncExpectation.fulfill()
|
||||
}, error: self.handleError)
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
firstSyncExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
|
||||
wait(for: [firstSyncExpectation], timeout: 120)
|
||||
let verifiedBalance: Zatoshi = coordinator.synchronizer.initializer.getVerifiedBalance()
|
||||
|
@ -237,22 +263,18 @@ class NetworkUpgradeTests: XCTestCase {
|
|||
/*
|
||||
send transaction to recipient address
|
||||
*/
|
||||
coordinator.synchronizer.sendToAddress(
|
||||
spendingKey: self.coordinator.spendingKeys!.first!,
|
||||
zatoshi: spendAmount,
|
||||
toAddress: self.testRecipientAddress,
|
||||
memo: "this is a test",
|
||||
from: 0,
|
||||
resultBlock: { result in
|
||||
switch result {
|
||||
case .failure(let e):
|
||||
self.handleError(e)
|
||||
case .success(let pendingTx):
|
||||
pendingEntity = pendingTx
|
||||
}
|
||||
sendExpectation.fulfill()
|
||||
}
|
||||
)
|
||||
do {
|
||||
let pendingTx = try await coordinator.synchronizer.sendToAddress(
|
||||
spendingKey: self.coordinator.spendingKeys!.first!,
|
||||
zatoshi: spendAmount,
|
||||
toAddress: self.testRecipientAddress,
|
||||
memo: "this is a test",
|
||||
from: 0)
|
||||
pendingEntity = pendingTx
|
||||
sendExpectation.fulfill()
|
||||
} catch {
|
||||
self.handleError(error)
|
||||
}
|
||||
|
||||
wait(for: [sendExpectation], timeout: 11)
|
||||
|
||||
|
@ -283,9 +305,16 @@ class NetworkUpgradeTests: XCTestCase {
|
|||
|
||||
let afterSendExpectation = XCTestExpectation(description: "aftersend")
|
||||
|
||||
try coordinator.sync(completion: { _ in
|
||||
afterSendExpectation.fulfill()
|
||||
}, error: self.handleError)
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
afterSendExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
|
||||
wait(for: [afterSendExpectation], timeout: 10)
|
||||
|
||||
|
@ -303,7 +332,7 @@ class NetworkUpgradeTests: XCTestCase {
|
|||
/**
|
||||
Given that a wallet sends funds somewhere between (activation - expiry_height) and activation, those funds are available if expired after expiration height.
|
||||
*/
|
||||
func testExpiredSpendAfterActivation() throws {
|
||||
func testExpiredSpendAfterActivation() async throws {
|
||||
try FakeChainBuilder.buildChain(
|
||||
darksideWallet: coordinator.service,
|
||||
birthday: birthday,
|
||||
|
@ -320,9 +349,16 @@ class NetworkUpgradeTests: XCTestCase {
|
|||
|
||||
let verifiedBalancePreActivation: Zatoshi = coordinator.synchronizer.initializer.getVerifiedBalance()
|
||||
|
||||
try coordinator.sync(completion: { _ in
|
||||
firstSyncExpectation.fulfill()
|
||||
}, error: self.handleError)
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
firstSyncExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
|
||||
wait(for: [firstSyncExpectation], timeout: 120)
|
||||
let verifiedBalance: Zatoshi = coordinator.synchronizer.initializer.getVerifiedBalance()
|
||||
|
@ -338,22 +374,18 @@ class NetworkUpgradeTests: XCTestCase {
|
|||
/*
|
||||
send transaction to recipient address
|
||||
*/
|
||||
coordinator.synchronizer.sendToAddress(
|
||||
spendingKey: self.coordinator.spendingKeys!.first!,
|
||||
zatoshi: spendAmount,
|
||||
toAddress: self.testRecipientAddress,
|
||||
memo: "this is a test",
|
||||
from: 0,
|
||||
resultBlock: { result in
|
||||
switch result {
|
||||
case .failure(let e):
|
||||
self.handleError(e)
|
||||
case .success(let pendingTx):
|
||||
pendingEntity = pendingTx
|
||||
}
|
||||
sendExpectation.fulfill()
|
||||
}
|
||||
)
|
||||
do {
|
||||
let pendingTx = try await coordinator.synchronizer.sendToAddress(
|
||||
spendingKey: self.coordinator.spendingKeys!.first!,
|
||||
zatoshi: spendAmount,
|
||||
toAddress: self.testRecipientAddress,
|
||||
memo: "this is a test",
|
||||
from: 0)
|
||||
pendingEntity = pendingTx
|
||||
sendExpectation.fulfill()
|
||||
} catch {
|
||||
self.handleError(error)
|
||||
}
|
||||
|
||||
wait(for: [sendExpectation], timeout: 11)
|
||||
|
||||
|
@ -380,9 +412,16 @@ class NetworkUpgradeTests: XCTestCase {
|
|||
|
||||
let afterSendExpectation = XCTestExpectation(description: "aftersend")
|
||||
|
||||
try coordinator.sync(completion: { _ in
|
||||
afterSendExpectation.fulfill()
|
||||
}, error: self.handleError)
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
afterSendExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
|
||||
wait(for: [afterSendExpectation], timeout: 10)
|
||||
|
||||
|
@ -400,7 +439,7 @@ class NetworkUpgradeTests: XCTestCase {
|
|||
/**
|
||||
Given that a wallet has notes both received prior and after activation these can be combined to supply a larger amount spend.
|
||||
*/
|
||||
func testCombinePreActivationNotesAndPostActivationNotesOnSpend() throws {
|
||||
func testCombinePreActivationNotesAndPostActivationNotesOnSpend() async throws {
|
||||
try FakeChainBuilder.buildChainMixedFunds(
|
||||
darksideWallet: coordinator.service,
|
||||
birthday: birthday,
|
||||
|
@ -415,9 +454,16 @@ class NetworkUpgradeTests: XCTestCase {
|
|||
try coordinator.applyStaged(blockheight: activationHeight - 1)
|
||||
sleep(3)
|
||||
|
||||
try coordinator.sync(completion: { _ in
|
||||
firstSyncExpectation.fulfill()
|
||||
}, error: self.handleError)
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
firstSyncExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
|
||||
wait(for: [firstSyncExpectation], timeout: 120)
|
||||
|
||||
|
@ -427,10 +473,17 @@ class NetworkUpgradeTests: XCTestCase {
|
|||
sleep(2)
|
||||
|
||||
let secondSyncExpectation = XCTestExpectation(description: "second sync")
|
||||
try coordinator.sync(completion: { _ in
|
||||
secondSyncExpectation.fulfill()
|
||||
}, error: self.handleError)
|
||||
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
secondSyncExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
|
||||
wait(for: [secondSyncExpectation], timeout: 10)
|
||||
guard try !coordinator.synchronizer.allReceivedTransactions().filter({ $0.minedHeight > activationHeight }).isEmpty else {
|
||||
XCTFail("this test requires funds received after activation height")
|
||||
|
@ -450,22 +503,18 @@ class NetworkUpgradeTests: XCTestCase {
|
|||
/*
|
||||
send transaction to recipient address
|
||||
*/
|
||||
coordinator.synchronizer.sendToAddress(
|
||||
spendingKey: self.coordinator.spendingKeys!.first!,
|
||||
zatoshi: spendAmount,
|
||||
toAddress: self.testRecipientAddress,
|
||||
memo: "this is a test",
|
||||
from: 0,
|
||||
resultBlock: { result in
|
||||
switch result {
|
||||
case .failure(let e):
|
||||
self.handleError(e)
|
||||
case .success(let pendingTx):
|
||||
pendingEntity = pendingTx
|
||||
}
|
||||
sendExpectation.fulfill()
|
||||
}
|
||||
)
|
||||
do {
|
||||
let pendingTx = try await coordinator.synchronizer.sendToAddress(
|
||||
spendingKey: self.coordinator.spendingKeys!.first!,
|
||||
zatoshi: spendAmount,
|
||||
toAddress: self.testRecipientAddress,
|
||||
memo: "this is a test",
|
||||
from: 0)
|
||||
pendingEntity = pendingTx
|
||||
sendExpectation.fulfill()
|
||||
} catch {
|
||||
self.handleError(error)
|
||||
}
|
||||
|
||||
wait(for: [sendExpectation], timeout: 15)
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ class PendingTransactionUpdatesTest: XCTestCase {
|
|||
reorgExpectation.fulfill()
|
||||
}
|
||||
|
||||
func testPendingTransactionMinedHeightUpdated() throws {
|
||||
func testPendingTransactionMinedHeightUpdated() async throws {
|
||||
/*
|
||||
1. create fake chain
|
||||
*/
|
||||
|
@ -78,10 +78,16 @@ class PendingTransactionUpdatesTest: XCTestCase {
|
|||
1a. sync to latest height
|
||||
*/
|
||||
LoggerProxy.info("1a. sync to latest height")
|
||||
try coordinator.sync(completion: { _ in
|
||||
firstSyncExpectation.fulfill()
|
||||
}, error: self.handleError)
|
||||
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
firstSyncExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
wait(for: [firstSyncExpectation], timeout: 5)
|
||||
|
||||
sleep(1)
|
||||
|
@ -93,23 +99,19 @@ class PendingTransactionUpdatesTest: XCTestCase {
|
|||
2. send transaction to recipient address
|
||||
*/
|
||||
LoggerProxy.info("2. send transaction to recipient address")
|
||||
coordinator.synchronizer.sendToAddress(
|
||||
// swiftlint:disable:next force_unwrapping
|
||||
spendingKey: self.coordinator.spendingKeys!.first!,
|
||||
zatoshi: Zatoshi(20000),
|
||||
toAddress: self.testRecipientAddress,
|
||||
memo: "this is a test",
|
||||
from: 0,
|
||||
resultBlock: { result in
|
||||
switch result {
|
||||
case .failure(let e):
|
||||
self.handleError(e)
|
||||
case .success(let pendingTx):
|
||||
pendingEntity = pendingTx
|
||||
}
|
||||
sendExpectation.fulfill()
|
||||
}
|
||||
)
|
||||
do {
|
||||
let pendingTx = try await coordinator.synchronizer.sendToAddress(
|
||||
// swiftlint:disable:next force_unwrapping
|
||||
spendingKey: self.coordinator.spendingKeys!.first!,
|
||||
zatoshi: Zatoshi(20000),
|
||||
toAddress: self.testRecipientAddress,
|
||||
memo: "this is a test",
|
||||
from: 0)
|
||||
pendingEntity = pendingTx
|
||||
sendExpectation.fulfill()
|
||||
} catch {
|
||||
self.handleError(error)
|
||||
}
|
||||
|
||||
wait(for: [sendExpectation], timeout: 11)
|
||||
|
||||
|
@ -167,13 +169,17 @@ class PendingTransactionUpdatesTest: XCTestCase {
|
|||
LoggerProxy.info("6. sync to latest height")
|
||||
let secondSyncExpectation = XCTestExpectation(description: "after send expectation")
|
||||
|
||||
try coordinator.sync(
|
||||
completion: { _ in
|
||||
secondSyncExpectation.fulfill()
|
||||
},
|
||||
error: self.handleError
|
||||
)
|
||||
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
secondSyncExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
|
||||
wait(for: [secondSyncExpectation], timeout: 5)
|
||||
|
||||
XCTAssertEqual(coordinator.synchronizer.pendingTransactions.count, 1)
|
||||
|
@ -207,10 +213,17 @@ class PendingTransactionUpdatesTest: XCTestCase {
|
|||
*/
|
||||
LoggerProxy.info("last sync to latest height: \(lastStageHeight)")
|
||||
|
||||
try coordinator.sync(completion: { _ in
|
||||
syncToConfirmExpectation.fulfill()
|
||||
}, error: self.handleError)
|
||||
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
syncToConfirmExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
|
||||
wait(for: [syncToConfirmExpectation], timeout: 6)
|
||||
var supposedlyPendingUnexistingTransaction: PendingTransactionEntity?
|
||||
|
||||
|
|
|
@ -64,7 +64,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)
|
||||
|
||||
|
@ -86,7 +86,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)
|
||||
|
@ -107,7 +107,7 @@ class RewindRescanTests: XCTestCase {
|
|||
XCTAssertEqual(totalBalance, coordinator.synchronizer.initializer.getBalance())
|
||||
}
|
||||
|
||||
func testRescanToHeight() throws {
|
||||
func testRescanToHeight() async throws {
|
||||
// 1 sync and get spendable funds
|
||||
try FakeChainBuilder.buildChainWithTxsFarFromEachOther(
|
||||
darksideWallet: coordinator.service,
|
||||
|
@ -121,13 +121,16 @@ class RewindRescanTests: XCTestCase {
|
|||
let initialVerifiedBalance: Zatoshi = coordinator.synchronizer.initializer.getVerifiedBalance()
|
||||
let firstSyncExpectation = XCTestExpectation(description: "first sync expectation")
|
||||
|
||||
try coordinator.sync(
|
||||
completion: { _ in
|
||||
firstSyncExpectation.fulfill()
|
||||
},
|
||||
error: handleError
|
||||
)
|
||||
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
firstSyncExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
wait(for: [firstSyncExpectation], timeout: 20)
|
||||
let verifiedBalance: Zatoshi = coordinator.synchronizer.initializer.getVerifiedBalance()
|
||||
let totalBalance: Zatoshi = coordinator.synchronizer.initializer.getBalance()
|
||||
|
@ -142,7 +145,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")")
|
||||
|
@ -154,10 +157,17 @@ class RewindRescanTests: XCTestCase {
|
|||
|
||||
let secondScanExpectation = XCTestExpectation(description: "rescan")
|
||||
|
||||
try coordinator.sync(completion: { _ in
|
||||
secondScanExpectation.fulfill()
|
||||
}, error: handleError)
|
||||
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
secondScanExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
|
||||
wait(for: [secondScanExpectation], timeout: 20)
|
||||
|
||||
// verify that the balance still adds up
|
||||
|
@ -166,25 +176,21 @@ class RewindRescanTests: XCTestCase {
|
|||
|
||||
// try to spend the funds
|
||||
let sendExpectation = XCTestExpectation(description: "after rewind expectation")
|
||||
coordinator.synchronizer.sendToAddress(
|
||||
spendingKey: coordinator.spendingKey,
|
||||
zatoshi: Zatoshi(1000),
|
||||
toAddress: testRecipientAddress,
|
||||
memo: nil,
|
||||
from: 0
|
||||
) { result in
|
||||
sendExpectation.fulfill()
|
||||
switch result {
|
||||
case .success(let pendingTx):
|
||||
XCTAssertEqual(Zatoshi(1000), pendingTx.value)
|
||||
case .failure(let error):
|
||||
XCTFail("sending fail: \(error)")
|
||||
}
|
||||
do {
|
||||
let pendingTx = try await coordinator.synchronizer.sendToAddress(
|
||||
spendingKey: coordinator.spendingKey,
|
||||
zatoshi: Zatoshi(1000),
|
||||
toAddress: testRecipientAddress,
|
||||
memo: nil,
|
||||
from: 0)
|
||||
XCTAssertEqual(Zatoshi(1000), pendingTx.value)
|
||||
} catch {
|
||||
XCTFail("sending fail: \(error)")
|
||||
}
|
||||
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)
|
||||
|
||||
|
@ -210,7 +216,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(
|
||||
|
@ -231,7 +237,7 @@ class RewindRescanTests: XCTestCase {
|
|||
XCTAssertEqual(totalBalance, coordinator.synchronizer.initializer.getBalance())
|
||||
}
|
||||
|
||||
func testRewindAfterSendingTransaction() throws {
|
||||
func testRewindAfterSendingTransaction() async throws {
|
||||
let notificationHandler = SDKSynchonizerListener()
|
||||
let foundTransactionsExpectation = XCTestExpectation(description: "found transactions expectation")
|
||||
let transactionMinedExpectation = XCTestExpectation(description: "transaction mined expectation")
|
||||
|
@ -246,10 +252,16 @@ class RewindRescanTests: XCTestCase {
|
|||
sleep(1)
|
||||
let firstSyncExpectation = XCTestExpectation(description: "first sync expectation")
|
||||
|
||||
try coordinator.sync(completion: { _ in
|
||||
firstSyncExpectation.fulfill()
|
||||
}, error: handleError)
|
||||
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
firstSyncExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
wait(for: [firstSyncExpectation], timeout: 12)
|
||||
// 2 check that there are no unconfirmed funds
|
||||
|
||||
|
@ -267,20 +279,17 @@ class RewindRescanTests: XCTestCase {
|
|||
return
|
||||
}
|
||||
var pendingTx: PendingTransactionEntity?
|
||||
coordinator.synchronizer.sendToAddress(
|
||||
spendingKey: spendingKey,
|
||||
zatoshi: maxBalance,
|
||||
toAddress: testRecipientAddress,
|
||||
memo: "test send \(self.description) \(Date().description)",
|
||||
from: 0
|
||||
) { result in
|
||||
switch result {
|
||||
case .failure(let error):
|
||||
XCTFail("sendToAddress failed: \(error)")
|
||||
case .success(let transaction):
|
||||
pendingTx = transaction
|
||||
}
|
||||
do {
|
||||
let transaction = try await coordinator.synchronizer.sendToAddress(
|
||||
spendingKey: spendingKey,
|
||||
zatoshi: maxBalance,
|
||||
toAddress: testRecipientAddress,
|
||||
memo: "test send \(self.description) \(Date().description)",
|
||||
from: 0)
|
||||
pendingTx = transaction
|
||||
self.sentTransactionExpectation.fulfill()
|
||||
} catch {
|
||||
XCTFail("sendToAddress failed: \(error)")
|
||||
}
|
||||
wait(for: [sentTransactionExpectation], timeout: 20)
|
||||
guard let pendingTx = pendingTx else {
|
||||
|
@ -320,25 +329,30 @@ class RewindRescanTests: XCTestCase {
|
|||
|
||||
let mineExpectation = XCTestExpectation(description: "mineTxExpectation")
|
||||
|
||||
try coordinator.sync(
|
||||
completion: { synchronizer in
|
||||
let pendingTransaction = synchronizer.pendingTransactions
|
||||
.first(where: { $0.rawTransactionId == pendingTx.rawTransactionId })
|
||||
XCTAssertNotNil(pendingTransaction, "pending transaction should have been mined by now")
|
||||
XCTAssertTrue(pendingTransaction?.isMined ?? false)
|
||||
XCTAssertEqual(pendingTransaction?.minedHeight, sentTxHeight)
|
||||
mineExpectation.fulfill()
|
||||
},
|
||||
error: { error in
|
||||
guard let e = error else {
|
||||
XCTFail("unknown error syncing after sending transaction")
|
||||
return
|
||||
}
|
||||
|
||||
XCTFail("Error: \(e)")
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(
|
||||
completion: { synchronizer in
|
||||
let pendingTransaction = synchronizer.pendingTransactions
|
||||
.first(where: { $0.rawTransactionId == pendingTx.rawTransactionId })
|
||||
XCTAssertNotNil(pendingTransaction, "pending transaction should have been mined by now")
|
||||
XCTAssertTrue(pendingTransaction?.isMined ?? false)
|
||||
XCTAssertEqual(pendingTransaction?.minedHeight, sentTxHeight)
|
||||
mineExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: { error in
|
||||
guard let error else {
|
||||
XCTFail("unknown error syncing after sending transaction")
|
||||
return
|
||||
}
|
||||
|
||||
XCTFail("Error: \(error)")
|
||||
}
|
||||
)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
wait(for: [mineExpectation, transactionMinedExpectation, foundTransactionsExpectation], timeout: 5)
|
||||
|
||||
// 7 advance to confirmation
|
||||
|
@ -349,7 +363,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()
|
||||
|
@ -375,15 +389,16 @@ class RewindRescanTests: XCTestCase {
|
|||
XCTFail("We shouldn't find any mined transactions at this point but found \(transaction)")
|
||||
}
|
||||
|
||||
try coordinator.sync(
|
||||
completion: { _ in
|
||||
confirmExpectation.fulfill()
|
||||
},
|
||||
error: { e in
|
||||
self.handleError(e)
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
confirmExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
wait(for: [confirmExpectation], timeout: 10)
|
||||
|
||||
let confirmedPending = try coordinator.synchronizer.allPendingTransactions()
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import XCTest
|
||||
@testable import TestUtils
|
||||
@testable import ZcashLightClientKit
|
||||
|
||||
class ShieldFundsTests: XCTestCase {
|
||||
// TODO: Parameterize this from environment?
|
||||
// swiftlint:disable:next line_length
|
||||
|
@ -82,7 +83,7 @@ class ShieldFundsTests: XCTestCase {
|
|||
/// 15. sync up to the new chain tip
|
||||
/// verify that the shielded transactions are confirmed
|
||||
///
|
||||
func testShieldFunds() throws {
|
||||
func testShieldFunds() async throws {
|
||||
// 1. load the dataset
|
||||
try coordinator.service.useDataset(from: "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/shielding-dataset/shield-funds/1631000.txt")
|
||||
|
||||
|
@ -92,7 +93,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: """
|
||||
{
|
||||
|
@ -110,15 +111,19 @@ class ShieldFundsTests: XCTestCase {
|
|||
let preTxExpectation = XCTestExpectation(description: "pre receive")
|
||||
|
||||
// 3. sync up to that height
|
||||
try coordinator.sync(
|
||||
completion: { synchro in
|
||||
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: { synchronizer in
|
||||
initialVerifiedBalance = synchronizer.initializer.getVerifiedBalance()
|
||||
initialTotalBalance = synchronizer.initializer.getBalance()
|
||||
preTxExpectation.fulfill()
|
||||
shouldContinue = true
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
|
||||
wait(for: [preTxExpectation], timeout: 10)
|
||||
|
||||
|
@ -130,7 +135,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)
|
||||
|
@ -149,19 +154,22 @@ class ShieldFundsTests: XCTestCase {
|
|||
shouldContinue = false
|
||||
|
||||
// 6. Sync and find the UXTO on chain.
|
||||
try coordinator.sync(
|
||||
completion: { synchro in
|
||||
tFundsDetectionExpectation.fulfill()
|
||||
shouldContinue = true
|
||||
},
|
||||
error: self.handleError
|
||||
)
|
||||
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
shouldContinue = true
|
||||
tFundsDetectionExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
wait(for: [tFundsDetectionExpectation], timeout: 2)
|
||||
|
||||
// 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
|
||||
|
@ -176,18 +184,22 @@ class ShieldFundsTests: XCTestCase {
|
|||
sleep(2)
|
||||
|
||||
// 8. sync up to chain tip.
|
||||
try coordinator.sync(
|
||||
completion: { synchro in
|
||||
tFundsConfirmationSyncExpectation.fulfill()
|
||||
shouldContinue = true
|
||||
},
|
||||
error: self.handleError
|
||||
)
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
shouldContinue = true
|
||||
tFundsConfirmationSyncExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
|
@ -209,29 +221,25 @@ class ShieldFundsTests: XCTestCase {
|
|||
var shieldingPendingTx: PendingTransactionEntity?
|
||||
|
||||
// shield the funds
|
||||
coordinator.synchronizer.shieldFunds(
|
||||
spendingKey: coordinator.spendingKey,
|
||||
transparentSecretKey: transparentSecretKey,
|
||||
memo: "shield funds",
|
||||
from: 0
|
||||
) { result in
|
||||
switch result {
|
||||
case .failure(let error):
|
||||
XCTFail("Failed With error: \(error.localizedDescription)")
|
||||
|
||||
case .success(let pendingTx):
|
||||
shouldContinue = true
|
||||
XCTAssertEqual(pendingTx.value, Zatoshi(10000))
|
||||
shieldingPendingTx = pendingTx
|
||||
}
|
||||
do {
|
||||
let pendingTx = try await coordinator.synchronizer.shieldFunds(
|
||||
spendingKey: coordinator.spendingKey,
|
||||
transparentSecretKey: transparentSecretKey,
|
||||
memo: "shield funds",
|
||||
from: 0)
|
||||
shouldContinue = true
|
||||
XCTAssertEqual(pendingTx.value, Zatoshi(10000))
|
||||
shieldingPendingTx = pendingTx
|
||||
shieldFundsExpectation.fulfill()
|
||||
} catch {
|
||||
XCTFail("Failed With error: \(error.localizedDescription)")
|
||||
}
|
||||
|
||||
wait(for: [shieldFundsExpectation], timeout: 30)
|
||||
|
||||
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
|
||||
|
@ -264,14 +272,17 @@ class ShieldFundsTests: XCTestCase {
|
|||
// 13. sync up to chain tip
|
||||
let postShieldSyncExpectation = XCTestExpectation(description: "sync Post shield")
|
||||
shouldContinue = false
|
||||
try coordinator.sync(
|
||||
completion: { synchro in
|
||||
postShieldSyncExpectation.fulfill()
|
||||
shouldContinue = true
|
||||
},
|
||||
error: self.handleError
|
||||
)
|
||||
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
shouldContinue = true
|
||||
postShieldSyncExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
wait(for: [postShieldSyncExpectation], timeout: 3)
|
||||
|
||||
guard shouldContinue else { return }
|
||||
|
@ -279,7 +290,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
|
||||
|
@ -294,13 +305,17 @@ class ShieldFundsTests: XCTestCase {
|
|||
shouldContinue = false
|
||||
|
||||
// 15. sync up to the new chain tip
|
||||
try coordinator.sync(
|
||||
completion: { synchro in
|
||||
confirmationExpectation.fulfill()
|
||||
shouldContinue = true
|
||||
},
|
||||
error: self.handleError
|
||||
)
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
shouldContinue = true
|
||||
confirmationExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
|
||||
wait(for: [confirmationExpectation], timeout: 5)
|
||||
|
||||
|
@ -312,7 +327,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)
|
||||
|
||||
|
|
|
@ -67,7 +67,7 @@ final class SynchronizerTests: XCTestCase {
|
|||
reorgExpectation.fulfill()
|
||||
}
|
||||
|
||||
func testSynchronizerStops() throws {
|
||||
func testSynchronizerStops() async throws {
|
||||
hookToReOrgNotification()
|
||||
|
||||
/*
|
||||
|
@ -102,14 +102,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, .stopped)
|
||||
let state = await coordinator.synchronizer.blockProcessor.state
|
||||
XCTAssertEqual(state, .stopped)
|
||||
}
|
||||
|
||||
func handleError(_ error: Error?) {
|
||||
|
|
|
@ -110,7 +110,7 @@ class TransactionEnhancementTests: XCTestCase {
|
|||
NotificationCenter.default.removeObserver(self)
|
||||
}
|
||||
|
||||
private func startProcessing() throws {
|
||||
private func startProcessing() async throws {
|
||||
XCTAssertNotNil(processor)
|
||||
|
||||
// Subscribe to notifications
|
||||
|
@ -120,17 +120,17 @@ class TransactionEnhancementTests: XCTestCase {
|
|||
startedValidatingNotificationExpectation.subscribe(to: Notification.Name.blockProcessorStartedValidating, object: processor)
|
||||
startedScanningNotificationExpectation.subscribe(to: Notification.Name.blockProcessorStartedScanning, object: processor)
|
||||
|
||||
try processor.start()
|
||||
try await processor.start()
|
||||
}
|
||||
|
||||
func testBasicEnhacement() throws {
|
||||
func testBasicEnhacement() async throws {
|
||||
let targetLatestHeight = BlockHeight(663250)
|
||||
let walletBirthday = Checkpoint.birthday(with: 663151, network: network).height
|
||||
|
||||
try basicEnhancementTest(latestHeight: targetLatestHeight, walletBirthday: walletBirthday)
|
||||
try await basicEnhancementTest(latestHeight: targetLatestHeight, walletBirthday: walletBirthday)
|
||||
}
|
||||
|
||||
func basicEnhancementTest(latestHeight: BlockHeight, walletBirthday: BlockHeight) throws {
|
||||
func basicEnhancementTest(latestHeight: BlockHeight, walletBirthday: BlockHeight) async throws {
|
||||
do {
|
||||
try darksideWalletService.reset(saplingActivation: 663150, branchID: branchID, chainName: chainName)
|
||||
try darksideWalletService.useDataset(DarksideDataset.beforeReOrg.rawValue)
|
||||
|
@ -157,7 +157,7 @@ class TransactionEnhancementTests: XCTestCase {
|
|||
download and sync blocks from walletBirthday to firstLatestHeight
|
||||
*/
|
||||
do {
|
||||
try startProcessing()
|
||||
try await startProcessing()
|
||||
} catch {
|
||||
XCTFail("Error: \(error)")
|
||||
}
|
||||
|
|
|
@ -69,7 +69,7 @@ class Z2TReceiveTests: XCTestCase {
|
|||
self.foundTransactionsExpectation.fulfill()
|
||||
}
|
||||
|
||||
func testFoundTransactions() throws {
|
||||
func testFoundTransactions() async throws {
|
||||
subscribeToFoundTransactions()
|
||||
try FakeChainBuilder.buildChain(darksideWallet: self.coordinator.service, branchID: branchID, chainName: chainName)
|
||||
let receivedTxHeight: BlockHeight = 663188
|
||||
|
@ -85,42 +85,42 @@ class Z2TReceiveTests: XCTestCase {
|
|||
/*
|
||||
3. sync up to received_Tx_height
|
||||
*/
|
||||
try coordinator.sync(
|
||||
completion: { _ in
|
||||
preTxExpectation.fulfill()
|
||||
},
|
||||
error: self.handleError
|
||||
)
|
||||
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
preTxExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
wait(for: [preTxExpectation, foundTransactionsExpectation], timeout: 5)
|
||||
|
||||
let sendExpectation = XCTestExpectation(description: "sendToAddress")
|
||||
var pendingEntity: PendingTransactionEntity?
|
||||
var error: Error?
|
||||
var testError: Error?
|
||||
let sendAmount = Zatoshi(10000)
|
||||
/*
|
||||
4. create transaction
|
||||
*/
|
||||
coordinator.synchronizer.sendToAddress(
|
||||
spendingKey: coordinator.spendingKeys!.first!,
|
||||
zatoshi: sendAmount,
|
||||
toAddress: testRecipientAddress,
|
||||
memo: "test transaction",
|
||||
from: 0
|
||||
) { result in
|
||||
switch result {
|
||||
case .success(let pending):
|
||||
pendingEntity = pending
|
||||
case .failure(let e):
|
||||
error = e
|
||||
}
|
||||
do {
|
||||
let pending = try await coordinator.synchronizer.sendToAddress(
|
||||
spendingKey: coordinator.spendingKeys!.first!,
|
||||
zatoshi: sendAmount,
|
||||
toAddress: testRecipientAddress,
|
||||
memo: "test transaction",
|
||||
from: 0)
|
||||
pendingEntity = pending
|
||||
sendExpectation.fulfill()
|
||||
} catch {
|
||||
testError = error
|
||||
}
|
||||
|
||||
wait(for: [sendExpectation], timeout: 12)
|
||||
|
||||
guard pendingEntity != nil else {
|
||||
XCTFail("error sending to address. Error: \(String(describing: error))")
|
||||
XCTFail("error sending to address. Error: \(String(describing: testError))")
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -154,13 +154,20 @@ class Z2TReceiveTests: XCTestCase {
|
|||
*/
|
||||
let sentTxSyncExpectation = XCTestExpectation(description: "sent tx sync expectation")
|
||||
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
let pMinedHeight = synchronizer.pendingTransactions.first?.minedHeight
|
||||
XCTAssertEqual(pMinedHeight, sentTxHeight)
|
||||
|
||||
sentTxSyncExpectation.fulfill()
|
||||
}, error: self.handleError)
|
||||
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
do {
|
||||
try coordinator.sync(completion: { synchronizer in
|
||||
let pMinedHeight = synchronizer.pendingTransactions.first?.minedHeight
|
||||
XCTAssertEqual(pMinedHeight, sentTxHeight)
|
||||
|
||||
sentTxSyncExpectation.fulfill()
|
||||
continuation.resume()
|
||||
}, error: self.handleError)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
|
||||
wait(for: [sentTxSyncExpectation, foundTransactionsExpectation], timeout: 5)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,265 +0,0 @@
|
|||
//
|
||||
// BlockScanOperationTests.swift
|
||||
// ZcashLightClientKitTests
|
||||
//
|
||||
// Created by Francisco Gindre on 10/17/19.
|
||||
// Copyright © 2019 Electric Coin Company. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
import SQLite
|
||||
@testable import TestUtils
|
||||
@testable import ZcashLightClientKit
|
||||
|
||||
// swiftlint:disable implicitly_unwrapped_optional force_try force_unwrapping print_function_usage
|
||||
class BlockScanOperationTests: XCTestCase {
|
||||
let rustWelding = ZcashRustBackend.self
|
||||
|
||||
var operationQueue = OperationQueue()
|
||||
var cacheDbURL: URL!
|
||||
var dataDbURL: URL!
|
||||
|
||||
var uvk = UVFakeKey(
|
||||
extfvk: "zxviewtestsapling1qw88ayg8qqqqpqyhg7jnh9mlldejfqwu46pm40ruwstd8znq3v3l4hjf33qcu2a5e36katshcfhcxhzgyfugj2lkhmt40j45cv38rv3frnghzkxcx73k7m7afw9j7ujk7nm4dx5mv02r26umxqgar7v3x390w2h3crqqgjsjly7jy4vtwzrmustm5yudpgcydw7x78awca8wqjvkqj8p8e3ykt7lrgd7xf92fsfqjs5vegfsja4ekzpfh5vtccgvs5747xqm6qflmtqpr8s9u", // swiftlint:disable:this line_length
|
||||
extpub: "02075a7f5f7507d64022dad5954849f216b0f1b09b2d588be663d8e7faeb5aaf61"
|
||||
)
|
||||
|
||||
var walletBirthDay = Checkpoint.birthday(
|
||||
with: 1386000,
|
||||
network: ZcashNetworkBuilder.network(for: .testnet)
|
||||
)
|
||||
|
||||
var network = ZcashNetworkBuilder.network(for: .testnet)
|
||||
var blockRepository: BlockRepository!
|
||||
|
||||
override func setUp() {
|
||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||
super.setUp()
|
||||
self.cacheDbURL = try! __cacheDbURL()
|
||||
self.dataDbURL = try! __dataDbURL()
|
||||
|
||||
deleteDBs()
|
||||
operationQueue.maxConcurrentOperationCount = 1
|
||||
}
|
||||
|
||||
private func deleteDBs() {
|
||||
try? FileManager.default.removeItem(at: cacheDbURL)
|
||||
try? FileManager.default.removeItem(at: dataDbURL)
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||
super.tearDown()
|
||||
operationQueue.cancelAllOperations()
|
||||
|
||||
try? FileManager.default.removeItem(at: cacheDbURL)
|
||||
try? FileManager.default.removeItem(at: dataDbURL)
|
||||
}
|
||||
|
||||
func testSingleDownloadAndScanOperation() {
|
||||
logger = SampleLogger(logLevel: .debug)
|
||||
|
||||
XCTAssertNoThrow(try rustWelding.initDataDb(dbData: dataDbURL, networkType: network.networkType))
|
||||
|
||||
let downloadStartedExpect = XCTestExpectation(description: "\(self.description) download started")
|
||||
let downloadExpect = XCTestExpectation(description: "\(self.description) download")
|
||||
let scanStartedExpect = XCTestExpectation(description: "\(self.description) scan started")
|
||||
let scanExpect = XCTestExpectation(description: "\(self.description) scan")
|
||||
let latestScannedBlockExpect = XCTestExpectation(description: "\(self.description) latestScannedHeight")
|
||||
let service = LightWalletGRPCService(
|
||||
endpoint: LightWalletEndpoint(
|
||||
address: "lightwalletd.testnet.electriccoin.co",
|
||||
port: 9067
|
||||
)
|
||||
)
|
||||
let blockCount = 100
|
||||
let range = network.constants.saplingActivationHeight ... network.constants.saplingActivationHeight + blockCount
|
||||
let downloadOperation = CompactBlockDownloadOperation(
|
||||
downloader: CompactBlockDownloader.sqlDownloader(
|
||||
service: service,
|
||||
at: cacheDbURL
|
||||
)!,
|
||||
range: range
|
||||
)
|
||||
let scanOperation = CompactBlockScanningOperation(
|
||||
rustWelding: rustWelding,
|
||||
cacheDb: cacheDbURL,
|
||||
dataDb: dataDbURL,
|
||||
networkType: network.networkType
|
||||
)
|
||||
|
||||
downloadOperation.startedHandler = {
|
||||
downloadStartedExpect.fulfill()
|
||||
}
|
||||
|
||||
downloadOperation.completionHandler = { finished, cancelled in
|
||||
downloadExpect.fulfill()
|
||||
XCTAssertTrue(finished)
|
||||
XCTAssertFalse(cancelled)
|
||||
}
|
||||
|
||||
downloadOperation.errorHandler = { error in
|
||||
XCTFail("Download Operation failed with Error: \(error)")
|
||||
}
|
||||
|
||||
scanOperation.startedHandler = {
|
||||
scanStartedExpect.fulfill()
|
||||
}
|
||||
|
||||
scanOperation.completionHandler = { finished, cancelled in
|
||||
scanExpect.fulfill()
|
||||
XCTAssertFalse(cancelled)
|
||||
XCTAssertTrue(finished)
|
||||
}
|
||||
|
||||
scanOperation.errorHandler = { error in
|
||||
XCTFail("Scan Operation failed with Error: \(error)")
|
||||
}
|
||||
|
||||
scanOperation.addDependency(downloadOperation)
|
||||
var latestScannedheight = BlockHeight.empty()
|
||||
let latestScannedBlockOperation = BlockOperation {
|
||||
let repository = BlockSQLDAO(dbProvider: SimpleConnectionProvider.init(path: self.dataDbURL.absoluteString, readonly: true))
|
||||
latestScannedheight = repository.lastScannedBlockHeight()
|
||||
}
|
||||
|
||||
latestScannedBlockOperation.completionBlock = {
|
||||
latestScannedBlockExpect.fulfill()
|
||||
XCTAssertEqual(latestScannedheight, range.upperBound)
|
||||
}
|
||||
|
||||
latestScannedBlockOperation.addDependency(scanOperation)
|
||||
|
||||
operationQueue.addOperations(
|
||||
[downloadOperation, scanOperation, latestScannedBlockOperation],
|
||||
waitUntilFinished: false
|
||||
)
|
||||
|
||||
wait(
|
||||
for: [downloadStartedExpect, downloadExpect, scanStartedExpect, scanExpect, latestScannedBlockExpect],
|
||||
timeout: 10,
|
||||
enforceOrder: true
|
||||
)
|
||||
}
|
||||
@objc func observeBenchmark(_ notification: Notification) {
|
||||
guard let report = SDKMetrics.blockReportFromNotification(notification) else {
|
||||
return
|
||||
}
|
||||
|
||||
print("observed benchmark: \(report)")
|
||||
}
|
||||
func testScanValidateDownload() throws {
|
||||
logger = SampleLogger(logLevel: .debug)
|
||||
|
||||
NotificationCenter.default.addObserver(
|
||||
self,
|
||||
selector: #selector(observeBenchmark(_:)),
|
||||
name: SDKMetrics.notificationName,
|
||||
object: nil
|
||||
)
|
||||
|
||||
try self.rustWelding.initDataDb(dbData: dataDbURL, networkType: network.networkType)
|
||||
|
||||
guard try self.rustWelding.initAccountsTable(dbData: self.dataDbURL, uvks: [uvk], networkType: network.networkType) else {
|
||||
XCTFail("failed to init account table")
|
||||
return
|
||||
}
|
||||
|
||||
try self.rustWelding.initBlocksTable(
|
||||
dbData: dataDbURL,
|
||||
height: Int32(walletBirthDay.height),
|
||||
hash: walletBirthDay.hash,
|
||||
time: walletBirthDay.time,
|
||||
saplingTree: walletBirthDay.saplingTree,
|
||||
networkType: network.networkType
|
||||
)
|
||||
|
||||
let service = LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.eccTestnet)
|
||||
let storage = CompactBlockStorage(url: cacheDbURL, readonly: false)
|
||||
try storage.createTable()
|
||||
|
||||
let downloadExpectation = XCTestExpectation(description: "download expectation")
|
||||
let validateExpectation = XCTestExpectation(description: "validate expectation")
|
||||
let scanExpectation = XCTestExpectation(description: "scan expectation")
|
||||
|
||||
let downloadOperation = CompactBlockStreamDownloadOperation(
|
||||
service: service,
|
||||
storage: storage,
|
||||
blockBufferSize: 10,
|
||||
startHeight: walletBirthDay.height,
|
||||
targetHeight: walletBirthDay.height + 10000,
|
||||
progressDelegate: self
|
||||
)
|
||||
|
||||
downloadOperation.completionHandler = { finished, cancelled in
|
||||
XCTAssert(finished)
|
||||
XCTAssertFalse(cancelled)
|
||||
downloadExpectation.fulfill()
|
||||
}
|
||||
|
||||
downloadOperation.errorHandler = { error in
|
||||
if let lwdError = error as? LightWalletServiceError {
|
||||
switch lwdError {
|
||||
case .timeOut:
|
||||
XCTAssert(true)
|
||||
default:
|
||||
XCTFail("LWD Service error found, but should have been a timeLimit reached Error - \(lwdError)")
|
||||
}
|
||||
} else {
|
||||
XCTFail("Error should have been a timeLimit reached Error - \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
let validationOperation = CompactBlockValidationOperation(
|
||||
rustWelding: rustWelding,
|
||||
cacheDb: cacheDbURL,
|
||||
dataDb: dataDbURL,
|
||||
networkType: network.networkType
|
||||
)
|
||||
|
||||
validationOperation.errorHandler = { error in
|
||||
self.operationQueue.cancelAllOperations()
|
||||
XCTFail("failed with error \(error)")
|
||||
}
|
||||
|
||||
validationOperation.completionHandler = { finished, cancelled in
|
||||
XCTAssert(finished)
|
||||
XCTAssertFalse(cancelled)
|
||||
validateExpectation.fulfill()
|
||||
}
|
||||
|
||||
let transactionRepository = TransactionRepositoryBuilder.build(dataDbURL: dataDbURL)
|
||||
let scanningOperation = CompactBlockBatchScanningOperation(
|
||||
rustWelding: rustWelding,
|
||||
cacheDb: cacheDbURL,
|
||||
dataDb: dataDbURL,
|
||||
transactionRepository: transactionRepository,
|
||||
range: CompactBlockRange(
|
||||
uncheckedBounds: (walletBirthDay.height, walletBirthDay.height + 10000)
|
||||
),
|
||||
batchSize: 1000,
|
||||
networkType: network.networkType,
|
||||
progressDelegate: self
|
||||
)
|
||||
|
||||
scanningOperation.completionHandler = { finished, cancelled in
|
||||
XCTAssert(finished)
|
||||
XCTAssertFalse(cancelled)
|
||||
scanExpectation.fulfill()
|
||||
}
|
||||
|
||||
operationQueue.addOperations([downloadOperation, validationOperation, scanningOperation], waitUntilFinished: false)
|
||||
|
||||
wait(for: [downloadExpectation, validateExpectation, scanExpectation], timeout: 300, enforceOrder: true)
|
||||
}
|
||||
}
|
||||
|
||||
extension BlockScanOperationTests: CompactBlockProgressDelegate {
|
||||
func progressUpdated(_ progress: CompactBlockProgress) {
|
||||
}
|
||||
}
|
||||
|
||||
struct UVFakeKey: UnifiedViewingKey {
|
||||
var extfvk: ExtendedFullViewingKey
|
||||
var extpub: ExtendedPublicKey
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
//
|
||||
// BlockScanTests.swift
|
||||
// ZcashLightClientKitTests
|
||||
//
|
||||
// Created by Francisco Gindre on 10/17/19.
|
||||
// Copyright © 2019 Electric Coin Company. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
import SQLite
|
||||
@testable import TestUtils
|
||||
@testable import ZcashLightClientKit
|
||||
|
||||
// swiftlint:disable implicitly_unwrapped_optional force_try force_unwrapping print_function_usage
|
||||
class BlockScanTests: XCTestCase {
|
||||
let rustWelding = ZcashRustBackend.self
|
||||
|
||||
var cacheDbURL: URL!
|
||||
var dataDbURL: URL!
|
||||
|
||||
var uvk = UVFakeKey(
|
||||
extfvk: "zxviewtestsapling1qw88ayg8qqqqpqyhg7jnh9mlldejfqwu46pm40ruwstd8znq3v3l4hjf33qcu2a5e36katshcfhcxhzgyfugj2lkhmt40j45cv38rv3frnghzkxcx73k7m7afw9j7ujk7nm4dx5mv02r26umxqgar7v3x390w2h3crqqgjsjly7jy4vtwzrmustm5yudpgcydw7x78awca8wqjvkqj8p8e3ykt7lrgd7xf92fsfqjs5vegfsja4ekzpfh5vtccgvs5747xqm6qflmtqpr8s9u", // swiftlint:disable:this line_length
|
||||
extpub: "02075a7f5f7507d64022dad5954849f216b0f1b09b2d588be663d8e7faeb5aaf61"
|
||||
)
|
||||
|
||||
var walletBirthDay = Checkpoint.birthday(
|
||||
with: 1386000,
|
||||
network: ZcashNetworkBuilder.network(for: .testnet)
|
||||
)
|
||||
|
||||
var network = ZcashNetworkBuilder.network(for: .testnet)
|
||||
var blockRepository: BlockRepository!
|
||||
|
||||
override func setUp() {
|
||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||
super.setUp()
|
||||
self.cacheDbURL = try! __cacheDbURL()
|
||||
self.dataDbURL = try! __dataDbURL()
|
||||
|
||||
deleteDBs()
|
||||
}
|
||||
|
||||
private func deleteDBs() {
|
||||
try? FileManager.default.removeItem(at: cacheDbURL)
|
||||
try? FileManager.default.removeItem(at: dataDbURL)
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||
super.tearDown()
|
||||
|
||||
try? FileManager.default.removeItem(at: cacheDbURL)
|
||||
try? FileManager.default.removeItem(at: dataDbURL)
|
||||
}
|
||||
|
||||
func testSingleDownloadAndScan() async throws {
|
||||
logger = SampleLogger(logLevel: .debug)
|
||||
|
||||
XCTAssertNoThrow(try rustWelding.initDataDb(dbData: dataDbURL, networkType: network.networkType))
|
||||
|
||||
let storage = try! TestDbBuilder.inMemoryCompactBlockStorage()
|
||||
let service = LightWalletGRPCService(
|
||||
endpoint: LightWalletEndpoint(
|
||||
address: "lightwalletd.testnet.electriccoin.co",
|
||||
port: 9067
|
||||
)
|
||||
)
|
||||
let blockCount = 100
|
||||
let range = network.constants.saplingActivationHeight ... network.constants.saplingActivationHeight + blockCount
|
||||
let downloader = CompactBlockDownloader.sqlDownloader(
|
||||
service: service,
|
||||
at: cacheDbURL
|
||||
)!
|
||||
|
||||
let processorConfig = CompactBlockProcessor.Configuration.standard(
|
||||
for: network,
|
||||
walletBirthday: network.constants.saplingActivationHeight
|
||||
)
|
||||
let compactBlockProcessor = CompactBlockProcessor(
|
||||
service: service,
|
||||
storage: storage,
|
||||
backend: ZcashRustBackend.self,
|
||||
config: processorConfig
|
||||
)
|
||||
|
||||
let repository = BlockSQLDAO(dbProvider: SimpleConnectionProvider.init(path: self.dataDbURL.absoluteString, readonly: true))
|
||||
var latestScannedheight = BlockHeight.empty()
|
||||
do {
|
||||
try await compactBlockProcessor.compactBlockDownload(
|
||||
downloader: downloader,
|
||||
range: range
|
||||
)
|
||||
XCTAssertFalse(Task.isCancelled)
|
||||
try await compactBlockProcessor.compactBlockScanning(
|
||||
rustWelding: rustWelding,
|
||||
cacheDb: cacheDbURL,
|
||||
dataDb: dataDbURL,
|
||||
networkType: network.networkType
|
||||
)
|
||||
latestScannedheight = repository.lastScannedBlockHeight()
|
||||
XCTAssertEqual(latestScannedheight, range.upperBound)
|
||||
} catch {
|
||||
XCTFail("Download failed with error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
@objc func observeBenchmark(_ notification: Notification) {
|
||||
guard let report = SDKMetrics.blockReportFromNotification(notification) else {
|
||||
return
|
||||
}
|
||||
|
||||
print("observed benchmark: \(report)")
|
||||
}
|
||||
|
||||
func testScanValidateDownload() async throws {
|
||||
logger = SampleLogger(logLevel: .debug)
|
||||
|
||||
NotificationCenter.default.addObserver(
|
||||
self,
|
||||
selector: #selector(observeBenchmark(_:)),
|
||||
name: SDKMetrics.notificationName,
|
||||
object: nil
|
||||
)
|
||||
|
||||
try self.rustWelding.initDataDb(dbData: dataDbURL, networkType: network.networkType)
|
||||
|
||||
guard try self.rustWelding.initAccountsTable(dbData: self.dataDbURL, uvks: [uvk], networkType: network.networkType) else {
|
||||
XCTFail("failed to init account table")
|
||||
return
|
||||
}
|
||||
|
||||
try self.rustWelding.initBlocksTable(
|
||||
dbData: dataDbURL,
|
||||
height: Int32(walletBirthDay.height),
|
||||
hash: walletBirthDay.hash,
|
||||
time: walletBirthDay.time,
|
||||
saplingTree: walletBirthDay.saplingTree,
|
||||
networkType: network.networkType
|
||||
)
|
||||
|
||||
let service = LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.eccTestnet)
|
||||
let storage = CompactBlockStorage(url: cacheDbURL, readonly: false)
|
||||
try storage.createTable()
|
||||
|
||||
var processorConfig = CompactBlockProcessor.Configuration(
|
||||
cacheDb: cacheDbURL,
|
||||
dataDb: dataDbURL,
|
||||
walletBirthday: network.constants.saplingActivationHeight,
|
||||
network: network
|
||||
)
|
||||
processorConfig.scanningBatchSize = 1000
|
||||
|
||||
let compactBlockProcessor = CompactBlockProcessor(
|
||||
service: service,
|
||||
storage: storage,
|
||||
backend: rustWelding,
|
||||
config: processorConfig
|
||||
)
|
||||
|
||||
let range = CompactBlockRange(
|
||||
uncheckedBounds: (walletBirthDay.height, walletBirthDay.height + 10000)
|
||||
)
|
||||
|
||||
do {
|
||||
try await compactBlockProcessor.compactBlockStreamDownload(
|
||||
blockBufferSize: 10,
|
||||
startHeight: range.lowerBound,
|
||||
targetHeight: range.upperBound
|
||||
)
|
||||
XCTAssertFalse(Task.isCancelled)
|
||||
|
||||
try await compactBlockProcessor.compactBlockValidation()
|
||||
XCTAssertFalse(Task.isCancelled)
|
||||
|
||||
try await compactBlockProcessor.compactBlockBatchScanning(range: range)
|
||||
XCTAssertFalse(Task.isCancelled)
|
||||
} catch {
|
||||
if let lwdError = error as? LightWalletServiceError {
|
||||
switch lwdError {
|
||||
case .timeOut:
|
||||
XCTAssert(true)
|
||||
default:
|
||||
XCTFail("LWD Service error found, but should have been a timeLimit reached Error - \(lwdError)")
|
||||
}
|
||||
} else {
|
||||
XCTFail("Error should have been a timeLimit reached Error - \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct UVFakeKey: UnifiedViewingKey {
|
||||
var extfvk: ExtendedFullViewingKey
|
||||
var extpub: ExtendedPublicKey
|
||||
}
|
|
@ -11,12 +11,6 @@ import XCTest
|
|||
|
||||
// swiftlint:disable print_function_usage
|
||||
class BlockStreamingTest: XCTestCase {
|
||||
var queue: OperationQueue = {
|
||||
let queue = OperationQueue()
|
||||
queue.maxConcurrentOperationCount = 1
|
||||
return queue
|
||||
}()
|
||||
|
||||
override func setUpWithError() throws {
|
||||
try super.setUpWithError()
|
||||
logger = SampleLogger(logLevel: .debug)
|
||||
|
@ -27,9 +21,7 @@ class BlockStreamingTest: XCTestCase {
|
|||
try? FileManager.default.removeItem(at: __dataDbURL())
|
||||
}
|
||||
|
||||
func testStreamOperation() throws {
|
||||
let expectation = XCTestExpectation(description: "blockstream expectation")
|
||||
|
||||
func testStream() async throws {
|
||||
let service = LightWalletGRPCService(
|
||||
host: LightWalletEndpointBuilder.eccTestnet.host,
|
||||
port: 9067,
|
||||
|
@ -42,28 +34,22 @@ class BlockStreamingTest: XCTestCase {
|
|||
|
||||
let startHeight = latestHeight - 100_000
|
||||
var blocks: [ZcashCompactBlock] = []
|
||||
service.blockStream(startHeight: startHeight, endHeight: latestHeight) { result in
|
||||
expectation.fulfill()
|
||||
switch result {
|
||||
case .success(let status):
|
||||
XCTAssertEqual(GRPCResult.success, status)
|
||||
case .failure(let error):
|
||||
XCTFail("failed with error: \(error)")
|
||||
let stream = service.blockStream(startHeight: startHeight, endHeight: latestHeight)
|
||||
|
||||
do {
|
||||
for try await compactBlock in stream {
|
||||
print("received block \(compactBlock.height)")
|
||||
blocks.append(compactBlock)
|
||||
print("progressHeight: \(compactBlock.height)")
|
||||
print("startHeight: \(startHeight)")
|
||||
print("targetHeight: \(latestHeight)")
|
||||
}
|
||||
} handler: { compactBlock in
|
||||
print("received block \(compactBlock.height)")
|
||||
blocks.append(compactBlock)
|
||||
} progress: { progressReport in
|
||||
print("progressHeight: \(progressReport.progressHeight)")
|
||||
print("startHeight: \(progressReport.startHeight)")
|
||||
print("targetHeight: \(progressReport.targetHeight)")
|
||||
} catch {
|
||||
XCTFail("failed with error: \(error)")
|
||||
}
|
||||
wait(for: [expectation], timeout: 1000)
|
||||
}
|
||||
|
||||
func testStreamOperationCancellation() throws {
|
||||
let expectation = XCTestExpectation(description: "blockstream expectation")
|
||||
|
||||
func testStreamCancellation() async throws {
|
||||
let service = LightWalletGRPCService(
|
||||
host: LightWalletEndpointBuilder.eccTestnet.host,
|
||||
port: 9067,
|
||||
|
@ -71,37 +57,37 @@ class BlockStreamingTest: XCTestCase {
|
|||
singleCallTimeout: 10000,
|
||||
streamingCallTimeout: 10000
|
||||
)
|
||||
|
||||
let storage = try TestDbBuilder.inMemoryCompactBlockStorage()
|
||||
|
||||
let startHeight = try service.latestBlockHeight() - 100_000
|
||||
let operation = CompactBlockStreamDownloadOperation(
|
||||
let processorConfig = CompactBlockProcessor.Configuration.standard(
|
||||
for: ZcashNetworkBuilder.network(for: .testnet),
|
||||
walletBirthday: ZcashNetworkBuilder.network(for: .testnet).constants.saplingActivationHeight
|
||||
)
|
||||
|
||||
let compactBlockProcessor = CompactBlockProcessor(
|
||||
service: service,
|
||||
storage: storage,
|
||||
blockBufferSize: 10,
|
||||
startHeight: startHeight,
|
||||
progressDelegate: self
|
||||
backend: ZcashRustBackend.self,
|
||||
config: processorConfig
|
||||
)
|
||||
|
||||
operation.completionHandler = { _, cancelled in
|
||||
XCTAssert(cancelled)
|
||||
expectation.fulfill()
|
||||
let cancelableTask = Task {
|
||||
do {
|
||||
try await compactBlockProcessor.compactBlockStreamDownload(
|
||||
blockBufferSize: 10,
|
||||
startHeight: startHeight
|
||||
)
|
||||
} catch {
|
||||
XCTAssertTrue(Task.isCancelled)
|
||||
}
|
||||
}
|
||||
|
||||
operation.errorHandler = { error in
|
||||
XCTFail("failed with error: \(error)")
|
||||
expectation.fulfill()
|
||||
}
|
||||
|
||||
queue.addOperation(operation)
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 3, execute: {
|
||||
self.queue.cancelAllOperations()
|
||||
})
|
||||
wait(for: [expectation], timeout: 1000)
|
||||
try await Task.sleep(nanoseconds: 3_000_000_000)
|
||||
cancelableTask.cancel()
|
||||
}
|
||||
|
||||
func testStreamOperationTimeout() throws {
|
||||
let expectation = XCTestExpectation(description: "blockstream expectation")
|
||||
let errorExpectation = XCTestExpectation(description: "blockstream error expectation")
|
||||
func testStreamTimeout() async throws {
|
||||
let service = LightWalletGRPCService(
|
||||
host: LightWalletEndpointBuilder.eccTestnet.host,
|
||||
port: 9067,
|
||||
|
@ -109,49 +95,49 @@ class BlockStreamingTest: XCTestCase {
|
|||
singleCallTimeout: 1000,
|
||||
streamingCallTimeout: 3000
|
||||
)
|
||||
|
||||
let storage = try TestDbBuilder.inMemoryCompactBlockStorage()
|
||||
|
||||
let startHeight = try service.latestBlockHeight() - 100_000
|
||||
let operation = CompactBlockStreamDownloadOperation(
|
||||
|
||||
let processorConfig = CompactBlockProcessor.Configuration.standard(
|
||||
for: ZcashNetworkBuilder.network(for: .testnet),
|
||||
walletBirthday: ZcashNetworkBuilder.network(for: .testnet).constants.saplingActivationHeight
|
||||
)
|
||||
|
||||
let compactBlockProcessor = CompactBlockProcessor(
|
||||
service: service,
|
||||
storage: storage,
|
||||
blockBufferSize: 10,
|
||||
startHeight: startHeight,
|
||||
progressDelegate: self
|
||||
backend: ZcashRustBackend.self,
|
||||
config: processorConfig
|
||||
)
|
||||
|
||||
operation.completionHandler = { finished, _ in
|
||||
XCTAssert(finished)
|
||||
|
||||
expectation.fulfill()
|
||||
}
|
||||
let date = Date()
|
||||
|
||||
operation.errorHandler = { error in
|
||||
do {
|
||||
try await compactBlockProcessor.compactBlockStreamDownload(
|
||||
blockBufferSize: 10,
|
||||
startHeight: startHeight
|
||||
)
|
||||
} catch {
|
||||
if let lwdError = error as? LightWalletServiceError {
|
||||
switch lwdError {
|
||||
case .timeOut:
|
||||
XCTAssert(true)
|
||||
default:
|
||||
XCTFail("LWD Service erro found, but should have been a timeLimit reached Error")
|
||||
XCTFail("LWD Service error found, but should have been a timeLimit reached Error")
|
||||
}
|
||||
} else {
|
||||
XCTFail("Error should have been a timeLimit reached Error")
|
||||
}
|
||||
errorExpectation.fulfill()
|
||||
}
|
||||
|
||||
queue.addOperation(operation)
|
||||
let date = Date()
|
||||
wait(for: [errorExpectation], timeout: 4)
|
||||
let now = Date()
|
||||
|
||||
let elapsed = now.timeIntervalSince(date)
|
||||
print("took \(elapsed) seconds")
|
||||
}
|
||||
|
||||
func testBatchOperation() throws {
|
||||
let expectation = XCTestExpectation(description: "blockbatch expectation")
|
||||
|
||||
func testBatch() async throws {
|
||||
let service = LightWalletGRPCService(
|
||||
host: LightWalletEndpointBuilder.eccTestnet.host,
|
||||
port: 9067,
|
||||
|
@ -162,34 +148,29 @@ class BlockStreamingTest: XCTestCase {
|
|||
let storage = try TestDbBuilder.diskCompactBlockStorage(at: __dataDbURL() )
|
||||
let targetHeight = try service.latestBlockHeight()
|
||||
let startHeight = targetHeight - 10_000
|
||||
let operation = CompactBlockBatchDownloadOperation(
|
||||
|
||||
let processorConfig = CompactBlockProcessor.Configuration.standard(
|
||||
for: ZcashNetworkBuilder.network(for: .testnet),
|
||||
walletBirthday: ZcashNetworkBuilder.network(for: .testnet).constants.saplingActivationHeight
|
||||
)
|
||||
|
||||
let compactBlockProcessor = CompactBlockProcessor(
|
||||
service: service,
|
||||
storage: storage,
|
||||
startHeight: startHeight,
|
||||
targetHeight: targetHeight,
|
||||
progressDelegate: self
|
||||
backend: ZcashRustBackend.self,
|
||||
config: processorConfig
|
||||
)
|
||||
|
||||
operation.completionHandler = { _, cancelled in
|
||||
if cancelled {
|
||||
XCTFail("operation cancelled")
|
||||
}
|
||||
expectation.fulfill()
|
||||
}
|
||||
|
||||
operation.errorHandler = { error in
|
||||
let range = CompactBlockRange(uncheckedBounds: (startHeight, targetHeight))
|
||||
do {
|
||||
try await compactBlockProcessor.compactBlockBatchDownload(range: range)
|
||||
XCTAssertFalse(Task.isCancelled)
|
||||
} catch {
|
||||
XCTFail("failed with error: \(error)")
|
||||
expectation.fulfill()
|
||||
}
|
||||
|
||||
queue.addOperation(operation)
|
||||
|
||||
wait(for: [expectation], timeout: 120)
|
||||
}
|
||||
|
||||
func testBatchOperationCancellation() throws {
|
||||
let expectation = XCTestExpectation(description: "blockbatch expectation")
|
||||
|
||||
func testBatchCancellation() async throws {
|
||||
let service = LightWalletGRPCService(
|
||||
host: LightWalletEndpointBuilder.eccTestnet.host,
|
||||
port: 9067,
|
||||
|
@ -200,36 +181,30 @@ class BlockStreamingTest: XCTestCase {
|
|||
let storage = try TestDbBuilder.diskCompactBlockStorage(at: __dataDbURL() )
|
||||
let targetHeight = try service.latestBlockHeight()
|
||||
let startHeight = targetHeight - 100_000
|
||||
let operation = CompactBlockBatchDownloadOperation(
|
||||
|
||||
let processorConfig = CompactBlockProcessor.Configuration.standard(
|
||||
for: ZcashNetworkBuilder.network(for: .testnet),
|
||||
walletBirthday: ZcashNetworkBuilder.network(for: .testnet).constants.saplingActivationHeight
|
||||
)
|
||||
|
||||
let compactBlockProcessor = CompactBlockProcessor(
|
||||
service: service,
|
||||
storage: storage,
|
||||
startHeight: startHeight,
|
||||
targetHeight: targetHeight,
|
||||
progressDelegate: self
|
||||
backend: ZcashRustBackend.self,
|
||||
config: processorConfig
|
||||
)
|
||||
|
||||
operation.completionHandler = { _, cancelled in
|
||||
XCTAssert(cancelled)
|
||||
expectation.fulfill()
|
||||
let range = CompactBlockRange(uncheckedBounds: (startHeight, targetHeight))
|
||||
let cancelableTask = Task {
|
||||
do {
|
||||
try await compactBlockProcessor.compactBlockBatchDownload(range: range)
|
||||
XCTAssertTrue(Task.isCancelled)
|
||||
} catch {
|
||||
XCTFail("failed with error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
operation.errorHandler = { error in
|
||||
XCTFail("failed with error: \(error)")
|
||||
expectation.fulfill()
|
||||
}
|
||||
|
||||
queue.addOperation(operation)
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 3, execute: {
|
||||
self.queue.cancelAllOperations()
|
||||
})
|
||||
wait(for: [expectation], timeout: 1000)
|
||||
}
|
||||
}
|
||||
|
||||
extension BlockStreamingTest: CompactBlockProgressDelegate {
|
||||
func progressUpdated(_ progress: CompactBlockProgress) {
|
||||
print("progressHeight: \(String(describing: progress.progressHeight))")
|
||||
print("startHeight: \(progress.progress)")
|
||||
print("targetHeight: \(String(describing: progress.targetHeight))")
|
||||
try await Task.sleep(nanoseconds: 3_000_000_000)
|
||||
cancelableTask.cancel()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -96,7 +96,7 @@ class CompactBlockProcessorTests: XCTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
private func startProcessing() {
|
||||
private func startProcessing() async {
|
||||
XCTAssertNotNil(processor)
|
||||
|
||||
// Subscribe to notifications
|
||||
|
@ -107,11 +107,15 @@ class CompactBlockProcessorTests: XCTestCase {
|
|||
startedScanningNotificationExpectation.subscribe(to: Notification.Name.blockProcessorStartedScanning, object: processor)
|
||||
idleNotificationExpectation.subscribe(to: Notification.Name.blockProcessorFinished, object: processor)
|
||||
|
||||
XCTAssertNoThrow(try processor.start())
|
||||
do {
|
||||
try await processor.start()
|
||||
} catch {
|
||||
XCTFail("shouldn't fail")
|
||||
}
|
||||
}
|
||||
|
||||
func testStartNotifiesSuscriptors() {
|
||||
startProcessing()
|
||||
func testStartNotifiesSuscriptors() async {
|
||||
await startProcessing()
|
||||
|
||||
wait(
|
||||
for: [
|
||||
|
@ -125,7 +129,7 @@ class CompactBlockProcessorTests: XCTestCase {
|
|||
)
|
||||
}
|
||||
|
||||
func testProgressNotifications() {
|
||||
func testProgressNotifications() async {
|
||||
let expectedUpdates = expectedBatches(
|
||||
currentHeight: processorConfig.walletBirthday,
|
||||
targetHeight: mockLatestHeight,
|
||||
|
@ -133,7 +137,7 @@ class CompactBlockProcessorTests: XCTestCase {
|
|||
)
|
||||
updatedNotificationExpectation.expectedFulfillmentCount = expectedUpdates
|
||||
|
||||
startProcessing()
|
||||
await startProcessing()
|
||||
wait(for: [updatedNotificationExpectation], timeout: 300)
|
||||
}
|
||||
|
||||
|
@ -189,23 +193,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)
|
||||
|
|
|
@ -127,7 +127,7 @@ class CompactBlockReorgTests: XCTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
private func startProcessing() {
|
||||
private func startProcessing() async {
|
||||
XCTAssertNotNil(processor)
|
||||
|
||||
// Subscribe to notifications
|
||||
|
@ -139,11 +139,15 @@ class CompactBlockReorgTests: XCTestCase {
|
|||
idleNotificationExpectation.subscribe(to: Notification.Name.blockProcessorFinished, object: processor)
|
||||
reorgNotificationExpectation.subscribe(to: Notification.Name.blockProcessorHandledReOrg, object: processor)
|
||||
|
||||
XCTAssertNoThrow(try processor.start())
|
||||
do {
|
||||
try await processor.start()
|
||||
} catch {
|
||||
XCTFail("shouldn't fail")
|
||||
}
|
||||
}
|
||||
|
||||
func testNotifiesReorg() {
|
||||
startProcessing()
|
||||
func testNotifiesReorg() async {
|
||||
await startProcessing()
|
||||
|
||||
wait(
|
||||
for: [
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// DownloadOperationTests.swift
|
||||
// DownloadTests.swift
|
||||
// ZcashLightClientKitTests
|
||||
//
|
||||
// Created by Francisco Gindre on 10/16/19.
|
||||
|
@ -12,40 +12,38 @@ import SQLite
|
|||
@testable import ZcashLightClientKit
|
||||
|
||||
// swiftlint:disable force_try
|
||||
class DownloadOperationTests: XCTestCase {
|
||||
var operationQueue = OperationQueue()
|
||||
class DownloadTests: XCTestCase {
|
||||
var network = ZcashNetworkBuilder.network(for: .testnet)
|
||||
|
||||
override func tearDown() {
|
||||
super.tearDown()
|
||||
operationQueue.cancelAllOperations()
|
||||
}
|
||||
|
||||
func testSingleOperation() {
|
||||
let expect = XCTestExpectation(description: self.description)
|
||||
|
||||
func testSingleDownload() async throws {
|
||||
let service = LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.eccTestnet)
|
||||
let storage = try! TestDbBuilder.inMemoryCompactBlockStorage()
|
||||
let downloader = CompactBlockDownloader(service: service, storage: storage)
|
||||
let blockCount = 100
|
||||
let activationHeight = network.constants.saplingActivationHeight
|
||||
let range = activationHeight ... activationHeight + blockCount
|
||||
let downloadOperation = CompactBlockDownloadOperation(downloader: downloader, range: range)
|
||||
|
||||
downloadOperation.completionHandler = { finished, cancelled in
|
||||
expect.fulfill()
|
||||
XCTAssertTrue(finished)
|
||||
XCTAssertFalse(cancelled)
|
||||
let processorConfig = CompactBlockProcessor.Configuration.standard(
|
||||
for: network,
|
||||
walletBirthday: network.constants.saplingActivationHeight
|
||||
)
|
||||
let compactBlockProcessor = CompactBlockProcessor(
|
||||
service: service,
|
||||
storage: storage,
|
||||
backend: ZcashRustBackend.self,
|
||||
config: processorConfig
|
||||
)
|
||||
|
||||
do {
|
||||
try await compactBlockProcessor.compactBlockDownload(downloader: downloader, range: range)
|
||||
} catch {
|
||||
XCTFail("Download failed with error: \(error)")
|
||||
}
|
||||
|
||||
downloadOperation.errorHandler = { error in
|
||||
XCTFail("Donwload Operation failed with error: \(error)")
|
||||
}
|
||||
|
||||
operationQueue.addOperation(downloadOperation)
|
||||
|
||||
wait(for: [expect], timeout: 10)
|
||||
|
||||
XCTAssertEqual(try! storage.latestHeight(), range.upperBound)
|
||||
}
|
||||
}
|
|
@ -38,54 +38,35 @@ class LightWalletServiceTests: XCTestCase {
|
|||
// wait(for: [expect], timeout: 20)
|
||||
// }
|
||||
|
||||
func testHundredBlocks() {
|
||||
let expect = XCTestExpectation(description: self.description)
|
||||
func testHundredBlocks() async throws {
|
||||
let count = 99
|
||||
let lowerRange: BlockHeight = network.constants.saplingActivationHeight
|
||||
let upperRange: BlockHeight = network.constants.saplingActivationHeight + count
|
||||
let blockRange = lowerRange ... upperRange
|
||||
|
||||
service.blockRange(blockRange) { result in
|
||||
expect.fulfill()
|
||||
switch result {
|
||||
case .failure(let error):
|
||||
XCTFail("failed with error \(error)")
|
||||
|
||||
case .success(let blocks):
|
||||
XCTAssertEqual(blocks.count, blockRange.count)
|
||||
XCTAssertEqual(blocks[0].height, lowerRange)
|
||||
XCTAssertEqual(blocks.last!.height, upperRange)
|
||||
}
|
||||
var blocks: [ZcashCompactBlock] = []
|
||||
for try await block in service.blockRange(blockRange) {
|
||||
blocks.append(block)
|
||||
}
|
||||
|
||||
wait(for: [expect], timeout: 10)
|
||||
XCTAssertEqual(blocks.count, blockRange.count)
|
||||
XCTAssertEqual(blocks[0].height, lowerRange)
|
||||
XCTAssertEqual(blocks.last!.height, upperRange)
|
||||
}
|
||||
|
||||
func testSyncBlockRange() {
|
||||
func testSyncBlockRange() async throws {
|
||||
let lowerRange: BlockHeight = network.constants.saplingActivationHeight
|
||||
let upperRange: BlockHeight = network.constants.saplingActivationHeight + 99
|
||||
let blockRange = lowerRange ... upperRange
|
||||
|
||||
do {
|
||||
let blocks = try service.blockRange(blockRange)
|
||||
XCTAssertEqual(blocks.count, blockRange.count)
|
||||
} catch {
|
||||
XCTFail("\(error)")
|
||||
|
||||
var blocks: [ZcashCompactBlock] = []
|
||||
for try await block in service.blockRange(blockRange) {
|
||||
blocks.append(block)
|
||||
}
|
||||
XCTAssertEqual(blocks.count, blockRange.count)
|
||||
}
|
||||
|
||||
func testLatestBlock() {
|
||||
let expect = XCTestExpectation(description: self.description)
|
||||
service.latestBlockHeight { result in
|
||||
expect.fulfill()
|
||||
switch result {
|
||||
case .failure(let e):
|
||||
XCTFail("error: \(e)")
|
||||
case .success(let height):
|
||||
XCTAssertTrue(height > self.network.constants.saplingActivationHeight)
|
||||
}
|
||||
}
|
||||
|
||||
wait(for: [expect], timeout: 10)
|
||||
func testLatestBlock() async throws {
|
||||
let height = try await service.latestBlockHeightAsync()
|
||||
XCTAssertTrue(height > self.network.constants.saplingActivationHeight)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,29 +11,13 @@ import XCTest
|
|||
|
||||
// swiftlint:disable force_try type_body_length
|
||||
class BlockBatchValidationTests: XCTestCase {
|
||||
var queue: OperationQueue = {
|
||||
let queue = OperationQueue()
|
||||
queue.name = "Test Queue"
|
||||
queue.maxConcurrentOperationCount = 1
|
||||
return queue
|
||||
}()
|
||||
|
||||
override func setUp() {
|
||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||
super.setUp()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func testBranchIdFailure() throws {
|
||||
func testBranchIdFailure() async throws {
|
||||
let network = ZcashNetworkBuilder.network(for: .mainnet)
|
||||
let service = MockLightWalletService(
|
||||
latestBlockHeight: 1210000,
|
||||
service: LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.default)
|
||||
)
|
||||
let storage = try! TestDbBuilder.inMemoryCompactBlockStorage()
|
||||
let repository = ZcashConsoleFakeStorage(latestBlockHeight: 1220000)
|
||||
let downloader = CompactBlockDownloader(service: service, storage: repository)
|
||||
let config = CompactBlockProcessor.Configuration(
|
||||
|
@ -59,17 +43,18 @@ class BlockBatchValidationTests: XCTestCase {
|
|||
|
||||
let mockRust = MockRustBackend.self
|
||||
mockRust.consensusBranchID = Int32(0xd34d)
|
||||
|
||||
let operation = FigureNextBatchOperation(downloader: downloader, service: service, config: config, rustBackend: mockRust)
|
||||
let expectation = XCTestExpectation(description: "failure expectation")
|
||||
let startedExpectation = XCTestExpectation(description: "start Expectation")
|
||||
|
||||
operation.startedHandler = {
|
||||
startedExpectation.fulfill()
|
||||
}
|
||||
|
||||
operation.errorHandler = { error in
|
||||
expectation.fulfill()
|
||||
|
||||
let compactBlockProcessor = CompactBlockProcessor(
|
||||
service: service,
|
||||
storage: storage,
|
||||
backend: mockRust,
|
||||
config: config
|
||||
)
|
||||
|
||||
do {
|
||||
try await compactBlockProcessor.figureNextBatch(downloader: downloader)
|
||||
XCTAssertFalse(Task.isCancelled)
|
||||
} catch {
|
||||
switch error {
|
||||
case CompactBlockProcessorError.wrongConsensusBranchId:
|
||||
break
|
||||
|
@ -77,19 +62,15 @@ class BlockBatchValidationTests: XCTestCase {
|
|||
XCTFail("Expected CompactBlockProcessorError.wrongConsensusBranchId but found \(error)")
|
||||
}
|
||||
}
|
||||
queue.addOperations([operation], waitUntilFinished: false)
|
||||
|
||||
wait(for: [startedExpectation, expectation], timeout: 1, enforceOrder: true)
|
||||
XCTAssertNotNil(operation.error)
|
||||
XCTAssertTrue(operation.isCancelled)
|
||||
}
|
||||
|
||||
func testBranchNetworkMismatchFailure() throws {
|
||||
func testBranchNetworkMismatchFailure() async throws {
|
||||
let network = ZcashNetworkBuilder.network(for: .mainnet)
|
||||
let service = MockLightWalletService(
|
||||
latestBlockHeight: 1210000,
|
||||
service: LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.default)
|
||||
)
|
||||
let storage = try! TestDbBuilder.inMemoryCompactBlockStorage()
|
||||
let repository = ZcashConsoleFakeStorage(latestBlockHeight: 1220000)
|
||||
let downloader = CompactBlockDownloader(service: service, storage: repository)
|
||||
let config = CompactBlockProcessor.Configuration(
|
||||
|
@ -115,17 +96,18 @@ class BlockBatchValidationTests: XCTestCase {
|
|||
|
||||
let mockRust = MockRustBackend.self
|
||||
mockRust.consensusBranchID = 0xd34db4d
|
||||
|
||||
let operation = FigureNextBatchOperation(downloader: downloader, service: service, config: config, rustBackend: mockRust)
|
||||
let expectation = XCTestExpectation(description: "failure expectation")
|
||||
let startedExpectation = XCTestExpectation(description: "start Expectation")
|
||||
|
||||
operation.startedHandler = {
|
||||
startedExpectation.fulfill()
|
||||
}
|
||||
|
||||
operation.errorHandler = { error in
|
||||
expectation.fulfill()
|
||||
|
||||
let compactBlockProcessor = CompactBlockProcessor(
|
||||
service: service,
|
||||
storage: storage,
|
||||
backend: mockRust,
|
||||
config: config
|
||||
)
|
||||
|
||||
do {
|
||||
try await compactBlockProcessor.figureNextBatch(downloader: downloader)
|
||||
XCTAssertFalse(Task.isCancelled)
|
||||
} catch {
|
||||
switch error {
|
||||
case CompactBlockProcessorError.networkMismatch(expected: .mainnet, found: .testnet):
|
||||
break
|
||||
|
@ -133,20 +115,15 @@ class BlockBatchValidationTests: XCTestCase {
|
|||
XCTFail("Expected CompactBlockProcessorError.networkMismatch but found \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
queue.addOperations([operation], waitUntilFinished: false)
|
||||
|
||||
wait(for: [startedExpectation, expectation], timeout: 1, enforceOrder: true)
|
||||
XCTAssertNotNil(operation.error)
|
||||
XCTAssertTrue(operation.isCancelled)
|
||||
}
|
||||
|
||||
func testBranchNetworkTypeWrongFailure() throws {
|
||||
func testBranchNetworkTypeWrongFailure() async throws {
|
||||
let network = ZcashNetworkBuilder.network(for: .testnet)
|
||||
let service = MockLightWalletService(
|
||||
latestBlockHeight: 1210000,
|
||||
service: LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.default)
|
||||
)
|
||||
let storage = try! TestDbBuilder.inMemoryCompactBlockStorage()
|
||||
let repository = ZcashConsoleFakeStorage(latestBlockHeight: 1220000)
|
||||
let downloader = CompactBlockDownloader(service: service, storage: repository)
|
||||
let config = CompactBlockProcessor.Configuration(
|
||||
|
@ -172,17 +149,18 @@ class BlockBatchValidationTests: XCTestCase {
|
|||
|
||||
let mockRust = MockRustBackend.self
|
||||
mockRust.consensusBranchID = 0xd34db4d
|
||||
|
||||
let operation = FigureNextBatchOperation(downloader: downloader, service: service, config: config, rustBackend: mockRust)
|
||||
let expectation = XCTestExpectation(description: "failure expectation")
|
||||
let startedExpectation = XCTestExpectation(description: "start Expectation")
|
||||
|
||||
operation.startedHandler = {
|
||||
startedExpectation.fulfill()
|
||||
}
|
||||
|
||||
operation.errorHandler = { error in
|
||||
expectation.fulfill()
|
||||
|
||||
let compactBlockProcessor = CompactBlockProcessor(
|
||||
service: service,
|
||||
storage: storage,
|
||||
backend: mockRust,
|
||||
config: config
|
||||
)
|
||||
|
||||
do {
|
||||
try await compactBlockProcessor.figureNextBatch(downloader: downloader)
|
||||
XCTAssertFalse(Task.isCancelled)
|
||||
} catch {
|
||||
switch error {
|
||||
case CompactBlockProcessorError.generalError:
|
||||
break
|
||||
|
@ -190,20 +168,15 @@ class BlockBatchValidationTests: XCTestCase {
|
|||
XCTFail("Expected CompactBlockProcessorError.generalError but found \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
queue.addOperations([operation], waitUntilFinished: false)
|
||||
|
||||
wait(for: [startedExpectation, expectation], timeout: 1, enforceOrder: true)
|
||||
XCTAssertNotNil(operation.error)
|
||||
XCTAssertTrue(operation.isCancelled)
|
||||
}
|
||||
|
||||
func testSaplingActivationHeightMismatch() throws {
|
||||
func testSaplingActivationHeightMismatch() async throws {
|
||||
let network = ZcashNetworkBuilder.network(for: .mainnet)
|
||||
let service = MockLightWalletService(
|
||||
latestBlockHeight: 1210000,
|
||||
service: LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.default)
|
||||
)
|
||||
let storage = try! TestDbBuilder.inMemoryCompactBlockStorage()
|
||||
let repository = ZcashConsoleFakeStorage(latestBlockHeight: 1220000)
|
||||
let downloader = CompactBlockDownloader(service: service, storage: repository)
|
||||
let config = CompactBlockProcessor.Configuration(
|
||||
|
@ -230,17 +203,18 @@ class BlockBatchValidationTests: XCTestCase {
|
|||
|
||||
let mockRust = MockRustBackend.self
|
||||
mockRust.consensusBranchID = 0xd34db4d
|
||||
|
||||
let operation = FigureNextBatchOperation(downloader: downloader, service: service, config: config, rustBackend: mockRust)
|
||||
let expectation = XCTestExpectation(description: "failure expectation")
|
||||
let startedExpectation = XCTestExpectation(description: "start Expectation")
|
||||
|
||||
operation.startedHandler = {
|
||||
startedExpectation.fulfill()
|
||||
}
|
||||
|
||||
operation.errorHandler = { error in
|
||||
expectation.fulfill()
|
||||
|
||||
let compactBlockProcessor = CompactBlockProcessor(
|
||||
service: service,
|
||||
storage: storage,
|
||||
backend: mockRust,
|
||||
config: config
|
||||
)
|
||||
|
||||
do {
|
||||
try await compactBlockProcessor.figureNextBatch(downloader: downloader)
|
||||
XCTAssertFalse(Task.isCancelled)
|
||||
} catch {
|
||||
switch error {
|
||||
case CompactBlockProcessorError.saplingActivationMismatch(
|
||||
expected: network.constants.saplingActivationHeight,
|
||||
|
@ -251,15 +225,9 @@ class BlockBatchValidationTests: XCTestCase {
|
|||
XCTFail("Expected CompactBlockProcessorError.saplingActivationMismatch but found \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
queue.addOperations([operation], waitUntilFinished: false)
|
||||
|
||||
wait(for: [startedExpectation, expectation], timeout: 1, enforceOrder: true)
|
||||
XCTAssertNotNil(operation.error)
|
||||
XCTAssertTrue(operation.isCancelled)
|
||||
}
|
||||
|
||||
func testResultIsWait() throws {
|
||||
func testResultIsWait() async throws {
|
||||
let network = ZcashNetworkBuilder.network(for: .mainnet)
|
||||
|
||||
let expectedLatestHeight = BlockHeight(1210000)
|
||||
|
@ -268,10 +236,11 @@ class BlockBatchValidationTests: XCTestCase {
|
|||
service: LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.default)
|
||||
)
|
||||
let expectedStoreLatestHeight = BlockHeight(1220000)
|
||||
let expectedResult = FigureNextBatchOperation.NextState.wait(
|
||||
let expectedResult = CompactBlockProcessor.NextState.wait(
|
||||
latestHeight: expectedLatestHeight,
|
||||
latestDownloadHeight: expectedLatestHeight
|
||||
)
|
||||
let storage = try! TestDbBuilder.inMemoryCompactBlockStorage()
|
||||
let repository = ZcashConsoleFakeStorage(latestBlockHeight: expectedStoreLatestHeight)
|
||||
let downloader = CompactBlockDownloader(service: service, storage: repository)
|
||||
let config = CompactBlockProcessor.Configuration(
|
||||
|
@ -298,50 +267,41 @@ class BlockBatchValidationTests: XCTestCase {
|
|||
|
||||
let mockRust = MockRustBackend.self
|
||||
mockRust.consensusBranchID = 0xd34db4d
|
||||
|
||||
let operation = FigureNextBatchOperation(downloader: downloader, service: service, config: config, rustBackend: mockRust)
|
||||
let completedExpectation = XCTestExpectation(description: "completed expectation")
|
||||
let startedExpectation = XCTestExpectation(description: "start Expectation")
|
||||
|
||||
operation.startedHandler = {
|
||||
startedExpectation.fulfill()
|
||||
}
|
||||
|
||||
operation.errorHandler = { error in
|
||||
|
||||
let compactBlockProcessor = CompactBlockProcessor(
|
||||
service: service,
|
||||
storage: storage,
|
||||
backend: mockRust,
|
||||
config: config
|
||||
)
|
||||
|
||||
var nextBatch: CompactBlockProcessor.NextState?
|
||||
do {
|
||||
nextBatch = try await compactBlockProcessor.figureNextBatch(downloader: downloader)
|
||||
XCTAssertFalse(Task.isCancelled)
|
||||
} catch {
|
||||
XCTFail("this shouldn't happen: \(error)")
|
||||
}
|
||||
|
||||
operation.completionHandler = { finished, cancelled in
|
||||
completedExpectation.fulfill()
|
||||
XCTAssertTrue(finished)
|
||||
XCTAssertFalse(cancelled)
|
||||
}
|
||||
|
||||
queue.addOperations([operation], waitUntilFinished: false)
|
||||
|
||||
wait(for: [startedExpectation, completedExpectation], timeout: 1, enforceOrder: true)
|
||||
XCTAssertNil(operation.error)
|
||||
XCTAssertFalse(operation.isCancelled)
|
||||
|
||||
guard let result = operation.result else {
|
||||
guard let _ = nextBatch else {
|
||||
XCTFail("result should not be nil")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertTrue(
|
||||
{
|
||||
switch result {
|
||||
switch nextBatch {
|
||||
case .wait(latestHeight: expectedLatestHeight, latestDownloadHeight: expectedLatestHeight):
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}(),
|
||||
"Expected \(expectedResult) got: \(result)"
|
||||
"Expected \(expectedResult) got: \(String(describing: nextBatch))"
|
||||
)
|
||||
}
|
||||
|
||||
func testResultProcessNew() throws {
|
||||
func testResultProcessNew() async throws {
|
||||
let network = ZcashNetworkBuilder.network(for: .mainnet)
|
||||
let expectedLatestHeight = BlockHeight(1230000)
|
||||
let service = MockLightWalletService(
|
||||
|
@ -350,13 +310,14 @@ class BlockBatchValidationTests: XCTestCase {
|
|||
)
|
||||
let expectedStoreLatestHeight = BlockHeight(1220000)
|
||||
let walletBirthday = BlockHeight(1210000)
|
||||
let expectedResult = FigureNextBatchOperation.NextState.processNewBlocks(
|
||||
let expectedResult = CompactBlockProcessor.NextState.processNewBlocks(
|
||||
range: CompactBlockProcessor.nextBatchBlockRange(
|
||||
latestHeight: expectedLatestHeight,
|
||||
latestDownloadedHeight: expectedStoreLatestHeight,
|
||||
walletBirthday: walletBirthday
|
||||
)
|
||||
)
|
||||
let storage = try! TestDbBuilder.inMemoryCompactBlockStorage()
|
||||
let repository = ZcashConsoleFakeStorage(latestBlockHeight: expectedStoreLatestHeight)
|
||||
let downloader = CompactBlockDownloader(service: service, storage: repository)
|
||||
let config = CompactBlockProcessor.Configuration(
|
||||
|
@ -383,50 +344,41 @@ class BlockBatchValidationTests: XCTestCase {
|
|||
|
||||
let mockRust = MockRustBackend.self
|
||||
mockRust.consensusBranchID = 0xd34db4d
|
||||
|
||||
let operation = FigureNextBatchOperation(downloader: downloader, service: service, config: config, rustBackend: mockRust)
|
||||
let completedExpectation = XCTestExpectation(description: "completed expectation")
|
||||
let startedExpectation = XCTestExpectation(description: "start Expectation")
|
||||
|
||||
operation.startedHandler = {
|
||||
startedExpectation.fulfill()
|
||||
}
|
||||
|
||||
operation.errorHandler = { _ in
|
||||
XCTFail("this shouldn't happen")
|
||||
}
|
||||
|
||||
operation.completionHandler = { finished, cancelled in
|
||||
completedExpectation.fulfill()
|
||||
XCTAssertTrue(finished)
|
||||
XCTAssertFalse(cancelled)
|
||||
}
|
||||
|
||||
queue.addOperations([operation], waitUntilFinished: false)
|
||||
|
||||
wait(for: [startedExpectation, completedExpectation], timeout: 1, enforceOrder: true)
|
||||
XCTAssertNil(operation.error)
|
||||
XCTAssertFalse(operation.isCancelled)
|
||||
|
||||
guard let result = operation.result else {
|
||||
let compactBlockProcessor = CompactBlockProcessor(
|
||||
service: service,
|
||||
storage: storage,
|
||||
backend: mockRust,
|
||||
config: config
|
||||
)
|
||||
|
||||
var nextBatch: CompactBlockProcessor.NextState?
|
||||
do {
|
||||
nextBatch = try await compactBlockProcessor.figureNextBatch(downloader: downloader)
|
||||
XCTAssertFalse(Task.isCancelled)
|
||||
} catch {
|
||||
XCTFail("this shouldn't happen: \(error)")
|
||||
}
|
||||
|
||||
guard let _ = nextBatch else {
|
||||
XCTFail("result should not be nil")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertTrue(
|
||||
{
|
||||
switch result {
|
||||
switch nextBatch {
|
||||
case .processNewBlocks(range: CompactBlockRange(uncheckedBounds: (expectedStoreLatestHeight + 1, expectedLatestHeight))):
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}(),
|
||||
"Expected \(expectedResult) got: \(result)"
|
||||
"Expected \(expectedResult) got: \(String(describing: nextBatch))"
|
||||
)
|
||||
}
|
||||
|
||||
func testResultProcessorFinished() throws {
|
||||
func testResultProcessorFinished() async throws {
|
||||
let network = ZcashNetworkBuilder.network(for: .mainnet)
|
||||
let expectedLatestHeight = BlockHeight(1230000)
|
||||
let service = MockLightWalletService(
|
||||
|
@ -435,7 +387,8 @@ class BlockBatchValidationTests: XCTestCase {
|
|||
)
|
||||
let expectedStoreLatestHeight = BlockHeight(1230000)
|
||||
let walletBirthday = BlockHeight(1210000)
|
||||
let expectedResult = FigureNextBatchOperation.NextState.finishProcessing(height: expectedStoreLatestHeight)
|
||||
let expectedResult = CompactBlockProcessor.NextState.finishProcessing(height: expectedStoreLatestHeight)
|
||||
let storage = try! TestDbBuilder.inMemoryCompactBlockStorage()
|
||||
let repository = ZcashConsoleFakeStorage(latestBlockHeight: expectedStoreLatestHeight)
|
||||
let downloader = CompactBlockDownloader(service: service, storage: repository)
|
||||
let config = CompactBlockProcessor.Configuration(
|
||||
|
@ -462,46 +415,38 @@ class BlockBatchValidationTests: XCTestCase {
|
|||
|
||||
let mockRust = MockRustBackend.self
|
||||
mockRust.consensusBranchID = 0xd34db4d
|
||||
|
||||
let operation = FigureNextBatchOperation(downloader: downloader, service: service, config: config, rustBackend: mockRust)
|
||||
let completedExpectation = XCTestExpectation(description: "completed expectation")
|
||||
let startedExpectation = XCTestExpectation(description: "start Expectation")
|
||||
|
||||
operation.startedHandler = {
|
||||
startedExpectation.fulfill()
|
||||
}
|
||||
|
||||
operation.errorHandler = { _ in
|
||||
XCTFail("this shouldn't happen")
|
||||
}
|
||||
|
||||
operation.completionHandler = { finished, cancelled in
|
||||
completedExpectation.fulfill()
|
||||
XCTAssertTrue(finished)
|
||||
XCTAssertFalse(cancelled)
|
||||
}
|
||||
|
||||
queue.addOperations([operation], waitUntilFinished: false)
|
||||
|
||||
wait(for: [startedExpectation, completedExpectation], timeout: 1, enforceOrder: true)
|
||||
XCTAssertNil(operation.error)
|
||||
XCTAssertFalse(operation.isCancelled)
|
||||
|
||||
guard let result = operation.result else {
|
||||
let compactBlockProcessor = CompactBlockProcessor(
|
||||
service: service,
|
||||
storage: storage,
|
||||
backend: mockRust,
|
||||
config: config
|
||||
)
|
||||
|
||||
var nextBatch: CompactBlockProcessor.NextState?
|
||||
do {
|
||||
nextBatch = try await compactBlockProcessor.figureNextBatch(downloader: downloader)
|
||||
XCTAssertFalse(Task.isCancelled)
|
||||
} catch {
|
||||
XCTFail("this shouldn't happen: \(error)")
|
||||
}
|
||||
|
||||
guard let _ = nextBatch else {
|
||||
XCTFail("result should not be nil")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertTrue(
|
||||
{
|
||||
switch result {
|
||||
|
||||
switch nextBatch {
|
||||
case .finishProcessing(height: expectedLatestHeight):
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}(),
|
||||
"Expected \(expectedResult) got: \(result)"
|
||||
"Expected \(expectedResult) got: \(String(describing: nextBatch))"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,15 +19,20 @@ class CompactBlockStorageTests: XCTestCase {
|
|||
func testEmptyStorage() {
|
||||
XCTAssertEqual(try! compactBlockDao.latestHeight(), BlockHeight.empty())
|
||||
}
|
||||
|
||||
func testStoreThousandBlocks() {
|
||||
|
||||
func testEmptyStorageAsync() async throws {
|
||||
let latestHeight = try await compactBlockDao.latestHeightAsync()
|
||||
XCTAssertEqual(latestHeight, BlockHeight.empty())
|
||||
}
|
||||
|
||||
func testStoreThousandBlocks() async {
|
||||
let initialHeight = try! compactBlockDao.latestHeight()
|
||||
let startHeight = self.network.constants.saplingActivationHeight
|
||||
let blockCount = Int(1_000)
|
||||
let finalHeight = startHeight + blockCount
|
||||
|
||||
do {
|
||||
try TestDbBuilder.seed(db: compactBlockDao, with: startHeight...finalHeight)
|
||||
try await TestDbBuilder.seed(db: compactBlockDao, with: startHeight...finalHeight)
|
||||
} catch {
|
||||
XCTFail("seed faild with error: \(error)")
|
||||
return
|
||||
|
@ -38,7 +43,20 @@ class CompactBlockStorageTests: XCTestCase {
|
|||
XCTAssertEqual(latestHeight, finalHeight)
|
||||
}
|
||||
|
||||
func testStoreOneBlockFromEmpty() {
|
||||
func testStoreThousandBlocksAsync() async throws {
|
||||
let initialHeight = try! compactBlockDao.latestHeight()
|
||||
let startHeight = self.network.constants.saplingActivationHeight
|
||||
let blockCount = Int(1_000)
|
||||
let finalHeight = startHeight + blockCount
|
||||
|
||||
try await TestDbBuilder.seed(db: compactBlockDao, with: startHeight...finalHeight)
|
||||
|
||||
let latestHeight = try await compactBlockDao.latestHeightAsync()
|
||||
XCTAssertNotEqual(initialHeight, latestHeight)
|
||||
XCTAssertEqual(latestHeight, finalHeight)
|
||||
}
|
||||
|
||||
func testStoreOneBlockFromEmpty() async {
|
||||
let initialHeight = try! compactBlockDao.latestHeight()
|
||||
guard initialHeight == BlockHeight.empty() else {
|
||||
XCTFail("database not empty, latest height: \(initialHeight)")
|
||||
|
@ -50,7 +68,11 @@ class CompactBlockStorageTests: XCTestCase {
|
|||
XCTFail("could not create randem block with height: \(expectedHeight)")
|
||||
return
|
||||
}
|
||||
XCTAssertNoThrow(try compactBlockDao.write(blocks: [block]))
|
||||
do {
|
||||
try await compactBlockDao.write(blocks: [block])
|
||||
} catch {
|
||||
XCTFail("unexpected testStoreOneBlockFromEmpty fail")
|
||||
}
|
||||
|
||||
do {
|
||||
let result = try compactBlockDao.latestHeight()
|
||||
|
@ -61,25 +83,56 @@ class CompactBlockStorageTests: XCTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
func testRewindTo() {
|
||||
func testStoreOneBlockFromEmptyAsync() async throws {
|
||||
let initialHeight = try await compactBlockDao.latestHeightAsync()
|
||||
guard initialHeight == BlockHeight.empty() else {
|
||||
XCTFail("database not empty, latest height: \(initialHeight)")
|
||||
return
|
||||
}
|
||||
|
||||
let expectedHeight = BlockHeight(123_456)
|
||||
guard let block = StubBlockCreator.createRandomDataBlock(with: expectedHeight) else {
|
||||
XCTFail("could not create randem block with height: \(expectedHeight)")
|
||||
return
|
||||
}
|
||||
try await compactBlockDao.write(blocks: [block])
|
||||
|
||||
let result = try await compactBlockDao.latestHeightAsync()
|
||||
XCTAssertEqual(result, expectedHeight)
|
||||
}
|
||||
|
||||
func testRewindTo() async {
|
||||
let startHeight = self.network.constants.saplingActivationHeight
|
||||
let blockCount = Int(1_000)
|
||||
let finalHeight = startHeight + blockCount
|
||||
|
||||
do {
|
||||
try TestDbBuilder.seed(db: compactBlockDao, with: startHeight...finalHeight)
|
||||
try await TestDbBuilder.seed(db: compactBlockDao, with: startHeight...finalHeight)
|
||||
} catch {
|
||||
XCTFail("seed faild with error: \(error)")
|
||||
return
|
||||
}
|
||||
let rewindHeight = BlockHeight(finalHeight - 233)
|
||||
|
||||
XCTAssertNoThrow(try compactBlockDao.rewind(to: rewindHeight))
|
||||
do {
|
||||
try await compactBlockDao.rewindAsync(to: rewindHeight)
|
||||
let latestHeight = try compactBlockDao.latestHeight()
|
||||
XCTAssertEqual(latestHeight, rewindHeight - 1)
|
||||
} catch {
|
||||
XCTFail("Rewind latest block failed with error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func testRewindToAsync() async throws {
|
||||
let startHeight = self.network.constants.saplingActivationHeight
|
||||
let blockCount = Int(1_000)
|
||||
let finalHeight = startHeight + blockCount
|
||||
|
||||
try await TestDbBuilder.seed(db: compactBlockDao, with: startHeight...finalHeight)
|
||||
let rewindHeight = BlockHeight(finalHeight - 233)
|
||||
|
||||
try await compactBlockDao.rewindAsync(to: rewindHeight)
|
||||
let latestHeight = try await compactBlockDao.latestHeightAsync()
|
||||
XCTAssertEqual(latestHeight, rewindHeight - 1)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ class WalletTests: XCTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
func testWalletInitialization() throws {
|
||||
func testWalletInitialization() async throws {
|
||||
let derivationTool = DerivationTool(networkType: network.networkType)
|
||||
let uvk = try derivationTool.deriveUnifiedViewingKeysFromSeed(seedData.bytes, numberOfAccounts: 1)
|
||||
let wallet = Initializer(
|
||||
|
@ -49,7 +49,11 @@ class WalletTests: XCTestCase {
|
|||
)
|
||||
|
||||
let synchronizer = try SDKSynchronizer(initializer: wallet)
|
||||
XCTAssertNoThrow(try synchronizer.prepare())
|
||||
do {
|
||||
try await synchronizer.prepare()
|
||||
} catch {
|
||||
XCTFail("shouldn't fail here")
|
||||
}
|
||||
|
||||
// fileExists actually sucks, so attempting to delete the file and checking what happens is far better :)
|
||||
XCTAssertNoThrow( try FileManager.default.removeItem(at: dbData!) )
|
||||
|
|
|
@ -58,94 +58,25 @@ class DarksideWalletService: LightWalletService {
|
|||
self.init(endpoint: LightWalletEndpointBuilder.default)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func blockStream(
|
||||
startHeight: BlockHeight,
|
||||
endHeight: BlockHeight,
|
||||
result: @escaping (Result<GRPCResult, LightWalletServiceError>) -> Void,
|
||||
handler: @escaping (ZcashCompactBlock) -> Void,
|
||||
progress: @escaping (BlockProgress) -> Void
|
||||
) -> CancellableCall {
|
||||
return service.blockStream(
|
||||
startHeight: startHeight,
|
||||
endHeight: endHeight,
|
||||
result: result,
|
||||
handler: handler,
|
||||
progress: progress
|
||||
)
|
||||
func blockStream(startHeight: BlockHeight, endHeight: BlockHeight) -> AsyncThrowingStream<ZcashCompactBlock, Error> {
|
||||
service.blockStream(startHeight: startHeight, endHeight: endHeight)
|
||||
}
|
||||
|
||||
func getInfo() throws -> LightWalletdInfo {
|
||||
try service.getInfo()
|
||||
}
|
||||
|
||||
func getInfo(result: @escaping (Result<LightWalletdInfo, LightWalletServiceError>) -> Void) {
|
||||
service.getInfo(result: result)
|
||||
}
|
||||
|
||||
|
||||
func closeConnection() {
|
||||
}
|
||||
|
||||
func fetchUTXOs(for tAddress: String, height: BlockHeight) throws -> [UnspentTransactionOutputEntity] {
|
||||
return []
|
||||
func fetchUTXOs(for tAddress: String, height: BlockHeight) -> AsyncThrowingStream<UnspentTransactionOutputEntity, Error> {
|
||||
service.fetchUTXOs(for: tAddress, height: height)
|
||||
}
|
||||
|
||||
func fetchUTXOs(
|
||||
for tAddress: String,
|
||||
height: BlockHeight,
|
||||
result: @escaping (Result<[UnspentTransactionOutputEntity], LightWalletServiceError>) -> Void
|
||||
) {
|
||||
service.fetchUTXOs(for: tAddress, height: height, result: result)
|
||||
}
|
||||
|
||||
func fetchUTXOs(for tAddresses: [String], height: BlockHeight) throws -> [UnspentTransactionOutputEntity] {
|
||||
try service.fetchUTXOs(for: tAddresses, height: height)
|
||||
}
|
||||
|
||||
func fetchUTXOs(
|
||||
for tAddresses: [String],
|
||||
height: BlockHeight,
|
||||
result: @escaping (Result<[UnspentTransactionOutputEntity], LightWalletServiceError>) -> Void
|
||||
) {
|
||||
service.fetchUTXOs(for: tAddresses, height: height, result: result)
|
||||
}
|
||||
|
||||
|
||||
func fetchTransaction(txId: Data) throws -> TransactionEntity {
|
||||
try service.fetchTransaction(txId: txId)
|
||||
}
|
||||
|
||||
func fetchTransaction(txId: Data, result: @escaping (Result<TransactionEntity, LightWalletServiceError>) -> Void) {
|
||||
service.fetchTransaction(txId: txId, result: result)
|
||||
}
|
||||
|
||||
func latestBlockHeight(result: @escaping (Result<BlockHeight, LightWalletServiceError>) -> Void) {
|
||||
service.latestBlockHeight(result: result)
|
||||
|
||||
func fetchUTXOs(for tAddresses: [String], height: BlockHeight) -> AsyncThrowingStream<UnspentTransactionOutputEntity, Error> {
|
||||
service.fetchUTXOs(for: tAddresses, height: height)
|
||||
}
|
||||
|
||||
func latestBlockHeight() throws -> BlockHeight {
|
||||
try service.latestBlockHeight()
|
||||
}
|
||||
|
||||
func blockRange(_ range: CompactBlockRange, result: @escaping (Result<[ZcashCompactBlock], LightWalletServiceError>) -> Void) {
|
||||
service.blockRange(range, result: result)
|
||||
}
|
||||
|
||||
func blockRange(_ range: CompactBlockRange) throws -> [ZcashCompactBlock] {
|
||||
try service.blockRange(range)
|
||||
}
|
||||
|
||||
/**
|
||||
Darskside lightwalletd should do a fake submission, by sending over the tx, retrieving it and including it in a new block
|
||||
*/
|
||||
func submit(spendTransaction: Data, result: @escaping (Result<LightWalletServiceResponse, LightWalletServiceError>) -> Void) {
|
||||
service.submit(spendTransaction: spendTransaction, result: result)
|
||||
}
|
||||
|
||||
func submit(spendTransaction: Data) throws -> LightWalletServiceResponse {
|
||||
try service.submit(spendTransaction: spendTransaction)
|
||||
}
|
||||
|
||||
func useDataset(_ datasetUrl: String) throws {
|
||||
try useDataset(from: datasetUrl)
|
||||
}
|
||||
|
@ -222,6 +153,27 @@ class DarksideWalletService: LightWalletService {
|
|||
func clearAddedUTXOs() throws {
|
||||
_ = try darksideService.clearAddressUtxo(Empty(), callOptions: nil).response.wait()
|
||||
}
|
||||
|
||||
func getInfo() async throws -> LightWalletdInfo {
|
||||
try await service.getInfo()
|
||||
}
|
||||
|
||||
func latestBlockHeightAsync() async throws -> BlockHeight {
|
||||
try service.latestBlockHeight()
|
||||
}
|
||||
|
||||
func blockRange(_ range: CompactBlockRange) -> AsyncThrowingStream<ZcashCompactBlock, Error> {
|
||||
service.blockRange(range)
|
||||
}
|
||||
|
||||
/// Darskside lightwalletd should do a fake submission, by sending over the tx, retrieving it and including it in a new block
|
||||
func submit(spendTransaction: Data) async throws -> LightWalletServiceResponse {
|
||||
try await service.submit(spendTransaction: spendTransaction)
|
||||
}
|
||||
|
||||
func fetchTransaction(txId: Data) async throws -> TransactionEntity {
|
||||
try await service.fetchTransaction(txId: txId)
|
||||
}
|
||||
}
|
||||
|
||||
enum DarksideWalletDConstants: NetworkConstants {
|
||||
|
|
|
@ -24,63 +24,19 @@ class MockLightWalletService: LightWalletService {
|
|||
var mockLightDInfo: LightWalletdInfo?
|
||||
var queue = DispatchQueue(label: "mock service queue")
|
||||
|
||||
@discardableResult
|
||||
func blockStream(
|
||||
startHeight: BlockHeight,
|
||||
endHeight: BlockHeight,
|
||||
result: @escaping (Result<GRPCResult, LightWalletServiceError>) -> Void,
|
||||
handler: @escaping (ZcashCompactBlock) -> Void,
|
||||
progress: @escaping (BlockProgress) -> Void
|
||||
) -> CancellableCall {
|
||||
return MockCancellable()
|
||||
func blockStream(startHeight: BlockHeight, endHeight: BlockHeight) -> AsyncThrowingStream<ZcashCompactBlock, Error> {
|
||||
service.blockStream(startHeight: startHeight, endHeight: endHeight)
|
||||
}
|
||||
|
||||
func getInfo() throws -> LightWalletdInfo {
|
||||
guard let info = mockLightDInfo else {
|
||||
throw LightWalletServiceError.generalError(message: "Not Implemented")
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
func getInfo(result: @escaping (Result<LightWalletdInfo, LightWalletServiceError>) -> Void) {
|
||||
queue.async { [weak self] in
|
||||
guard let info = self?.mockLightDInfo else {
|
||||
result(.failure(LightWalletServiceError.generalError(message: "Not Implemented")))
|
||||
return
|
||||
}
|
||||
result(.success(info))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func closeConnection() {
|
||||
}
|
||||
|
||||
func fetchUTXOs(for tAddress: String, height: BlockHeight) throws -> [UnspentTransactionOutputEntity] {
|
||||
[]
|
||||
|
||||
func fetchUTXOs(for tAddress: String, height: BlockHeight) -> AsyncThrowingStream<UnspentTransactionOutputEntity, Error> {
|
||||
service.fetchUTXOs(for: tAddress, height: height)
|
||||
}
|
||||
|
||||
func fetchUTXOs(
|
||||
for tAddress: String,
|
||||
height: BlockHeight,
|
||||
result: @escaping (Result<[UnspentTransactionOutputEntity], LightWalletServiceError>) -> Void
|
||||
) {
|
||||
}
|
||||
|
||||
func fetchUTXOs(for tAddresses: [String], height: BlockHeight) throws -> [UnspentTransactionOutputEntity] {
|
||||
[]
|
||||
}
|
||||
|
||||
func fetchUTXOs(
|
||||
for tAddresses: [String],
|
||||
height: BlockHeight,
|
||||
result: @escaping (Result<[UnspentTransactionOutputEntity], LightWalletServiceError>) -> Void
|
||||
) {
|
||||
}
|
||||
|
||||
func fetchUTXOs(
|
||||
for tAddress: String,
|
||||
result: @escaping (Result<[UnspentTransactionOutputEntity], LightWalletServiceError>) -> Void
|
||||
) {
|
||||
|
||||
func fetchUTXOs(for tAddresses: [String], height: BlockHeight) -> AsyncThrowingStream<UnspentTransactionOutputEntity, Error> {
|
||||
service.fetchUTXOs(for: tAddresses, height: height)
|
||||
}
|
||||
|
||||
private var service: LightWalletService
|
||||
|
@ -92,38 +48,30 @@ class MockLightWalletService: LightWalletService {
|
|||
self.service = service
|
||||
}
|
||||
|
||||
func latestBlockHeight(result: @escaping (Result<BlockHeight, LightWalletServiceError>) -> Void) {
|
||||
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
|
||||
result(.success(self.latestHeight))
|
||||
}
|
||||
}
|
||||
|
||||
func latestBlockHeight() throws -> BlockHeight {
|
||||
return self.latestHeight
|
||||
}
|
||||
|
||||
func blockRange(_ range: CompactBlockRange, result: @escaping (Result<[ZcashCompactBlock], LightWalletServiceError>) -> Void) {
|
||||
self.service.blockRange(range, result: result)
|
||||
}
|
||||
|
||||
func blockRange(_ range: CompactBlockRange) throws -> [ZcashCompactBlock] {
|
||||
try self.service.blockRange(range)
|
||||
}
|
||||
|
||||
func submit(spendTransaction: Data, result: @escaping (Result<LightWalletServiceResponse, LightWalletServiceError>) -> Void) {
|
||||
DispatchQueue.global(qos: .default).asyncAfter(deadline: .now() + 1) {
|
||||
result(.success(LightWalletServiceMockResponse(errorCode: 0, errorMessage: "", unknownFields: UnknownStorage())))
|
||||
func getInfo() async throws -> LightWalletdInfo {
|
||||
guard let info = mockLightDInfo else {
|
||||
throw LightWalletServiceError.generalError(message: "Not Implemented")
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
func latestBlockHeightAsync() async throws -> BlockHeight {
|
||||
latestHeight
|
||||
}
|
||||
|
||||
func submit(spendTransaction: Data) throws -> LightWalletServiceResponse {
|
||||
return LightWalletServiceMockResponse(errorCode: 0, errorMessage: "", unknownFields: UnknownStorage())
|
||||
func blockRange(_ range: CompactBlockRange) -> AsyncThrowingStream<ZcashCompactBlock, Error> {
|
||||
service.blockRange(range)
|
||||
}
|
||||
|
||||
func fetchTransaction(txId: Data) throws -> TransactionEntity {
|
||||
func submit(spendTransaction: Data) async throws -> LightWalletServiceResponse {
|
||||
LightWalletServiceMockResponse(errorCode: 0, errorMessage: "", unknownFields: UnknownStorage())
|
||||
}
|
||||
|
||||
func fetchTransaction(txId: Data) async throws -> TransactionEntity {
|
||||
Transaction(id: 1, transactionId: Data(), created: "Today", transactionIndex: 1, expiryHeight: -1, minedHeight: -1, raw: nil)
|
||||
}
|
||||
|
||||
func fetchTransaction(txId: Data, result: @escaping (Result<TransactionEntity, LightWalletServiceError>) -> Void) {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,14 +10,22 @@ import Foundation
|
|||
@testable import ZcashLightClientKit
|
||||
|
||||
class ZcashConsoleFakeStorage: CompactBlockRepository {
|
||||
func latestHeight() throws -> Int {
|
||||
return self.latestBlockHeight
|
||||
func latestHeightAsync() async throws -> BlockHeight {
|
||||
latestBlockHeight
|
||||
}
|
||||
|
||||
func write(blocks: [ZcashCompactBlock]) throws {
|
||||
func write(blocks: [ZcashCompactBlock]) async throws {
|
||||
fakeSave(blocks: blocks)
|
||||
}
|
||||
|
||||
func rewindAsync(to height: BlockHeight) async throws {
|
||||
fakeRewind(to: height)
|
||||
}
|
||||
|
||||
func latestHeight() throws -> Int {
|
||||
return self.latestBlockHeight
|
||||
}
|
||||
|
||||
func rewind(to height: BlockHeight) throws {
|
||||
fakeRewind(to: height)
|
||||
}
|
||||
|
@ -29,12 +37,6 @@ class ZcashConsoleFakeStorage: CompactBlockRepository {
|
|||
self.latestBlockHeight = latestBlockHeight
|
||||
}
|
||||
|
||||
func latestHeight(result: @escaping (Result<BlockHeight, Error>) -> Void) {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
|
||||
result(.success(self.latestBlockHeight))
|
||||
}
|
||||
}
|
||||
|
||||
private func fakeSave(blocks: [ZcashCompactBlock]) {
|
||||
blocks.forEach {
|
||||
LoggerProxy.debug("saving block \($0)")
|
||||
|
@ -42,20 +44,6 @@ class ZcashConsoleFakeStorage: CompactBlockRepository {
|
|||
}
|
||||
}
|
||||
|
||||
func write(blocks: [ZcashCompactBlock], completion: ((Error?) -> Void)?) {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
|
||||
self.fakeSave(blocks: blocks)
|
||||
completion?(nil)
|
||||
}
|
||||
}
|
||||
|
||||
func rewind(to height: BlockHeight, completion: ((Error?) -> Void)?) {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
|
||||
self.fakeRewind(to: height)
|
||||
completion?(nil)
|
||||
}
|
||||
}
|
||||
|
||||
private func fakeRewind(to height: BlockHeight) {
|
||||
LoggerProxy.debug("rewind to \(height)")
|
||||
self.latestBlockHeight = min(self.latestBlockHeight, height)
|
||||
|
|
|
@ -17,48 +17,26 @@ class AwfulLightWalletService: MockLightWalletService {
|
|||
throw LightWalletServiceError.criticalError
|
||||
}
|
||||
|
||||
override func blockRange(_ range: CompactBlockRange) throws -> [ZcashCompactBlock] {
|
||||
override func latestBlockHeightAsync() async throws -> BlockHeight {
|
||||
throw LightWalletServiceError.invalidBlock
|
||||
}
|
||||
|
||||
override func latestBlockHeight(result: @escaping (Result<BlockHeight, LightWalletServiceError>) -> Void) {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
result(.failure(LightWalletServiceError.invalidBlock))
|
||||
}
|
||||
override func blockRange(_ range: CompactBlockRange) -> AsyncThrowingStream<ZcashCompactBlock, Error> {
|
||||
AsyncThrowingStream { continuation in continuation.finish(throwing: LightWalletServiceError.invalidBlock) }
|
||||
}
|
||||
|
||||
override func blockRange(_ range: CompactBlockRange, result: @escaping (Result<[ZcashCompactBlock], LightWalletServiceError>) -> Void) {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
result(.failure(LightWalletServiceError.invalidBlock))
|
||||
}
|
||||
}
|
||||
|
||||
override func submit(spendTransaction: Data, result: @escaping(Result<LightWalletServiceResponse, LightWalletServiceError>) -> Void) {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
result(.failure(LightWalletServiceError.invalidBlock))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Submits a raw transaction over lightwalletd. Blocking
|
||||
*/
|
||||
override func submit(spendTransaction: Data) throws -> LightWalletServiceResponse {
|
||||
|
||||
/// Submits a raw transaction over lightwalletd.
|
||||
override func submit(spendTransaction: Data) async throws -> LightWalletServiceResponse {
|
||||
throw LightWalletServiceError.invalidBlock
|
||||
}
|
||||
}
|
||||
|
||||
class SlightlyBadLightWalletService: MockLightWalletService {
|
||||
override func submit(spendTransaction: Data, result: @escaping(Result<LightWalletServiceResponse, LightWalletServiceError>) -> Void) {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
result(.success(LightWalletServiceMockResponse.error))
|
||||
}
|
||||
}
|
||||
extension LightWalletServiceMockResponse: Error { }
|
||||
|
||||
/**
|
||||
Submits a raw transaction over lightwalletd. Blocking
|
||||
*/
|
||||
override func submit(spendTransaction: Data) throws -> LightWalletServiceResponse {
|
||||
LightWalletServiceMockResponse.error
|
||||
class SlightlyBadLightWalletService: MockLightWalletService {
|
||||
/// Submits a raw transaction over lightwalletd.
|
||||
override func submit(spendTransaction: Data) async throws -> LightWalletServiceResponse {
|
||||
throw LightWalletServiceMockResponse.error
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -200,6 +200,12 @@ class TestCoordinator {
|
|||
}
|
||||
}
|
||||
|
||||
extension CompactBlockProcessor {
|
||||
public func setConfig(_ config: Configuration) {
|
||||
self.config = config
|
||||
}
|
||||
}
|
||||
|
||||
extension TestCoordinator {
|
||||
func resetBlocks(dataset: DarksideData) throws {
|
||||
switch dataset {
|
||||
|
@ -233,19 +239,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)
|
||||
}
|
||||
|
||||
|
@ -308,7 +320,9 @@ enum TestSynchronizerBuilder {
|
|||
)
|
||||
|
||||
let synchronizer = try SDKSynchronizer(initializer: initializer)
|
||||
try synchronizer.prepare()
|
||||
Task {
|
||||
try await synchronizer.prepare()
|
||||
}
|
||||
|
||||
return ([spendingKey], synchronizer)
|
||||
}
|
||||
|
|
|
@ -92,12 +92,12 @@ class TestDbBuilder {
|
|||
return ReceivedNotesSQLDAO(dbProvider: provider)
|
||||
}
|
||||
|
||||
static func seed(db: CompactBlockRepository, with blockRange: CompactBlockRange) throws {
|
||||
static func seed(db: CompactBlockRepository, with blockRange: CompactBlockRange) async throws {
|
||||
guard let blocks = StubBlockCreator.createBlockRange(blockRange) else {
|
||||
throw TestBuilderError.generalError
|
||||
}
|
||||
|
||||
try db.write(blocks: blocks)
|
||||
try await db.write(blocks: blocks)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import GRPC
|
|||
import ZcashLightClientKit
|
||||
import XCTest
|
||||
import NIO
|
||||
import NIOTransportServices
|
||||
|
||||
enum Environment {
|
||||
static let lightwalletdKey = "LIGHTWALLETD_ADDRESS"
|
||||
|
@ -40,8 +41,8 @@ class ChannelProvider {
|
|||
let endpoint = LightWalletEndpointBuilder.default
|
||||
|
||||
let connectionBuilder = secure ?
|
||||
ClientConnection.usingPlatformAppropriateTLS(for: MultiThreadedEventLoopGroup(numberOfThreads: 1)) :
|
||||
ClientConnection.insecure(group: MultiThreadedEventLoopGroup(numberOfThreads: 1))
|
||||
ClientConnection.usingPlatformAppropriateTLS(for: NIOTSEventLoopGroup(loopCount: 1, defaultQoS: .default)) :
|
||||
ClientConnection.insecure(group: NIOTSEventLoopGroup(loopCount: 1, defaultQoS: .default))
|
||||
|
||||
let channel = connectionBuilder
|
||||
.withKeepalive(
|
||||
|
|
Loading…
Reference in New Issue