From 60aa28628bf0f36d20789f3be9f576df71795b73 Mon Sep 17 00:00:00 2001 From: Francisco Gindre Date: Fri, 16 Sep 2022 16:59:31 -0300 Subject: [PATCH 1/6] [#532] [0.16.x-beta] Download does not stop correctly Issue Reported: When the synchronizer is stopped, the processor does not cancel the download correctly. Then when attempting to resume sync, the synchronizer is not on .stopped and can't be resumed this doesn't appear to happen in master branch that uses structured concurrency for operations. Fix: This commit makes sure that the download streamer checks cancelation before processing any block, or getting called back to report progress --- .../CompactBlockDownloadOperation.swift | 29 +++- Tests/DarksideTests/SynchronizerTests.swift | 128 ++++++++++++++++++ 2 files changed, 151 insertions(+), 6 deletions(-) create mode 100644 Tests/DarksideTests/SynchronizerTests.swift diff --git a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockDownloadOperation.swift b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockDownloadOperation.swift index 8ecf6516..06cd0601 100644 --- a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockDownloadOperation.swift +++ b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockDownloadOperation.swift @@ -111,41 +111,58 @@ class CompactBlockStreamDownloadOperation: ZcashOperation { 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 + try self.flush() + self.done = true } catch { - self?.fail(error: error) + self.fail(error: error) } return case .error(let e): - self?.fail(error: e) + self.fail(error: e) } case .failure(let e): if case .userCancelled = e { - self?.done = true + self.done = true } else { - self?.fail(error: e) + 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) } diff --git a/Tests/DarksideTests/SynchronizerTests.swift b/Tests/DarksideTests/SynchronizerTests.swift new file mode 100644 index 00000000..c500f0e4 --- /dev/null +++ b/Tests/DarksideTests/SynchronizerTests.swift @@ -0,0 +1,128 @@ +// +// SynchronizerTests.swift +// DarksideTests +// +// Created by Francisco Gindre on 9/16/22. +// + +import XCTest +@testable import TestUtils +@testable import ZcashLightClientKit + +// swiftlint:disable implicitly_unwrapped_optional force_unwrapping type_body_length +final class SynchronizerTests: XCTestCase { + + // TODO: Parameterize this from environment? + // swiftlint:disable:next line_length + var seedPhrase = "still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread" + + // TODO: Parameterize this from environment + let testRecipientAddress = "zs17mg40levjezevuhdp5pqrd52zere7r7vrjgdwn5sj4xsqtm20euwahv9anxmwr3y3kmwuz8k55a" + + let sendAmount = Zatoshi(1000) + var birthday: BlockHeight = 663150 + let defaultLatestHeight: BlockHeight = 663175 + var coordinator: TestCoordinator! + var syncedExpectation = XCTestExpectation(description: "synced") + var sentTransactionExpectation = XCTestExpectation(description: "sent") + var expectedReorgHeight: BlockHeight = 665188 + var expectedRewindHeight: BlockHeight = 665188 + var reorgExpectation = XCTestExpectation(description: "reorg") + let branchID = "2bb40e60" + let chainName = "main" + let network = DarksideWalletDNetwork() + + override func setUpWithError() throws { + try super.setUpWithError() + coordinator = try TestCoordinator( + seed: seedPhrase, + walletBirthday: birthday + 50, //don't use an exact birthday, users never do. + channelProvider: ChannelProvider(), + network: network + ) + try coordinator.reset(saplingActivation: 663150, branchID: self.branchID, chainName: self.chainName) + } + + override func tearDownWithError() throws { + try super.tearDownWithError() + NotificationCenter.default.removeObserver(self) + try coordinator.stop() + try? FileManager.default.removeItem(at: coordinator.databases.cacheDB) + try? FileManager.default.removeItem(at: coordinator.databases.dataDB) + try? FileManager.default.removeItem(at: coordinator.databases.pendingDB) + } + + @objc func handleReorg(_ notification: Notification) { + guard + let reorgHeight = notification.userInfo?[CompactBlockProcessorNotificationKey.reorgHeight] as? BlockHeight, + let rewindHeight = notification.userInfo?[CompactBlockProcessorNotificationKey.rewindHeight] as? BlockHeight + else { + XCTFail("empty reorg notification") + return + } + + logger!.debug("--- REORG DETECTED \(reorgHeight)--- RewindHeight: \(rewindHeight)", file: #file, function: #function, line: #line) + + XCTAssertEqual(reorgHeight, expectedReorgHeight) + reorgExpectation.fulfill() + } + + func testSynchronizerStops() throws { + hookToReOrgNotification() + + /* + 1. create fake chain + */ + let fullSyncLength = 100_000 + + try FakeChainBuilder.buildChain(darksideWallet: coordinator.service, branchID: branchID, chainName: chainName, length: fullSyncLength) + + try coordinator.applyStaged(blockheight: birthday + fullSyncLength) + + sleep(10) + + let syncStoppedExpectation = XCTestExpectation(description: "SynchronizerStopped Expectation") + syncStoppedExpectation.subscribe(to: .synchronizerStopped, object: nil) + + let processorStoppedExpectation = XCTestExpectation(description: "ProcessorStopped Expectation") + processorStoppedExpectation.subscribe(to: .blockProcessorStopped, object: nil) + + /* + sync to latest height + */ + try coordinator.sync(completion: { _ in + XCTFail("Sync should have stopped") + }, error: { error in + _ = try? self.coordinator.stop() + + guard let testError = error else { + XCTFail("failed with nil error") + return + } + XCTFail("Failed with error: \(testError)") + }) + + DispatchQueue.main.asyncAfter(deadline: .now() + 5) { + self.coordinator.synchronizer.stop() + } + + wait(for: [processorStoppedExpectation,syncStoppedExpectation], timeout: 6, enforceOrder: true) + + XCTAssertEqual(coordinator.synchronizer.status, .stopped) + XCTAssertEqual(coordinator.synchronizer.blockProcessor.state, .stopped) + } + + func handleError(_ error: Error?) { + _ = try? coordinator.stop() + guard let testError = error else { + XCTFail("failed with nil error") + return + } + XCTFail("Failed with error: \(testError)") + } + + func hookToReOrgNotification() { + NotificationCenter.default.addObserver(self, selector: #selector(handleReorg(_:)), name: .blockProcessorHandledReOrg, object: nil) + + } +} From 044d7d99d5b1f5d63c4f068b4f2b897ba5461b0e Mon Sep 17 00:00:00 2001 From: Francisco Gindre Date: Fri, 16 Sep 2022 17:41:47 -0300 Subject: [PATCH 2/6] Bump version and add new checkpoints --- .../checkpoints/mainnet/1807500.json | 8 +++++++ .../checkpoints/mainnet/1810000.json | 8 +++++++ ZcashLightClientKit.podspec | 2 +- changelog.md | 22 +++++++++++++++++++ 4 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1807500.json create mode 100644 Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1810000.json diff --git a/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1807500.json b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1807500.json new file mode 100644 index 00000000..902d00ec --- /dev/null +++ b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1807500.json @@ -0,0 +1,8 @@ +{ + "network": "main", + "height": "1807500", + "hash": "0000000000b92234ffe6efd360a2fb1528e2f5cf891a54e58a9b83cd6c513ad9", + "time": 1663095814, + "saplingTree": "0146b9cd1dc80b44c19108d53ecf72b5bacd405e8ed5c8038c413ad2d7bf9f486201fb61737c80f0c3816ddc487ff9ba360c60b1663151cbd07c08dd8c09c648760d190177aa27c08a1ff4754710984f2f94c5a101a10f57eb6ad49f7b1df47f7e8a5730014c0fb4e36c1b7968082d0355f318a67e265e5a5533ffcc01b939e4bf1779841b000000019315fe44f132446949cd9ea1f34ec20f32c0dd0ba4f732e563c040c692fd5d49010e6b6ae8956bd722b0f5e3f355adfc7c9b6a39542c07fb0bcd317d0eff8d99080001382db97fabb726a88e7a7baab050a1c9169d2142a9ce0b7f3022349acf74981a0175d44cdd04e213c2d95fb281f96fc7d734c65be70a77b7aa4fef8a4b6fb2475b015fe654f11132fc9c133b46dbbf19b0113cc45715f52dfd3282ede76f6880740c01d14f83f0fd7d09f4f52c8ae9d39c63b57c59a37666d211b9a2deb290808c02720001701df279d9a2270a82379486df546fafeaeb831993cde1cd9e5c9cb17be5191f01cc6ae86e9147b0b1c3f5fb32fb7acc012b4cb4384a1a1331ae3c2324a804483e00017fb2e2890b05355ba797af2f77e38cab3e8ec1623d29a912bdd0dc4a78cf554a0001de17a599d0c6d73eaf1a5939e95af4427f75b89f703749e00d853cce3d6af84f00014f6313837ed19d2b480ec531529fb6b425006f2c1d981077640be21627659410018c2d6adea2ad4faf20eccfc2c2a2c59192fb53d3204b3a2757f1c247dadec16b0001c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e", + "orchardTree": "013de3a4ad28b5df77a979fc925643e69b7dd26ba787a3122fcd6a445c47d9280e01b68287983d323c90e6a1ed5980ab5b1a846d49340ee6d40d2349795c132a382c1f0001c2db91c5b9baa1c09623429cb4005ae12e521a018eb8df2d051d6793a307eb3e01c6b2f8d93635310d470d4a6d011ea77f59e28bee6ffca3df88f5f2a98980331a01777860ffe739b8047045e2dff8ba77070666075214a0f7702568205410351b39019ed7c5c3e958cb8b9d5324c290ff384dc3ca6cbc870950002f64398478ff1904000114b5ad56c8f210854a1688f47116b5d272fea09559646cee33ad3e6958306a15000001110e689714d772b170f63bfbf4b144c6a48e0a57c4a2780513633d5c799a0d1300010cf94eaf4d5268d9e0878064a458eaa3363dfcfcf2602681c443baf989d5de20000000019776dec2ea06cc5ecd2d212d37023972f526cb2ffa7ce1e8cf8eb4ef04700b01000001c7146e487b3ae97b190ebf93eac554968e683d31115d13fe83dd620859d9a92d000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000" +} diff --git a/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1810000.json b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1810000.json new file mode 100644 index 00000000..e1edd90b --- /dev/null +++ b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1810000.json @@ -0,0 +1,8 @@ +{ + "network": "main", + "height": "1810000", + "hash": "0000000000bdf01be068ff1f0b21b8b266f839b31ed066b72888b67f672c9800", + "time": 1663283618, + "saplingTree": "019dbc466ad114f2b4a6d0d91198c0a2c5c20c1dec7c2e7f932c9f9197d76b80020019015b272118101cac0ee6b9b8cf26d5104ab42913d2f5253388bac28e7998f2c41b0001868d5008c587f0fa3d26fc42097d34df49a70508a85a6acccf1263d39cb62c28000001b2325a6ecbf023f3ce4b74c1c14bac8d9c462560dce9611bd7b08cd55ecf4b0100000001cd62e4e2142c664656f956fd0ea8d1de1027c327580398fe8912e6264801650201ccc9b305f6e65d641a4e461ea5e853354a3ac4b2acf2591849e00421bd58fb48000000013a591632f71e1fe214ab46b464112520e98f15d171da599a350f7d8dba79595d018d254447626cf40828102a60e2b433d05498a780599cdf56a14f3888c2f42008000148af2e64d92d1944a451180f1738c9f468f608525b0273967db19029b53ba16d0000000001f416eb7e062c981dbbf76f8845fda959b948bc742fc62d9edb2f36bae852ba4e01c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e", + "orchardTree": "0180c286284cd52360af960fce56fe7dc339667c35580c75e9bc339483230e932701260b8984058125b5e3558fb65172d59718427111cbc06891017cc7633f74e1001f00000000000001cde58ba8e982a34499406e06a763ede11353d39ab93a5af1b34905cf268c033701226087d6a9bb28c2ed6890b989224791093cd5012a27285040025c0a72b2ce06000001ba71f8a1897b754e9fe37a29eb2c1a93ddc1678298498b4a84da732ebf056f15018073f4aff677a24eaac68c20d271ea228041f1e77710b0504d3f6c0b71d63d24000146b37a3e6167ae7f07725ab4e32247619c37e2a91c87182dd68b8feb99d5a22201e18dad85447ef2e9b8d647c9b9f1e6cef3e1d03f908975cd5e1d5c5808e443010001898b4a8f384f342a67efb3f6c4afd87310df4ff1532b86ca8d1394975aab5a1e0001c7146e487b3ae97b190ebf93eac554968e683d31115d13fe83dd620859d9a92d000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000" +} diff --git a/ZcashLightClientKit.podspec b/ZcashLightClientKit.podspec index 9f9c6728..6b23ca6c 100644 --- a/ZcashLightClientKit.podspec +++ b/ZcashLightClientKit.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'ZcashLightClientKit' - s.version = '0.16.9-beta' + s.version = '0.16.10-beta' s.summary = 'Zcash Light Client wallet SDK for iOS' s.description = <<-DESC diff --git a/changelog.md b/changelog.md index 935b6aec..eacab72e 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,25 @@ +# 0.16.10-beta +- [#532] [0.16.x-beta] Download does not stop correctly + +Issue Reported: + +When the synchronizer is stopped, the processor does not cancel +the download correctly. Then when attempting to resume sync, the +synchronizer is not on `.stopped` and can't be resumed + +this doesn't appear to happen in `master` branch that uses +structured concurrency for operations. + +Fix: +This commit makes sure that the download streamer checks cancelation +before processing any block, or getting called back to report progress + +Checkpoints added: +Mainnet +```` +Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1807500.json +Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1810000.json +```` # 0.16.9-beta Checkpoints added: Mainnet From d8ee9ff277e0cb3a9b4ded398b069f98081e6268 Mon Sep 17 00:00:00 2001 From: Francisco Gindre Date: Fri, 16 Sep 2022 18:08:31 -0300 Subject: [PATCH 3/6] - [#532] Download does not stop correctly Issue Reported on [0.16.x-beta] When the synchronizer is stopped, the processor does not cancel the download correctly. Then when attempting to resume sync, the synchronizer is not on `.stopped` and can't be resumed Fix: This commit makes sure that the download streamer checks cancelation before processing any block, or getting called back to report progress --- .../Block/Processor/CompactBlockDownload.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockDownload.swift b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockDownload.swift index 58eefb02..db6cd260 100644 --- a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockDownload.swift +++ b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockDownload.swift @@ -29,6 +29,7 @@ extension CompactBlockProcessor { 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) @@ -38,6 +39,7 @@ extension CompactBlockProcessor { ) for try await zcashCompactBlock in stream { + try Task.checkCancellation() buffer.append(zcashCompactBlock) if buffer.count >= blockBufferSize { // TODO: writeAsync doesn't make sense here, awaiting it or calling blocking API have the same result and impact From 61683a85f345deaacdb7a2359fd4d1b5a83b0548 Mon Sep 17 00:00:00 2001 From: Lukas Korba Date: Tue, 13 Sep 2022 12:19:56 +0200 Subject: [PATCH 4/6] [486] sendToAddress async/await - sendToAddress and SaplingParameterDownloader refactored to async - unit tests WIP [486] sendToAddress async/await - testing code cleanup [#486] sendToAddress async/await - reducing number of Tasks in Sample app [#486] sendToAddress async/await - I finally figure out how to make the dark side tests async, using new sendToAddress APIs and pass, POC done, 18 more tests to refactor [#486] sendToAddress async/await - async sendToAddress unit test refactored except NetworkUpgradeTests [#486] sendToAddress async/await - NetworkUpgradeTests refactored to async API [#486] sendToAddress async/await - cleanup [#486] sendToAddress async/await (#536) - PR comments resolved --- .../SaplingParametersViewController.swift | 32 +- .../Send/SendViewController.swift | 30 +- Sources/ZcashLightClientKit/Initializer.swift | 49 +- .../ZcashLightClientKit/Synchronizer.swift | 26 +- .../Synchronizer/SDKSynchronizer.swift | 84 +-- .../Utils/SaplingParameterDownloader.swift | 108 +-- Tests/DarksideTests/AdvancedReOrgTests.swift | 312 +++++--- Tests/DarksideTests/BalanceTests.swift | 701 ++++++++++-------- Tests/DarksideTests/NetworkUpgradeTests.swift | 285 ++++--- .../PendingTransactionUpdatesTest.swift | 79 +- Tests/DarksideTests/RewindRescanTests.swift | 155 ++-- Tests/DarksideTests/Z2TReceiveTests.swift | 67 +- 12 files changed, 1046 insertions(+), 882 deletions(-) diff --git a/Example/ZcashLightClientSample/ZcashLightClientSample/Sapling Parameters/SaplingParametersViewController.swift b/Example/ZcashLightClientSample/ZcashLightClientSample/Sapling Parameters/SaplingParametersViewController.swift index 51aff80c..ad9d9812 100644 --- a/Example/ZcashLightClientSample/ZcashLightClientSample/Sapling Parameters/SaplingParametersViewController.swift +++ b/Example/ZcashLightClientSample/ZcashLightClientSample/Sapling Parameters/SaplingParametersViewController.swift @@ -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 { diff --git a/Example/ZcashLightClientSample/ZcashLightClientSample/Send/SendViewController.swift b/Example/ZcashLightClientSample/ZcashLightClientSample/Send/SendViewController.swift index 8502ff99..abc6f62e 100644 --- a/Example/ZcashLightClientSample/ZcashLightClientSample/Send/SendViewController.swift +++ b/Example/ZcashLightClientSample/ZcashLightClientSample/Send/SendViewController.swift @@ -214,26 +214,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)") } } } diff --git a/Sources/ZcashLightClientKit/Initializer.swift b/Sources/ZcashLightClientKit/Initializer.swift index fe0c7851..c812d0c9 100644 --- a/Sources/ZcashLightClientKit/Initializer.swift +++ b/Sources/ZcashLightClientKit/Initializer.swift @@ -320,48 +320,35 @@ public class Initializer { FileManager.default.isReadableFile(atPath: self.outputParamsURL.path) } - func downloadParametersIfNeeded(result: @escaping (Result) -> 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 } } diff --git a/Sources/ZcashLightClientKit/Synchronizer.swift b/Sources/ZcashLightClientKit/Synchronizer.swift index c7948e47..01a0bcef 100644 --- a/Sources/ZcashLightClientKit/Synchronizer.swift +++ b/Sources/ZcashLightClientKit/Synchronizer.swift @@ -107,40 +107,20 @@ public protocol Synchronizer { /// - 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) -> Void - ) - - + /// 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) -> Void - ) + from accountIndex: Int + ) async throws -> PendingTransactionEntity /// Sends zatoshi. /// - Parameter spendingKey: the key that allows spends to occur. diff --git a/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift b/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift index f6c27f44..819de7a1 100644 --- a/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift +++ b/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift @@ -455,52 +455,27 @@ 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) -> 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) -> 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( @@ -547,16 +522,14 @@ public class SDKSynchronizer: Synchronizer { return } } - - // swiftlint:disable:next function_parameter_count + func createToAddress( spendingKey: String, zatoshi: Zatoshi, toAddress: String, memo: String?, - from accountIndex: Int, - resultBlock: @escaping (Result) -> Void - ) { + from accountIndex: Int + ) async throws -> PendingTransactionEntity { do { let spend = try transactionManager.initSpend( zatoshi: zatoshi, @@ -565,21 +538,14 @@ public class SDKSynchronizer: Synchronizer { from: accountIndex ) - // TODO: Task will be removed when this method is changed to async, issue 487, https://github.com/zcash/ZcashLightClientKit/issues/487 - Task { - do { - let transaction = try await transactionManager.encode( - spendingKey: spendingKey, - pendingTransaction: spend - ) - let submittedTx = try await transactionManager.submit(pendingTransaction: transaction) - resultBlock(.success(submittedTx)) - } catch { - 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 } } diff --git a/Sources/ZcashLightClientKit/Utils/SaplingParameterDownloader.swift b/Sources/ZcashLightClientKit/Utils/SaplingParameterDownloader.swift index 432d47b0..cc9b0e5b 100644 --- a/Sources/ZcashLightClientKit/Utils/SaplingParameterDownloader.swift +++ b/Sources/ZcashLightClientKit/Utils/SaplingParameterDownloader.swift @@ -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) -> 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) -> 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) -> 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) -> 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) -> 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) } } diff --git a/Tests/DarksideTests/AdvancedReOrgTests.swift b/Tests/DarksideTests/AdvancedReOrgTests.swift index 7b0e2050..8d02d393 100644 --- a/Tests/DarksideTests/AdvancedReOrgTests.swift +++ b/Tests/DarksideTests/AdvancedReOrgTests.swift @@ -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 @@ -268,7 +269,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 +281,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 +357,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 +386,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 +415,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 +697,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 +712,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 +734,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 +785,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 +830,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 +865,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 +902,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 +1086,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 +1103,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 +1125,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 +1176,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 +1196,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,10 +1223,17 @@ 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) diff --git a/Tests/DarksideTests/BalanceTests.swift b/Tests/DarksideTests/BalanceTests.swift index 409f3f7e..6c63373c 100644 --- a/Tests/DarksideTests/BalanceTests.swift +++ b/Tests/DarksideTests/BalanceTests.swift @@ -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) /* diff --git a/Tests/DarksideTests/NetworkUpgradeTests.swift b/Tests/DarksideTests/NetworkUpgradeTests.swift index ac77b350..9c56cac4 100644 --- a/Tests/DarksideTests/NetworkUpgradeTests.swift +++ b/Tests/DarksideTests/NetworkUpgradeTests.swift @@ -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) diff --git a/Tests/DarksideTests/PendingTransactionUpdatesTest.swift b/Tests/DarksideTests/PendingTransactionUpdatesTest.swift index 7bba41df..7ad817a1 100644 --- a/Tests/DarksideTests/PendingTransactionUpdatesTest.swift +++ b/Tests/DarksideTests/PendingTransactionUpdatesTest.swift @@ -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? diff --git a/Tests/DarksideTests/RewindRescanTests.swift b/Tests/DarksideTests/RewindRescanTests.swift index 316084e4..ce677576 100644 --- a/Tests/DarksideTests/RewindRescanTests.swift +++ b/Tests/DarksideTests/RewindRescanTests.swift @@ -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() @@ -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,20 +176,16 @@ 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) } @@ -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 @@ -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() diff --git a/Tests/DarksideTests/Z2TReceiveTests.swift b/Tests/DarksideTests/Z2TReceiveTests.swift index f673b497..e6881cba 100644 --- a/Tests/DarksideTests/Z2TReceiveTests.swift +++ b/Tests/DarksideTests/Z2TReceiveTests.swift @@ -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) } From 2c93802828044cdd8c29404ea2b19e01b9c645b7 Mon Sep 17 00:00:00 2001 From: Lukas Korba Date: Wed, 21 Sep 2022 14:30:06 +0200 Subject: [PATCH 5/6] [#487] shieldFunds to async/await - API refactored to async [#487] shieldFunds to async/await - unit tests refactored --- .../Get UTXOs/GetUTXOsViewController.swift | 28 ++-- .../ZcashLightClientKit/Synchronizer.swift | 7 +- .../Synchronizer/SDKSynchronizer.swift | 33 ++--- Tests/DarksideTests/ShieldFundsTests.swift | 125 ++++++++++-------- 4 files changed, 94 insertions(+), 99 deletions(-) diff --git a/Example/ZcashLightClientSample/ZcashLightClientSample/Get UTXOs/GetUTXOsViewController.swift b/Example/ZcashLightClientSample/ZcashLightClientSample/Get UTXOs/GetUTXOsViewController.swift index 0d79d9df..a0ed553c 100644 --- a/Example/ZcashLightClientSample/ZcashLightClientSample/Get UTXOs/GetUTXOsViewController.swift +++ b/Example/ZcashLightClientSample/ZcashLightClientSample/Get UTXOs/GetUTXOsViewController.swift @@ -46,25 +46,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)" } } } diff --git a/Sources/ZcashLightClientKit/Synchronizer.swift b/Sources/ZcashLightClientKit/Synchronizer.swift index c7948e47..10b86ffe 100644 --- a/Sources/ZcashLightClientKit/Synchronizer.swift +++ b/Sources/ZcashLightClientKit/Synchronizer.swift @@ -142,7 +142,7 @@ public protocol Synchronizer { resultBlock: @escaping (_ result: Result) -> Void ) - /// 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 +151,8 @@ public protocol Synchronizer { spendingKey: String, transparentSecretKey: String, memo: String?, - from accountIndex: Int, - resultBlock: @escaping (_ result: Result) -> 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. diff --git a/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift b/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift index f6c27f44..ceb36fd1 100644 --- a/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift +++ b/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift @@ -507,9 +507,8 @@ public class SDKSynchronizer: Synchronizer { spendingKey: String, transparentSecretKey: String, memo: String?, - from accountIndex: Int, - resultBlock: @escaping (Result) -> Void - ) { + from accountIndex: Int + ) async throws -> PendingTransactionEntity { // let's see if there are funds to shield let derivationTool = DerivationTool(networkType: self.network.networkType) @@ -519,32 +518,22 @@ public class SDKSynchronizer: Synchronizer { // 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) - // TODO: Task will be removed when this method is changed to async, issue 487, https://github.com/zcash/ZcashLightClientKit/issues/487 - Task { - do { - let transaction = try await transactionManager.encodeShieldingTransaction( - spendingKey: spendingKey, - tsk: transparentSecretKey, - pendingTransaction: shieldingSpend - ) - - let submittedTx = try await transactionManager.submit(pendingTransaction: transaction) - resultBlock(.success(submittedTx)) - } catch { - resultBlock(.failure(error)) - } - } + let transaction = try await transactionManager.encodeShieldingTransaction( + spendingKey: spendingKey, + tsk: transparentSecretKey, + pendingTransaction: shieldingSpend + ) + + return try await transactionManager.submit(pendingTransaction: transaction) } catch { - resultBlock(.failure(error)) - return + throw error } } diff --git a/Tests/DarksideTests/ShieldFundsTests.swift b/Tests/DarksideTests/ShieldFundsTests.swift index 836d4f13..c0a4463b 100644 --- a/Tests/DarksideTests/ShieldFundsTests.swift +++ b/Tests/DarksideTests/ShieldFundsTests.swift @@ -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") @@ -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) @@ -149,14 +154,17 @@ 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 @@ -176,13 +184,17 @@ 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) @@ -209,22 +221,18 @@ 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) @@ -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 } @@ -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) From 9e41fb43757fd7b0eb8e817320537230774258cf Mon Sep 17 00:00:00 2001 From: Francisco Gindre Date: Wed, 21 Sep 2022 13:27:35 -0300 Subject: [PATCH 6/6] Fix: [#539] 0.16.10-beta not in Podspec of that version --- .../checkpoints/mainnet/1807500.json | 8 +++++++ .../checkpoints/mainnet/1810000.json | 8 +++++++ ZcashLightClientKit.podspec | 2 +- changelog.md | 22 +++++++++++++++++++ 4 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1807500.json create mode 100644 Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1810000.json diff --git a/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1807500.json b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1807500.json new file mode 100644 index 00000000..902d00ec --- /dev/null +++ b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1807500.json @@ -0,0 +1,8 @@ +{ + "network": "main", + "height": "1807500", + "hash": "0000000000b92234ffe6efd360a2fb1528e2f5cf891a54e58a9b83cd6c513ad9", + "time": 1663095814, + "saplingTree": "0146b9cd1dc80b44c19108d53ecf72b5bacd405e8ed5c8038c413ad2d7bf9f486201fb61737c80f0c3816ddc487ff9ba360c60b1663151cbd07c08dd8c09c648760d190177aa27c08a1ff4754710984f2f94c5a101a10f57eb6ad49f7b1df47f7e8a5730014c0fb4e36c1b7968082d0355f318a67e265e5a5533ffcc01b939e4bf1779841b000000019315fe44f132446949cd9ea1f34ec20f32c0dd0ba4f732e563c040c692fd5d49010e6b6ae8956bd722b0f5e3f355adfc7c9b6a39542c07fb0bcd317d0eff8d99080001382db97fabb726a88e7a7baab050a1c9169d2142a9ce0b7f3022349acf74981a0175d44cdd04e213c2d95fb281f96fc7d734c65be70a77b7aa4fef8a4b6fb2475b015fe654f11132fc9c133b46dbbf19b0113cc45715f52dfd3282ede76f6880740c01d14f83f0fd7d09f4f52c8ae9d39c63b57c59a37666d211b9a2deb290808c02720001701df279d9a2270a82379486df546fafeaeb831993cde1cd9e5c9cb17be5191f01cc6ae86e9147b0b1c3f5fb32fb7acc012b4cb4384a1a1331ae3c2324a804483e00017fb2e2890b05355ba797af2f77e38cab3e8ec1623d29a912bdd0dc4a78cf554a0001de17a599d0c6d73eaf1a5939e95af4427f75b89f703749e00d853cce3d6af84f00014f6313837ed19d2b480ec531529fb6b425006f2c1d981077640be21627659410018c2d6adea2ad4faf20eccfc2c2a2c59192fb53d3204b3a2757f1c247dadec16b0001c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e", + "orchardTree": "013de3a4ad28b5df77a979fc925643e69b7dd26ba787a3122fcd6a445c47d9280e01b68287983d323c90e6a1ed5980ab5b1a846d49340ee6d40d2349795c132a382c1f0001c2db91c5b9baa1c09623429cb4005ae12e521a018eb8df2d051d6793a307eb3e01c6b2f8d93635310d470d4a6d011ea77f59e28bee6ffca3df88f5f2a98980331a01777860ffe739b8047045e2dff8ba77070666075214a0f7702568205410351b39019ed7c5c3e958cb8b9d5324c290ff384dc3ca6cbc870950002f64398478ff1904000114b5ad56c8f210854a1688f47116b5d272fea09559646cee33ad3e6958306a15000001110e689714d772b170f63bfbf4b144c6a48e0a57c4a2780513633d5c799a0d1300010cf94eaf4d5268d9e0878064a458eaa3363dfcfcf2602681c443baf989d5de20000000019776dec2ea06cc5ecd2d212d37023972f526cb2ffa7ce1e8cf8eb4ef04700b01000001c7146e487b3ae97b190ebf93eac554968e683d31115d13fe83dd620859d9a92d000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000" +} diff --git a/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1810000.json b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1810000.json new file mode 100644 index 00000000..e1edd90b --- /dev/null +++ b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1810000.json @@ -0,0 +1,8 @@ +{ + "network": "main", + "height": "1810000", + "hash": "0000000000bdf01be068ff1f0b21b8b266f839b31ed066b72888b67f672c9800", + "time": 1663283618, + "saplingTree": "019dbc466ad114f2b4a6d0d91198c0a2c5c20c1dec7c2e7f932c9f9197d76b80020019015b272118101cac0ee6b9b8cf26d5104ab42913d2f5253388bac28e7998f2c41b0001868d5008c587f0fa3d26fc42097d34df49a70508a85a6acccf1263d39cb62c28000001b2325a6ecbf023f3ce4b74c1c14bac8d9c462560dce9611bd7b08cd55ecf4b0100000001cd62e4e2142c664656f956fd0ea8d1de1027c327580398fe8912e6264801650201ccc9b305f6e65d641a4e461ea5e853354a3ac4b2acf2591849e00421bd58fb48000000013a591632f71e1fe214ab46b464112520e98f15d171da599a350f7d8dba79595d018d254447626cf40828102a60e2b433d05498a780599cdf56a14f3888c2f42008000148af2e64d92d1944a451180f1738c9f468f608525b0273967db19029b53ba16d0000000001f416eb7e062c981dbbf76f8845fda959b948bc742fc62d9edb2f36bae852ba4e01c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e", + "orchardTree": "0180c286284cd52360af960fce56fe7dc339667c35580c75e9bc339483230e932701260b8984058125b5e3558fb65172d59718427111cbc06891017cc7633f74e1001f00000000000001cde58ba8e982a34499406e06a763ede11353d39ab93a5af1b34905cf268c033701226087d6a9bb28c2ed6890b989224791093cd5012a27285040025c0a72b2ce06000001ba71f8a1897b754e9fe37a29eb2c1a93ddc1678298498b4a84da732ebf056f15018073f4aff677a24eaac68c20d271ea228041f1e77710b0504d3f6c0b71d63d24000146b37a3e6167ae7f07725ab4e32247619c37e2a91c87182dd68b8feb99d5a22201e18dad85447ef2e9b8d647c9b9f1e6cef3e1d03f908975cd5e1d5c5808e443010001898b4a8f384f342a67efb3f6c4afd87310df4ff1532b86ca8d1394975aab5a1e0001c7146e487b3ae97b190ebf93eac554968e683d31115d13fe83dd620859d9a92d000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000" +} diff --git a/ZcashLightClientKit.podspec b/ZcashLightClientKit.podspec index 9f9c6728..6b23ca6c 100644 --- a/ZcashLightClientKit.podspec +++ b/ZcashLightClientKit.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'ZcashLightClientKit' - s.version = '0.16.9-beta' + s.version = '0.16.10-beta' s.summary = 'Zcash Light Client wallet SDK for iOS' s.description = <<-DESC diff --git a/changelog.md b/changelog.md index 935b6aec..eacab72e 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,25 @@ +# 0.16.10-beta +- [#532] [0.16.x-beta] Download does not stop correctly + +Issue Reported: + +When the synchronizer is stopped, the processor does not cancel +the download correctly. Then when attempting to resume sync, the +synchronizer is not on `.stopped` and can't be resumed + +this doesn't appear to happen in `master` branch that uses +structured concurrency for operations. + +Fix: +This commit makes sure that the download streamer checks cancelation +before processing any block, or getting called back to report progress + +Checkpoints added: +Mainnet +```` +Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1807500.json +Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1810000.json +```` # 0.16.9-beta Checkpoints added: Mainnet