diff --git a/.swiftlint.yml b/.swiftlint.yml index 0d01815a..981379c6 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -1,4 +1,4 @@ -# This SwiftLint file is based on this great guideline. +# This SwiftLint file is based on this great guideline. # https://github.com/raywenderlich/swift-style-guide included: @@ -11,6 +11,7 @@ excluded: - Sources/ZcashLightClientKit/Modules/Service/GRPC/ProtoBuf/compact_formats.pb.swift - Sources/ZcashLightClientKit/Modules/Service/GRPC/ProtoBuf/service.pb.swift - Sources/ZcashLightClientKit/Modules/Service/GRPC/ProtoBuf/service.grpc.swift + - Sources/ZcashLightClientKit/Modules/Service/GRPC/ProtoBuf/proposal.pb.swift - Tests/ - ZcashLightClientKitTests/Constants.generated.swift - build/ @@ -25,7 +26,7 @@ disabled_rules: - todo - unused_capture_list - nesting # allow for types to be nested, common pattern in Swift - - multiple_closures_with_trailing_closure + - multiple_closures_with_trailing_closure - generic_type_name # allow for arbitrarily long generic type names - redundant_void_return - empty_parentheses_with_trailing_closure @@ -168,7 +169,7 @@ file_length: multiline_arguments: first_argument_location: next_line only_enforce_after_first_closure_on_first_line: true - + private_over_fileprivate: validate_extensions: true diff --git a/CHANGELOG.md b/CHANGELOG.md index dcb00bf3..a6bc3ade 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,22 @@ All notable changes to this library will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this library adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +# 2.0.7 - 2024-01-29 + +## Added +- `Model.ScanSummary` +- `Model.WalletSummary.{PoolBalance, AccountBalance, WalletSummary}` +- + +## Changed +- The `ZcashError` type has changed. + - Added variant `rustGetWalletSummary` + - Removed variants: + - `rustGetVerifiedBalance` (expect `rustGetWalletSummary` instead) + - `rustGetScanProgress` (expect `rustGetWalletSummary` instead) + - `rustGetBalance` (expect `rustGetWalletSummary` instead) +- The performance of `getWalletSummary` and `scanBlocks` have been improved. + # 2.0.6 - 2024-01-28 ## Changed @@ -12,7 +28,7 @@ and this library adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 We focused on performance of the synchronization and found out a root cause in progress reporting. Simple change reduced the synchronization significantly by reporting less frequently. This affect the UX a bit because the % of the sync is updated only every 500 scanned blocks instead of every 100. Proper solution is going to be handled in #1353. ### [#1351] Recover from block stream issues -Async block stream grpc calls sometimes fail with unknown error 14, most of the times represented as `Transport became inactive` or `NIOHTTP2.StreamClosed`. Unless the service is truly down, these errors are usually false positive ones. The SDK was able to recover from this error with the next sync triggered but it takes 10-30s to happen. This delay is unnecessary so we made 2 changes. When these errors are caught the next sync is triggered immediately (at most 3 times) + the error state is not passed to the clients. +Async block stream grpc calls sometimes fail with unknown error 14, most of the times represented as `Transport became inactive` or `NIOHTTP2.StreamClosed`. Unless the service is truly down, these errors are usually false positive ones. The SDK was able to recover from this error with the next sync triggered but it takes 10-30s to happen. This delay is unnecessary so we made 2 changes. When these errors are caught the next sync is triggered immediately (at most 3 times) + the error state is not passed to the clients. ## Checkpoints @@ -38,7 +54,7 @@ Sources/ZcashLightClientKit/Resources/checkpoints/testnet/2690000.json ## Added ### [#1336] Tweaks for sdk metrics -Shielded verified and total balances are logged for every sync of `SDKMetrics`. +Shielded verified and total balances are logged for every sync of `SDKMetrics`. ## Checkpoints @@ -51,12 +67,12 @@ Sources/ZcashLightClientKit/Resources/checkpoints/testnet/2630000.json # 2.0.4 - 2023-12-12 ## Changed -The `SDKMetrics` logs data using os_log. The public API `enableMetrics()` and `disableMetrics()` no longer exist. All metrics are automatically logged for every sync run. Extraction of the metrics is up to the client/dev - done by using `OSLogStore`. +The `SDKMetrics` logs data using os_log. The public API `enableMetrics()` and `disableMetrics()` no longer exist. All metrics are automatically logged for every sync run. Extraction of the metrics is up to the client/dev - done by using `OSLogStore`. ## Added ### [#1325] Log metrics -The sync process is measured and detailed metrics are logged for every sync run. The data are logged using os_log so any client can export it. Verbose logs are under `sdkLogs_default` category, `default` level. Sync specific logs use `error` level. +The sync process is measured and detailed metrics are logged for every sync run. The data are logged using os_log so any client can export it. Verbose logs are under `sdkLogs_default` category, `default` level. Sync specific logs use `error` level. ## Checkpoints @@ -85,7 +101,7 @@ Sources/ZcashLightClientKit/Resources/checkpoints/testnet/2620000.json The enhancing of the transactions now processes all the blocks suggested by scan ranges. The issue was that when new scan ranges were suggested the value that drives the enhancing range computation wasn't reset, so when higher ranges were processed, the lower ranges were skipped. This fix ensures all transaction data are properly set, as well as fixing eventStream `.foundTransaction` reporting. ### Fix incorrect note deduplication in v_transactions (librustzcash) -This is a fix in the rust layer. The amount sent in the transaction was incorrectly reported even though the actual amount was sent properly. Now clients should see the amount they expect to see in the UI. +This is a fix in the rust layer. The amount sent in the transaction was incorrectly reported even though the actual amount was sent properly. Now clients should see the amount they expect to see in the UI. ## Checkpoints @@ -111,12 +127,12 @@ Sources/ZcashLightClientKit/Resources/checkpoints/testnet/2550000.json ## Changed ### [#1303] Don't invalidate the timer with the error -The SDK has some simple logic of retrying when some erros occurs. There were 5 attempts of retry until the SDK stopped the synchronization process completely. (The timer is not restarted after those). That approach led to some annoying UX issue of manually starting the SDKSynchronizer from the client, shifting the responsibility to the devs/clients. This has been changed, the SDK never stops the timer unless `synchronizer.stop()` is called. +The SDK has some simple logic of retrying when some erros occurs. There were 5 attempts of retry until the SDK stopped the synchronization process completely. (The timer is not restarted after those). That approach led to some annoying UX issue of manually starting the SDKSynchronizer from the client, shifting the responsibility to the devs/clients. This has been changed, the SDK never stops the timer unless `synchronizer.stop()` is called. ## Fixed ### [#1301] foundTransactions don't emit after rewind -The `.foundTransactions` observed on eventStream worked well during the sync until the rewind was called. That API missed reset of the ActionContext in the CompactBlockProcesser and that led to never observing the same transactions again. This ticket fixed the problem, reset is called in the rewind and new sync passes the transactions to the stream. +The `.foundTransactions` observed on eventStream worked well during the sync until the rewind was called. That API missed reset of the ActionContext in the CompactBlockProcesser and that led to never observing the same transactions again. This ticket fixed the problem, reset is called in the rewind and new sync passes the transactions to the stream. # 2.0.1 - 2023-10-03 @@ -124,9 +140,9 @@ The `.foundTransactions` observed on eventStream worked well during the sync unt ### [#1294] Remove all uses of the incorrect 1000-ZAT fee The 1000 Zatoshi fee proposed in ZIP-313 is deprecated now and so the minimum is 10k Zatoshi, defined in ZIP-317. -The SDK has been cleaned up from deprecated fee but note, real fee is handled in a rust layer. +The SDK has been cleaned up from deprecated fee but note, real fee is handled in a rust layer. The public API `NetworkConstants.defaultFee(for: BlockHeight)` has been refactored to `NetworkConstants.defaultFee()`. - + # 2.0.0 - 2023-09-25 ## Notable Changes @@ -136,7 +152,7 @@ synchronization algorithm. ## Changed -Updated dependencies: +Updated dependencies: - `zcash-light-client-ffi 0.4.0` `CompactBlockProcessor` now processes compact blocks from the lightwalletd server with Spend-before-Sync algorithm (i.e. non-linear order). This feature shortens the time after which a wallet's spendable balance can be used. @@ -170,8 +186,8 @@ so both Height and Time don't make sense anymore. ### [#1230] Remove linear sync from the SDK - `latestScannedHeight` and `latestScannedTime` have been removed from the - SynchronizerState. -- The concept of pending transaction has changed: `func allPendingTransactions()` + SynchronizerState. +- The concept of pending transaction has changed: `func allPendingTransactions()` is no longer available. Use `public func allTransactions()` instead. # 0.22.0-beta diff --git a/Example/ZcashLightClientSample/ZcashLightClientSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Example/ZcashLightClientSample/ZcashLightClientSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index e67e0b6f..a8489d52 100644 --- a/Example/ZcashLightClientSample/ZcashLightClientSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Example/ZcashLightClientSample/ZcashLightClientSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/grpc/grpc-swift.git", "state" : { - "revision" : "84bac657e9930d26e9124bac082f26586dc2d209", - "version" : "1.19.1" + "revision" : "6ade19f0b57f5fc436dfecfced83f3c84d1095b9", + "version" : "1.21.0" } }, { @@ -68,8 +68,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-atomics.git", "state" : { - "revision" : "6c89474e62719ddcc1e9614989fff2f68208fe10", - "version" : "1.1.0" + "revision" : "cd142fd2f64be2100422d658e7411e39489da985", + "version" : "1.2.0" } }, { @@ -77,8 +77,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-collections.git", "state" : { - "revision" : "937e904258d22af6e447a0b72c0bc67583ef64a2", - "version" : "1.0.4" + "revision" : "d029d9d39c87bed85b1c50adee7c41795261a192", + "version" : "1.0.6" } }, { @@ -90,13 +90,22 @@ "version" : "2.6.0" } }, + { + "identity" : "swift-http-types", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-http-types", + "state" : { + "revision" : "12358d55a3824bd5fed310b999ea8cf83a9a1a65", + "version" : "1.0.3" + } + }, { "identity" : "swift-log", "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-log.git", "state" : { - "revision" : "532d8b529501fb73a2455b179e0bbb6d49b652ed", - "version" : "1.5.3" + "revision" : "e97a6fcb1ab07462881ac165fdbb37f067e205d5", + "version" : "1.5.4" } }, { @@ -104,8 +113,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio.git", "state" : { - "revision" : "3db5c4aeee8100d2db6f1eaf3864afdad5dc68fd", - "version" : "2.59.0" + "revision" : "635b2589494c97e48c62514bc8b37ced762e0a62", + "version" : "2.63.0" } }, { @@ -113,8 +122,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-extras.git", "state" : { - "revision" : "fb70a0f5e984f23be48b11b4f1909f3bee016178", - "version" : "1.19.1" + "revision" : "363da63c1966405764f380c627409b2f9d9e710b", + "version" : "1.21.0" } }, { @@ -122,8 +131,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-http2.git", "state" : { - "revision" : "a8ccf13fa62775277a5d56844878c828bbb3be1a", - "version" : "1.27.0" + "revision" : "0904bf0feb5122b7e5c3f15db7df0eabe623dd87", + "version" : "1.30.0" } }, { @@ -131,8 +140,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-ssl.git", "state" : { - "revision" : "320bd978cceb8e88c125dcbb774943a92f6286e9", - "version" : "2.25.0" + "revision" : "7c381eb6083542b124a6c18fae742f55001dc2b5", + "version" : "2.26.0" } }, { @@ -140,8 +149,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-transport-services.git", "state" : { - "revision" : "e7403c35ca6bb539a7ca353b91cc2d8ec0362d58", - "version" : "1.19.0" + "revision" : "6cbe0ed2b394f21ab0d46b9f0c50c6be964968ce", + "version" : "1.20.1" } }, { @@ -149,8 +158,17 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-protobuf.git", "state" : { - "revision" : "cf62cdaea48b77f1a631e5cb3aeda6047c2cba1d", - "version" : "1.23.0" + "revision" : "65e8f29b2d63c4e38e736b25c27b83e012159be8", + "version" : "1.25.2" + } + }, + { + "identity" : "swift-system", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-system.git", + "state" : { + "revision" : "025bcb1165deab2e20d4eaba79967ce73013f496", + "version" : "1.2.1" } }, { @@ -158,8 +176,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/zcash-hackworks/zcash-light-client-ffi", "state" : { - "revision" : "514dcd7e6fbfa252bf36d9f00d6b98f465a70704", - "version" : "0.4.1" + "revision" : "9a987c24240992a5eb3ff447e93f581d49a23acd", + "version" : "0.5.0" } } ], diff --git a/Package.resolved b/Package.resolved index 9962853c..12614023 100644 --- a/Package.resolved +++ b/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/grpc/grpc-swift.git", "state" : { - "revision" : "84bac657e9930d26e9124bac082f26586dc2d209", - "version" : "1.19.1" + "revision" : "6ade19f0b57f5fc436dfecfced83f3c84d1095b9", + "version" : "1.21.0" } }, { @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-atomics.git", "state" : { - "revision" : "6c89474e62719ddcc1e9614989fff2f68208fe10", - "version" : "1.1.0" + "revision" : "cd142fd2f64be2100422d658e7411e39489da985", + "version" : "1.2.0" } }, { @@ -32,8 +32,17 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-collections.git", "state" : { - "revision" : "937e904258d22af6e447a0b72c0bc67583ef64a2", - "version" : "1.0.4" + "revision" : "d029d9d39c87bed85b1c50adee7c41795261a192", + "version" : "1.0.6" + } + }, + { + "identity" : "swift-http-types", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-http-types", + "state" : { + "revision" : "12358d55a3824bd5fed310b999ea8cf83a9a1a65", + "version" : "1.0.3" } }, { @@ -41,8 +50,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-log.git", "state" : { - "revision" : "532d8b529501fb73a2455b179e0bbb6d49b652ed", - "version" : "1.5.3" + "revision" : "e97a6fcb1ab07462881ac165fdbb37f067e205d5", + "version" : "1.5.4" } }, { @@ -50,8 +59,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio.git", "state" : { - "revision" : "3db5c4aeee8100d2db6f1eaf3864afdad5dc68fd", - "version" : "2.59.0" + "revision" : "635b2589494c97e48c62514bc8b37ced762e0a62", + "version" : "2.63.0" } }, { @@ -59,8 +68,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-extras.git", "state" : { - "revision" : "fb70a0f5e984f23be48b11b4f1909f3bee016178", - "version" : "1.19.1" + "revision" : "363da63c1966405764f380c627409b2f9d9e710b", + "version" : "1.21.0" } }, { @@ -68,8 +77,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-http2.git", "state" : { - "revision" : "a8ccf13fa62775277a5d56844878c828bbb3be1a", - "version" : "1.27.0" + "revision" : "0904bf0feb5122b7e5c3f15db7df0eabe623dd87", + "version" : "1.30.0" } }, { @@ -77,8 +86,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-ssl.git", "state" : { - "revision" : "320bd978cceb8e88c125dcbb774943a92f6286e9", - "version" : "2.25.0" + "revision" : "7c381eb6083542b124a6c18fae742f55001dc2b5", + "version" : "2.26.0" } }, { @@ -86,8 +95,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-transport-services.git", "state" : { - "revision" : "e7403c35ca6bb539a7ca353b91cc2d8ec0362d58", - "version" : "1.19.0" + "revision" : "6cbe0ed2b394f21ab0d46b9f0c50c6be964968ce", + "version" : "1.20.1" } }, { @@ -95,8 +104,17 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-protobuf.git", "state" : { - "revision" : "cf62cdaea48b77f1a631e5cb3aeda6047c2cba1d", - "version" : "1.23.0" + "revision" : "65e8f29b2d63c4e38e736b25c27b83e012159be8", + "version" : "1.25.2" + } + }, + { + "identity" : "swift-system", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-system.git", + "state" : { + "revision" : "025bcb1165deab2e20d4eaba79967ce73013f496", + "version" : "1.2.1" } }, { @@ -104,8 +122,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/zcash-hackworks/zcash-light-client-ffi", "state" : { - "revision" : "9bc5877ef6302e877922f79ebead52e50bce94fd", - "version" : "0.4.0" + "revision" : "9a987c24240992a5eb3ff447e93f581d49a23acd", + "version" : "0.5.0" } } ], diff --git a/Package.swift b/Package.swift index 27f4c78f..3d73fd8d 100644 --- a/Package.swift +++ b/Package.swift @@ -16,7 +16,7 @@ let package = Package( dependencies: [ .package(url: "https://github.com/grpc/grpc-swift.git", from: "1.19.1"), .package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.14.1"), - .package(url: "https://github.com/zcash-hackworks/zcash-light-client-ffi", from: "0.4.1") + .package(url: "https://github.com/zcash-hackworks/zcash-light-client-ffi", exact: "0.5.0") ], targets: [ .target( @@ -28,6 +28,7 @@ let package = Package( ], exclude: [ "Modules/Service/GRPC/ProtoBuf/proto/compact_formats.proto", + "Modules/Service/GRPC/ProtoBuf/proto/proposal.proto", "Modules/Service/GRPC/ProtoBuf/proto/service.proto", "Error/Sourcery/" ], diff --git a/Sources/ZcashLightClientKit/Block/Actions/ScanAction.swift b/Sources/ZcashLightClientKit/Block/Actions/ScanAction.swift index b3ca1b31..fe7a4da7 100644 --- a/Sources/ZcashLightClientKit/Block/Actions/ScanAction.swift +++ b/Sources/ZcashLightClientKit/Block/Actions/ScanAction.swift @@ -63,7 +63,7 @@ extension ScanAction: Action { let incrementedProcessedHeight = processedHeight + BlockHeight(increment) await context.update(processedHeight: incrementedProcessedHeight) await self?.latestBlocksDataProvider.updateScannedData() - + // ScanAction is controlled locally so it must report back the updated scanned height await context.update(lastScannedHeight: lastScannedHeight) } @@ -74,7 +74,7 @@ extension ScanAction: Action { // TODO: [#1353] Advanced progress reporting, https://github.com/Electric-Coin-Company/zcash-swift-wallet-sdk/issues/1353 if progressReportReducer == 0 { // report scan progress only if it's available - if let scanProgress = try? await rustBackend.getScanProgress() { + if let scanProgress = try? await rustBackend.getWalletSummary()?.scanProgress { let progress = try scanProgress.progress() logger.debug("progress: \(progress)") await didUpdate(.syncProgress(progress)) diff --git a/Sources/ZcashLightClientKit/Block/CompactBlockProcessor.swift b/Sources/ZcashLightClientKit/Block/CompactBlockProcessor.swift index b69a7e3f..9669677c 100644 --- a/Sources/ZcashLightClientKit/Block/CompactBlockProcessor.swift +++ b/Sources/ZcashLightClientKit/Block/CompactBlockProcessor.swift @@ -658,13 +658,8 @@ extension CompactBlockProcessor { await send(event: .finished(lastScannedHeight)) await context.update(state: .finished) - let verifiedBalance = Zatoshi((try? await rustBackend.getVerifiedBalance(account: 0)) ?? 0) - let totalBalance = Zatoshi((try? await rustBackend.getBalance(account: 0)) ?? 0) - let shieldedBalance = WalletBalance( - verified: verifiedBalance, - total: totalBalance - ) - await metrics.logCBPOverviewReport(logger, shieldedBalance: shieldedBalance) + let walletSummary = try? await rustBackend.getWalletSummary() + await metrics.logCBPOverviewReport(logger, walletSummary: walletSummary) // If new blocks were mined during previous sync run the sync process again if newerBlocksWereMinedDuringSync { diff --git a/Sources/ZcashLightClientKit/Block/SaplingParameters/SaplingParametersHandler.swift b/Sources/ZcashLightClientKit/Block/SaplingParameters/SaplingParametersHandler.swift index 09746b6d..4538a225 100644 --- a/Sources/ZcashLightClientKit/Block/SaplingParameters/SaplingParametersHandler.swift +++ b/Sources/ZcashLightClientKit/Block/SaplingParameters/SaplingParametersHandler.swift @@ -1,6 +1,6 @@ // -// HandleSaplingParametersIfNeeded.swift -// +// SaplingParametersHandler.swift +// // // Created by Lukáš Korba on 23.11.2022. // @@ -28,11 +28,13 @@ extension SaplingParametersHandlerImpl: SaplingParametersHandler { try Task.checkCancellation() do { - let totalShieldedBalance = try await rustBackend.getBalance(account: Int32(0)) + let totalSaplingBalance = + try await rustBackend.getWalletSummary()?.accountBalances[0]?.saplingBalance.total().amount + ?? 0 let totalTransparentBalance = try await rustBackend.getTransparentBalance(account: Int32(0)) // Download Sapling parameters only if sapling funds are detected. - guard totalShieldedBalance > 0 || totalTransparentBalance > 0 else { return } + guard totalSaplingBalance > 0 || totalTransparentBalance > 0 else { return } } catch { // if sapling balance can't be detected of we fail to obtain the balance // for some reason we shall not proceed to download the parameters and diff --git a/Sources/ZcashLightClientKit/Block/Scan/BlockScanner.swift b/Sources/ZcashLightClientKit/Block/Scan/BlockScanner.swift index 9f9ccd93..cf203f6b 100644 --- a/Sources/ZcashLightClientKit/Block/Scan/BlockScanner.swift +++ b/Sources/ZcashLightClientKit/Block/Scan/BlockScanner.swift @@ -51,9 +51,12 @@ extension BlockScannerImpl: BlockScanner { let batchSize = UInt32(config.scanningBatchSize) + // TODO: [#1355] Do more with ScanSummary + // https://github.com/Electric-Coin-Company/zcash-swift-wallet-sdk/issues/1355 + let scanSummary: ScanSummary let scanStartTime = Date() do { - try await self.rustBackend.scanBlocks(fromHeight: Int32(startHeight), limit: batchSize) + scanSummary = try await self.rustBackend.scanBlocks(fromHeight: Int32(startHeight), limit: batchSize) } catch { logger.debug("block scanning failed with error: \(String(describing: error))") throw error @@ -61,10 +64,8 @@ extension BlockScannerImpl: BlockScanner { let scanFinishTime = Date() - // TODO: [#1259] potential bug when rustBackend.scanBlocks scan less blocks than batchSize, - // https://github.com/zcash/ZcashLightClientKit/issues/1259 - lastScannedHeight = startHeight + Int(batchSize) - 1 - + lastScannedHeight = scanSummary.scannedRange.upperBound - 1 + scannedNewBlocks = previousScannedHeight != lastScannedHeight if scannedNewBlocks { try await didScan(lastScannedHeight, batchSize) diff --git a/Sources/ZcashLightClientKit/Error/ZcashError.swift b/Sources/ZcashLightClientKit/Error/ZcashError.swift index 1bb52cc6..f2fce14c 100644 --- a/Sources/ZcashLightClientKit/Error/ZcashError.swift +++ b/Sources/ZcashLightClientKit/Error/ZcashError.swift @@ -121,11 +121,6 @@ public enum ZcashError: Equatable, Error { /// - `rustError` contains error generated by the rust layer. /// ZRUST0003 case rustDecryptAndStoreTransaction(_ rustError: String) - /// Error from rust layer when calling ZcashRustBackend.getBalance - /// - `account` is account passed to ZcashRustBackend.getBalance. - /// - `rustError` contains error generated by the rust layer. - /// ZRUST0004 - case rustGetBalance(_ account: Int, _ rustError: String) /// Error from rust layer when calling ZcashRustBackend.getCurrentAddress /// - `rustError` contains error generated by the rust layer. /// ZRUST0005 @@ -153,11 +148,6 @@ public enum ZcashError: Equatable, Error { /// - `rustError` contains error generated by the rust layer. /// ZRUST0011 case rustGetTransparentBalance(_ account: Int, _ rustError: String) - /// Error from rust layer when calling ZcashRustBackend.getVerifiedBalance - /// - `account` is account passed to ZcashRustBackend.getVerifiedBalance. - /// - `rustError` contains error generated by the rust layer. - /// ZRUST0012 - case rustGetVerifiedBalance(_ account: Int, _ rustError: String) /// account parameter is lower than 0 when calling ZcashRustBackend.getVerifiedTransparentBalance /// - `account` is account passed to ZcashRustBackend.getVerifiedTransparentBalance. /// ZRUST0013 @@ -297,10 +287,6 @@ public enum ZcashError: Equatable, Error { /// Invalid transaction ID length when calling ZcashRustBackend.getMemo. txId must be 32 bytes. /// ZRUST0050 case rustGetMemoInvalidTxIdLength - /// Error from rust layer when calling ZcashRustBackend.getScanProgress - /// - `rustError` contains error generated by the rust layer. - /// ZRUST0051 - case rustGetScanProgress(_ rustError: String) /// Error from rust layer when calling ZcashRustBackend.fullyScannedHeight /// - `rustError` contains error generated by the rust layer. /// ZRUST0052 @@ -317,6 +303,10 @@ public enum ZcashError: Equatable, Error { /// - `progress` value reported /// ZRUST0055 case rustScanProgressOutOfRange(_ progress: String) + /// Error from rust layer when calling ZcashRustBackend.getWalletSummary + /// - `rustError` contains error generated by the rust layer. + /// ZRUST0056 + case rustGetWalletSummary(_ rustError: String) /// SQLite query failed when fetching all accounts from the database. /// - `sqliteError` is error produced by SQLite library. /// ZADAO0001 @@ -614,7 +604,6 @@ public enum ZcashError: Equatable, Error { case .rustCreateAccount: return "Error from rust layer when calling ZcashRustBackend.createAccount" case .rustCreateToAddress: return "Error from rust layer when calling ZcashRustBackend.createToAddress" case .rustDecryptAndStoreTransaction: return "Error from rust layer when calling ZcashRustBackend.decryptAndStoreTransaction" - case .rustGetBalance: return "Error from rust layer when calling ZcashRustBackend.getBalance" case .rustGetCurrentAddress: return "Error from rust layer when calling ZcashRustBackend.getCurrentAddress" case .rustGetCurrentAddressInvalidAddress: return "Unified address generated by rust layer is invalid when calling ZcashRustBackend.getCurrentAddress" case .rustGetNearestRewindHeight: return "Error from rust layer when calling ZcashRustBackend.getNearestRewindHeight" @@ -622,7 +611,6 @@ public enum ZcashError: Equatable, Error { case .rustGetNextAvailableAddressInvalidAddress: return "Unified address generated by rust layer is invalid when calling ZcashRustBackend.getNextAvailableAddress" case .rustGetTransparentBalanceNegativeAccount: return "account parameter is lower than 0 when calling ZcashRustBackend.getTransparentBalance" case .rustGetTransparentBalance: return "Error from rust layer when calling ZcashRustBackend.getTransparentBalance" - case .rustGetVerifiedBalance: return "Error from rust layer when calling ZcashRustBackend.getVerifiedBalance" case .rustGetVerifiedTransparentBalanceNegativeAccount: return "account parameter is lower than 0 when calling ZcashRustBackend.getVerifiedTransparentBalance" case .rustGetVerifiedTransparentBalance: return "Error from rust layer when calling ZcashRustBackend.getVerifiedTransparentBalance" case .rustInitDataDb: return "Error from rust layer when calling ZcashRustBackend.initDataDb" @@ -660,11 +648,11 @@ public enum ZcashError: Equatable, Error { case .rustUpdateChainTip: return "Error from rust layer when calling ZcashRustBackend.updateChainTip" case .rustSuggestScanRanges: return "Error from rust layer when calling ZcashRustBackend.suggestScanRanges" case .rustGetMemoInvalidTxIdLength: return "Invalid transaction ID length when calling ZcashRustBackend.getMemo. txId must be 32 bytes." - case .rustGetScanProgress: return "Error from rust layer when calling ZcashRustBackend.getScanProgress" case .rustFullyScannedHeight: return "Error from rust layer when calling ZcashRustBackend.fullyScannedHeight" case .rustMaxScannedHeight: return "Error from rust layer when calling ZcashRustBackend.maxScannedHeight" case .rustLatestCachedBlockHeight: return "Error from rust layer when calling ZcashRustBackend.latestCachedBlockHeight" case .rustScanProgressOutOfRange: return "Rust layer's call ZcashRustBackend.getScanProgress returned values that after computation are outside of allowed range 0-100%." + case .rustGetWalletSummary: return "Error from rust layer when calling ZcashRustBackend.getWalletSummary" case .accountDAOGetAll: return "SQLite query failed when fetching all accounts from the database." case .accountDAOGetAllCantDecode: return "Fetched accounts from SQLite but can't decode them." case .accountDAOFindBy: return "SQLite query failed when seaching for accounts in the database." @@ -785,7 +773,6 @@ public enum ZcashError: Equatable, Error { case .rustCreateAccount: return .rustCreateAccount case .rustCreateToAddress: return .rustCreateToAddress case .rustDecryptAndStoreTransaction: return .rustDecryptAndStoreTransaction - case .rustGetBalance: return .rustGetBalance case .rustGetCurrentAddress: return .rustGetCurrentAddress case .rustGetCurrentAddressInvalidAddress: return .rustGetCurrentAddressInvalidAddress case .rustGetNearestRewindHeight: return .rustGetNearestRewindHeight @@ -793,7 +780,6 @@ public enum ZcashError: Equatable, Error { case .rustGetNextAvailableAddressInvalidAddress: return .rustGetNextAvailableAddressInvalidAddress case .rustGetTransparentBalanceNegativeAccount: return .rustGetTransparentBalanceNegativeAccount case .rustGetTransparentBalance: return .rustGetTransparentBalance - case .rustGetVerifiedBalance: return .rustGetVerifiedBalance case .rustGetVerifiedTransparentBalanceNegativeAccount: return .rustGetVerifiedTransparentBalanceNegativeAccount case .rustGetVerifiedTransparentBalance: return .rustGetVerifiedTransparentBalance case .rustInitDataDb: return .rustInitDataDb @@ -831,11 +817,11 @@ public enum ZcashError: Equatable, Error { case .rustUpdateChainTip: return .rustUpdateChainTip case .rustSuggestScanRanges: return .rustSuggestScanRanges case .rustGetMemoInvalidTxIdLength: return .rustGetMemoInvalidTxIdLength - case .rustGetScanProgress: return .rustGetScanProgress case .rustFullyScannedHeight: return .rustFullyScannedHeight case .rustMaxScannedHeight: return .rustMaxScannedHeight case .rustLatestCachedBlockHeight: return .rustLatestCachedBlockHeight case .rustScanProgressOutOfRange: return .rustScanProgressOutOfRange + case .rustGetWalletSummary: return .rustGetWalletSummary case .accountDAOGetAll: return .accountDAOGetAll case .accountDAOGetAllCantDecode: return .accountDAOGetAllCantDecode case .accountDAOFindBy: return .accountDAOFindBy diff --git a/Sources/ZcashLightClientKit/Error/ZcashErrorCode.swift b/Sources/ZcashLightClientKit/Error/ZcashErrorCode.swift index a093770b..1047244a 100644 --- a/Sources/ZcashLightClientKit/Error/ZcashErrorCode.swift +++ b/Sources/ZcashLightClientKit/Error/ZcashErrorCode.swift @@ -71,8 +71,6 @@ public enum ZcashErrorCode: String { case rustCreateToAddress = "ZRUST0002" /// Error from rust layer when calling ZcashRustBackend.decryptAndStoreTransaction case rustDecryptAndStoreTransaction = "ZRUST0003" - /// Error from rust layer when calling ZcashRustBackend.getBalance - case rustGetBalance = "ZRUST0004" /// Error from rust layer when calling ZcashRustBackend.getCurrentAddress case rustGetCurrentAddress = "ZRUST0005" /// Unified address generated by rust layer is invalid when calling ZcashRustBackend.getCurrentAddress @@ -87,8 +85,6 @@ public enum ZcashErrorCode: String { case rustGetTransparentBalanceNegativeAccount = "ZRUST0010" /// Error from rust layer when calling ZcashRustBackend.getTransparentBalance case rustGetTransparentBalance = "ZRUST0011" - /// Error from rust layer when calling ZcashRustBackend.getVerifiedBalance - case rustGetVerifiedBalance = "ZRUST0012" /// account parameter is lower than 0 when calling ZcashRustBackend.getVerifiedTransparentBalance case rustGetVerifiedTransparentBalanceNegativeAccount = "ZRUST0013" /// Error from rust layer when calling ZcashRustBackend.getVerifiedTransparentBalance @@ -163,8 +159,6 @@ public enum ZcashErrorCode: String { case rustSuggestScanRanges = "ZRUST0049" /// Invalid transaction ID length when calling ZcashRustBackend.getMemo. txId must be 32 bytes. case rustGetMemoInvalidTxIdLength = "ZRUST0050" - /// Error from rust layer when calling ZcashRustBackend.getScanProgress - case rustGetScanProgress = "ZRUST0051" /// Error from rust layer when calling ZcashRustBackend.fullyScannedHeight case rustFullyScannedHeight = "ZRUST0052" /// Error from rust layer when calling ZcashRustBackend.maxScannedHeight @@ -173,6 +167,8 @@ public enum ZcashErrorCode: String { case rustLatestCachedBlockHeight = "ZRUST0054" /// Rust layer's call ZcashRustBackend.getScanProgress returned values that after computation are outside of allowed range 0-100%. case rustScanProgressOutOfRange = "ZRUST0055" + /// Error from rust layer when calling ZcashRustBackend.getWalletSummary + case rustGetWalletSummary = "ZRUST0056" /// SQLite query failed when fetching all accounts from the database. case accountDAOGetAll = "ZADAO0001" /// Fetched accounts from SQLite but can't decode them. diff --git a/Sources/ZcashLightClientKit/Metrics/SDKMetrics.swift b/Sources/ZcashLightClientKit/Metrics/SDKMetrics.swift index 1b64be21..28176331 100644 --- a/Sources/ZcashLightClientKit/Metrics/SDKMetrics.swift +++ b/Sources/ZcashLightClientKit/Metrics/SDKMetrics.swift @@ -12,7 +12,7 @@ protocol SDKMetrics { func actionStart(_ action: CBPState) func actionDetail(_ detail: String, `for` action: CBPState) func actionStop() - func logCBPOverviewReport(_ logger: Logger, shieldedBalance: WalletBalance) async + func logCBPOverviewReport(_ logger: Logger, walletSummary: WalletSummary?) async } final class SDKMetricsImpl: SDKMetrics { @@ -102,15 +102,16 @@ final class SDKMetricsImpl: SDKMetrics { } // swiftlint:disable string_concatenation - func logCBPOverviewReport(_ logger: Logger, shieldedBalance: WalletBalance) async { + func logCBPOverviewReport(_ logger: Logger, walletSummary: WalletSummary?) async { actionStop() + let accountBalance = walletSummary?.accountBalances[0] logger.sync( """ SYNC (\(syncs)) REPORT finished in: \(Date().timeIntervalSince1970 - cbpStartTime) - verified balance: \(shieldedBalance.verified.amount) - total balance: \(shieldedBalance.total.amount) + verified balance: \(accountBalance?.saplingBalance.spendableValue.amount ?? 0) + total balance: \(accountBalance?.saplingBalance.total().amount ?? 0) """ ) diff --git a/Sources/ZcashLightClientKit/Model/ScanSummary.swift b/Sources/ZcashLightClientKit/Model/ScanSummary.swift new file mode 100644 index 00000000..eac3c3ae --- /dev/null +++ b/Sources/ZcashLightClientKit/Model/ScanSummary.swift @@ -0,0 +1,14 @@ +// +// ScanSummary.swift +// +// +// Created by Jack Grigg on 26/01/2024. +// + +import Foundation + +struct ScanSummary: Equatable { + let scannedRange: Range + let spentSaplingNoteCount: UInt64 + let receivedSaplingNoteCount: UInt64 +} diff --git a/Sources/ZcashLightClientKit/Model/ScanProgress.swift b/Sources/ZcashLightClientKit/Model/WalletSummary.swift similarity index 53% rename from Sources/ZcashLightClientKit/Model/ScanProgress.swift rename to Sources/ZcashLightClientKit/Model/WalletSummary.swift index 5a897207..0b8db4c4 100644 --- a/Sources/ZcashLightClientKit/Model/ScanProgress.swift +++ b/Sources/ZcashLightClientKit/Model/WalletSummary.swift @@ -1,12 +1,27 @@ // -// ScanProgress.swift -// +// WalletSummary.swift +// // // Created by Jack Grigg on 06/09/2023. // import Foundation +struct PoolBalance: Equatable { + let spendableValue: Zatoshi + let changePendingConfirmation: Zatoshi + let valuePendingSpendability: Zatoshi + + func total() -> Zatoshi { + self.spendableValue + self.changePendingConfirmation + self.valuePendingSpendability + } +} + +struct AccountBalance: Equatable { + let saplingBalance: PoolBalance + let unshielded: Zatoshi +} + struct ScanProgress: Equatable { let numerator: UInt64 let denominator: UInt64 @@ -27,3 +42,11 @@ struct ScanProgress: Equatable { return value } } + +struct WalletSummary: Equatable { + let accountBalances: [UInt32: AccountBalance] + let chainTipHeight: BlockHeight + let fullyScannedHeight: BlockHeight + let scanProgress: ScanProgress? + let nextSaplingSubtreeIndex: UInt32 +} diff --git a/Sources/ZcashLightClientKit/Modules/Service/GRPC/ProtoBuf/proposal.pb.swift b/Sources/ZcashLightClientKit/Modules/Service/GRPC/ProtoBuf/proposal.pb.swift new file mode 100644 index 00000000..3a7e3701 --- /dev/null +++ b/Sources/ZcashLightClientKit/Modules/Service/GRPC/ProtoBuf/proposal.pb.swift @@ -0,0 +1,548 @@ +// DO NOT EDIT. +// swift-format-ignore-file +// +// Generated by the Swift generator plugin for the protocol buffer compiler. +// Source: proto/proposal.proto +// +// For information on using the generated types, please see the documentation: +// https://github.com/apple/swift-protobuf/ + +// Copyright (c) 2023 The Zcash developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php . + +import Foundation +import SwiftProtobuf + +// If the compiler emits an error on this type, it is because this file +// was generated by a version of the `protoc` Swift plug-in that is +// incompatible with the version of SwiftProtobuf to which you are linking. +// Please ensure that you are building against the same version of the API +// that was used to generate this file. +fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { + struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} + typealias Version = _2 +} + +enum FfiValuePool: SwiftProtobuf.Enum { + typealias RawValue = Int + + /// Protobuf requires that enums have a zero discriminant as the default + /// value. However, we need to require that a known value pool is selected, + /// and we do not want to fall back to any default, so sending the + /// PoolNotSpecified value will be treated as an error. + case poolNotSpecified // = 0 + + /// The transparent value pool (P2SH is not distinguished from P2PKH) + case transparent // = 1 + + /// The Sapling value pool + case sapling // = 2 + + /// The Orchard value pool + case orchard // = 3 + case UNRECOGNIZED(Int) + + init() { + self = .poolNotSpecified + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .poolNotSpecified + case 1: self = .transparent + case 2: self = .sapling + case 3: self = .orchard + default: self = .UNRECOGNIZED(rawValue) + } + } + + var rawValue: Int { + switch self { + case .poolNotSpecified: return 0 + case .transparent: return 1 + case .sapling: return 2 + case .orchard: return 3 + case .UNRECOGNIZED(let i): return i + } + } + +} + +#if swift(>=4.2) + +extension FfiValuePool: CaseIterable { + // The compiler won't synthesize support with the UNRECOGNIZED case. + static let allCases: [FfiValuePool] = [ + .poolNotSpecified, + .transparent, + .sapling, + .orchard, + ] +} + +#endif // swift(>=4.2) + +/// The fee rule used in constructing a Proposal +enum FfiFeeRule: SwiftProtobuf.Enum { + typealias RawValue = Int + + /// Protobuf requires that enums have a zero discriminant as the default + /// value. However, we need to require that a known fee rule is selected, + /// and we do not want to fall back to any default, so sending the + /// FeeRuleNotSpecified value will be treated as an error. + case notSpecified // = 0 + + /// 10000 ZAT + case preZip313 // = 1 + + /// 1000 ZAT + case zip313 // = 2 + + /// MAX(10000, 5000 * logical_actions) ZAT + case zip317 // = 3 + case UNRECOGNIZED(Int) + + init() { + self = .notSpecified + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .notSpecified + case 1: self = .preZip313 + case 2: self = .zip313 + case 3: self = .zip317 + default: self = .UNRECOGNIZED(rawValue) + } + } + + var rawValue: Int { + switch self { + case .notSpecified: return 0 + case .preZip313: return 1 + case .zip313: return 2 + case .zip317: return 3 + case .UNRECOGNIZED(let i): return i + } + } + +} + +#if swift(>=4.2) + +extension FfiFeeRule: CaseIterable { + // The compiler won't synthesize support with the UNRECOGNIZED case. + static let allCases: [FfiFeeRule] = [ + .notSpecified, + .preZip313, + .zip313, + .zip317, + ] +} + +#endif // swift(>=4.2) + +/// A data structure that describes the inputs to be consumed and outputs to +/// be produced in a proposed transaction. +struct FfiProposal { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var protoVersion: UInt32 = 0 + + /// ZIP 321 serialized transaction request + var transactionRequest: String = String() + + /// The anchor height to be used in creating the transaction, if any. + /// Setting the anchor height to zero will disallow the use of any shielded + /// inputs. + var anchorHeight: UInt32 = 0 + + /// The inputs to be used in creating the transaction. + var inputs: [FfiProposedInput] = [] + + /// The total value, fee value, and change outputs of the proposed + /// transaction + var balance: FfiTransactionBalance { + get {return _balance ?? FfiTransactionBalance()} + set {_balance = newValue} + } + /// Returns true if `balance` has been explicitly set. + var hasBalance: Bool {return self._balance != nil} + /// Clears the value of `balance`. Subsequent reads from it will return its default value. + mutating func clearBalance() {self._balance = nil} + + /// The fee rule used in constructing this proposal + var feeRule: FfiFeeRule = .notSpecified + + /// The target height for which the proposal was constructed + /// + /// The chain must contain at least this many blocks in order for the proposal to + /// be executed. + var minTargetHeight: UInt32 = 0 + + /// A flag indicating whether the proposal is for a shielding transaction, + /// used for determining which OVK to select for wallet-internal outputs. + var isShielding: Bool = false + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _balance: FfiTransactionBalance? = nil +} + +/// The unique identifier and value for each proposed input. +struct FfiProposedInput { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var txid: Data = Data() + + var valuePool: FfiValuePool = .poolNotSpecified + + var index: UInt32 = 0 + + var value: UInt64 = 0 + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +/// The proposed change outputs and fee value. +struct FfiTransactionBalance { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var proposedChange: [FfiChangeValue] = [] + + var feeRequired: UInt64 = 0 + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +/// A proposed change output. If the transparent value pool is selected, +/// the `memo` field must be null. +struct FfiChangeValue { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var value: UInt64 = 0 + + var valuePool: FfiValuePool = .poolNotSpecified + + var memo: FfiMemoBytes { + get {return _memo ?? FfiMemoBytes()} + set {_memo = newValue} + } + /// Returns true if `memo` has been explicitly set. + var hasMemo: Bool {return self._memo != nil} + /// Clears the value of `memo`. Subsequent reads from it will return its default value. + mutating func clearMemo() {self._memo = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _memo: FfiMemoBytes? = nil +} + +/// An object wrapper for memo bytes, to facilitate representing the +/// `change_memo == None` case. +struct FfiMemoBytes { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var value: Data = Data() + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +#if swift(>=5.5) && canImport(_Concurrency) +extension FfiValuePool: @unchecked Sendable {} +extension FfiFeeRule: @unchecked Sendable {} +extension FfiProposal: @unchecked Sendable {} +extension FfiProposedInput: @unchecked Sendable {} +extension FfiTransactionBalance: @unchecked Sendable {} +extension FfiChangeValue: @unchecked Sendable {} +extension FfiMemoBytes: @unchecked Sendable {} +#endif // swift(>=5.5) && canImport(_Concurrency) + +// MARK: - Code below here is support for the SwiftProtobuf runtime. + +fileprivate let _protobuf_package = "cash.z.wallet.sdk.ffi" + +extension FfiValuePool: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "PoolNotSpecified"), + 1: .same(proto: "Transparent"), + 2: .same(proto: "Sapling"), + 3: .same(proto: "Orchard"), + ] +} + +extension FfiFeeRule: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "FeeRuleNotSpecified"), + 1: .same(proto: "PreZip313"), + 2: .same(proto: "Zip313"), + 3: .same(proto: "Zip317"), + ] +} + +extension FfiProposal: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".Proposal" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "protoVersion"), + 2: .same(proto: "transactionRequest"), + 3: .same(proto: "anchorHeight"), + 4: .same(proto: "inputs"), + 5: .same(proto: "balance"), + 6: .same(proto: "feeRule"), + 7: .same(proto: "minTargetHeight"), + 8: .same(proto: "isShielding"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt32Field(value: &self.protoVersion) }() + case 2: try { try decoder.decodeSingularStringField(value: &self.transactionRequest) }() + case 3: try { try decoder.decodeSingularUInt32Field(value: &self.anchorHeight) }() + case 4: try { try decoder.decodeRepeatedMessageField(value: &self.inputs) }() + case 5: try { try decoder.decodeSingularMessageField(value: &self._balance) }() + case 6: try { try decoder.decodeSingularEnumField(value: &self.feeRule) }() + case 7: try { try decoder.decodeSingularUInt32Field(value: &self.minTargetHeight) }() + case 8: try { try decoder.decodeSingularBoolField(value: &self.isShielding) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if self.protoVersion != 0 { + try visitor.visitSingularUInt32Field(value: self.protoVersion, fieldNumber: 1) + } + if !self.transactionRequest.isEmpty { + try visitor.visitSingularStringField(value: self.transactionRequest, fieldNumber: 2) + } + if self.anchorHeight != 0 { + try visitor.visitSingularUInt32Field(value: self.anchorHeight, fieldNumber: 3) + } + if !self.inputs.isEmpty { + try visitor.visitRepeatedMessageField(value: self.inputs, fieldNumber: 4) + } + try { if let v = self._balance { + try visitor.visitSingularMessageField(value: v, fieldNumber: 5) + } }() + if self.feeRule != .notSpecified { + try visitor.visitSingularEnumField(value: self.feeRule, fieldNumber: 6) + } + if self.minTargetHeight != 0 { + try visitor.visitSingularUInt32Field(value: self.minTargetHeight, fieldNumber: 7) + } + if self.isShielding != false { + try visitor.visitSingularBoolField(value: self.isShielding, fieldNumber: 8) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: FfiProposal, rhs: FfiProposal) -> Bool { + if lhs.protoVersion != rhs.protoVersion {return false} + if lhs.transactionRequest != rhs.transactionRequest {return false} + if lhs.anchorHeight != rhs.anchorHeight {return false} + if lhs.inputs != rhs.inputs {return false} + if lhs._balance != rhs._balance {return false} + if lhs.feeRule != rhs.feeRule {return false} + if lhs.minTargetHeight != rhs.minTargetHeight {return false} + if lhs.isShielding != rhs.isShielding {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension FfiProposedInput: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".ProposedInput" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "txid"), + 2: .same(proto: "valuePool"), + 3: .same(proto: "index"), + 4: .same(proto: "value"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularBytesField(value: &self.txid) }() + case 2: try { try decoder.decodeSingularEnumField(value: &self.valuePool) }() + case 3: try { try decoder.decodeSingularUInt32Field(value: &self.index) }() + case 4: try { try decoder.decodeSingularUInt64Field(value: &self.value) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if !self.txid.isEmpty { + try visitor.visitSingularBytesField(value: self.txid, fieldNumber: 1) + } + if self.valuePool != .poolNotSpecified { + try visitor.visitSingularEnumField(value: self.valuePool, fieldNumber: 2) + } + if self.index != 0 { + try visitor.visitSingularUInt32Field(value: self.index, fieldNumber: 3) + } + if self.value != 0 { + try visitor.visitSingularUInt64Field(value: self.value, fieldNumber: 4) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: FfiProposedInput, rhs: FfiProposedInput) -> Bool { + if lhs.txid != rhs.txid {return false} + if lhs.valuePool != rhs.valuePool {return false} + if lhs.index != rhs.index {return false} + if lhs.value != rhs.value {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension FfiTransactionBalance: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".TransactionBalance" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "proposedChange"), + 2: .same(proto: "feeRequired"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeRepeatedMessageField(value: &self.proposedChange) }() + case 2: try { try decoder.decodeSingularUInt64Field(value: &self.feeRequired) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if !self.proposedChange.isEmpty { + try visitor.visitRepeatedMessageField(value: self.proposedChange, fieldNumber: 1) + } + if self.feeRequired != 0 { + try visitor.visitSingularUInt64Field(value: self.feeRequired, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: FfiTransactionBalance, rhs: FfiTransactionBalance) -> Bool { + if lhs.proposedChange != rhs.proposedChange {return false} + if lhs.feeRequired != rhs.feeRequired {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension FfiChangeValue: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".ChangeValue" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "value"), + 2: .same(proto: "valuePool"), + 3: .same(proto: "memo"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt64Field(value: &self.value) }() + case 2: try { try decoder.decodeSingularEnumField(value: &self.valuePool) }() + case 3: try { try decoder.decodeSingularMessageField(value: &self._memo) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if self.value != 0 { + try visitor.visitSingularUInt64Field(value: self.value, fieldNumber: 1) + } + if self.valuePool != .poolNotSpecified { + try visitor.visitSingularEnumField(value: self.valuePool, fieldNumber: 2) + } + try { if let v = self._memo { + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: FfiChangeValue, rhs: FfiChangeValue) -> Bool { + if lhs.value != rhs.value {return false} + if lhs.valuePool != rhs.valuePool {return false} + if lhs._memo != rhs._memo {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension FfiMemoBytes: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".MemoBytes" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "value"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularBytesField(value: &self.value) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if !self.value.isEmpty { + try visitor.visitSingularBytesField(value: self.value, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: FfiMemoBytes, rhs: FfiMemoBytes) -> Bool { + if lhs.value != rhs.value {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} diff --git a/Sources/ZcashLightClientKit/Modules/Service/GRPC/ProtoBuf/proto/proposal.proto b/Sources/ZcashLightClientKit/Modules/Service/GRPC/ProtoBuf/proto/proposal.proto new file mode 100644 index 00000000..3ebee76f --- /dev/null +++ b/Sources/ZcashLightClientKit/Modules/Service/GRPC/ProtoBuf/proto/proposal.proto @@ -0,0 +1,91 @@ +// Copyright (c) 2023 The Zcash developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php . + +syntax = "proto3"; +package cash.z.wallet.sdk.ffi; +option swift_prefix = "Ffi"; + +// A data structure that describes the inputs to be consumed and outputs to +// be produced in a proposed transaction. +message Proposal { + uint32 protoVersion = 1; + // ZIP 321 serialized transaction request + string transactionRequest = 2; + // The anchor height to be used in creating the transaction, if any. + // Setting the anchor height to zero will disallow the use of any shielded + // inputs. + uint32 anchorHeight = 3; + // The inputs to be used in creating the transaction. + repeated ProposedInput inputs = 4; + // The total value, fee value, and change outputs of the proposed + // transaction + TransactionBalance balance = 5; + // The fee rule used in constructing this proposal + FeeRule feeRule = 6; + // The target height for which the proposal was constructed + // + // The chain must contain at least this many blocks in order for the proposal to + // be executed. + uint32 minTargetHeight = 7; + // A flag indicating whether the proposal is for a shielding transaction, + // used for determining which OVK to select for wallet-internal outputs. + bool isShielding = 8; +} + +enum ValuePool { + // Protobuf requires that enums have a zero discriminant as the default + // value. However, we need to require that a known value pool is selected, + // and we do not want to fall back to any default, so sending the + // PoolNotSpecified value will be treated as an error. + PoolNotSpecified = 0; + // The transparent value pool (P2SH is not distinguished from P2PKH) + Transparent = 1; + // The Sapling value pool + Sapling = 2; + // The Orchard value pool + Orchard = 3; +} + +// The unique identifier and value for each proposed input. +message ProposedInput { + bytes txid = 1; + ValuePool valuePool = 2; + uint32 index = 3; + uint64 value = 4; +} + +// The fee rule used in constructing a Proposal +enum FeeRule { + // Protobuf requires that enums have a zero discriminant as the default + // value. However, we need to require that a known fee rule is selected, + // and we do not want to fall back to any default, so sending the + // FeeRuleNotSpecified value will be treated as an error. + FeeRuleNotSpecified = 0; + // 10000 ZAT + PreZip313 = 1; + // 1000 ZAT + Zip313 = 2; + // MAX(10000, 5000 * logical_actions) ZAT + Zip317 = 3; +} + +// The proposed change outputs and fee value. +message TransactionBalance { + repeated ChangeValue proposedChange = 1; + uint64 feeRequired = 2; +} + +// A proposed change output. If the transparent value pool is selected, +// the `memo` field must be null. +message ChangeValue { + uint64 value = 1; + ValuePool valuePool = 2; + MemoBytes memo = 3; +} + +// An object wrapper for memo bytes, to facilitate representing the +// `change_memo == None` case. +message MemoBytes { + bytes value = 1; +} diff --git a/Sources/ZcashLightClientKit/Rust/ZcashRustBackend.swift b/Sources/ZcashLightClientKit/Rust/ZcashRustBackend.swift index 6c5083c1..100c0944 100644 --- a/Sources/ZcashLightClientKit/Rust/ZcashRustBackend.swift +++ b/Sources/ZcashLightClientKit/Rust/ZcashRustBackend.swift @@ -80,45 +80,36 @@ actor ZcashRustBackend: ZcashRustBackendWelding { return ffiBinaryKeyPtr.pointee.unsafeToUnifiedSpendingKey(network: networkType) } - func createToAddress( - usk: UnifiedSpendingKey, + func proposeTransfer( + account: Int32, to address: String, value: Int64, memo: MemoBytes? - ) async throws -> Data { - var contiguousTxIdBytes = ContiguousArray([UInt8](repeating: 0x0, count: 32)) - + ) async throws -> FfiProposal { globalDBLock.lock() - let success = contiguousTxIdBytes.withUnsafeMutableBufferPointer { txIdBytePtr in - usk.bytes.withUnsafeBufferPointer { uskPtr in - zcashlc_create_to_address( - dbData.0, - dbData.1, - uskPtr.baseAddress, - UInt(usk.bytes.count), - [CChar](address.utf8CString), - value, - memo?.bytes, - spendParamsPath.0, - spendParamsPath.1, - outputParamsPath.0, - outputParamsPath.1, - networkType.networkId, - minimumConfirmations, - useZIP317Fees, - txIdBytePtr.baseAddress - ) - } - } + let proposal = zcashlc_propose_transfer( + dbData.0, + dbData.1, + account, + [CChar](address.utf8CString), + value, + memo?.bytes, + networkType.networkId, + minimumConfirmations, + useZIP317Fees + ) globalDBLock.unlock() - guard success else { - throw ZcashError.rustCreateToAddress(lastErrorMessage(fallback: "`createToAddress` failed with unknown error")) + guard let proposal else { + throw ZcashError.rustCreateToAddress(lastErrorMessage(fallback: "`proposeTransfer` failed with unknown error")) } - return contiguousTxIdBytes.withUnsafeBufferPointer { txIdBytePtr in - Data(txIdBytePtr) - } + defer { zcashlc_free_boxed_slice(proposal) } + + return try FfiProposal(contiguousBytes: Data( + bytes: proposal.pointee.ptr, + count: Int(proposal.pointee.len) + )) } func decryptAndStoreTransaction(txBytes: [UInt8], minedHeight: Int32) async throws { @@ -138,18 +129,6 @@ actor ZcashRustBackend: ZcashRustBackendWelding { } } - func getBalance(account: Int32) async throws -> Int64 { - globalDBLock.lock() - let balance = zcashlc_get_balance(dbData.0, dbData.1, account, networkType.networkId) - globalDBLock.unlock() - - guard balance >= 0 else { - throw ZcashError.rustGetBalance(Int(account), lastErrorMessage(fallback: "Error getting total balance from account \(account)")) - } - - return balance - } - func getCurrentAddress(account: Int32) async throws -> UnifiedAddress { globalDBLock.lock() let addressCStr = zcashlc_get_current_address( @@ -256,27 +235,6 @@ actor ZcashRustBackend: ZcashRustBackendWelding { return balance } - func getVerifiedBalance(account: Int32) async throws -> Int64 { - globalDBLock.lock() - let balance = zcashlc_get_verified_balance( - dbData.0, - dbData.1, - account, - networkType.networkId, - minimumConfirmations - ) - globalDBLock.unlock() - - guard balance >= 0 else { - throw ZcashError.rustGetVerifiedBalance( - Int(account), - lastErrorMessage(fallback: "Error getting verified balance from account \(account)") - ) - } - - return balance - } - func getVerifiedTransparentBalance(account: Int32) async throws -> Int64 { guard account >= 0 else { throw ZcashError.rustGetVerifiedTransparentBalanceNegativeAccount(Int(account)) @@ -576,21 +534,35 @@ actor ZcashRustBackend: ZcashRustBackendWelding { } } - func getScanProgress() async throws -> ScanProgress? { + func getWalletSummary() async throws -> WalletSummary? { globalDBLock.lock() - let result = zcashlc_get_scan_progress(dbData.0, dbData.1, networkType.networkId) + let summaryPtr = zcashlc_get_wallet_summary(dbData.0, dbData.1, networkType.networkId, minimumConfirmations) globalDBLock.unlock() - if result.denominator == 0 { - switch result.numerator { - case 0: - return nil - default: - throw ZcashError.rustGetScanProgress(lastErrorMessage(fallback: "`getScanProgress` failed with unknown error")) - } - } else { - return ScanProgress(numerator: result.numerator, denominator: result.denominator) + guard let summaryPtr else { + throw ZcashError.rustGetWalletSummary(lastErrorMessage(fallback: "`getWalletSummary` failed with unknown error")) } + + defer { zcashlc_free_wallet_summary(summaryPtr) } + + if summaryPtr.pointee.fully_scanned_height < 0 { + return nil + } + + var accountBalances: [UInt32: AccountBalance] = [:] + + for i in (0 ..< Int(summaryPtr.pointee.account_balances_len)) { + let accountBalance = summaryPtr.pointee.account_balances.advanced(by: i).pointee + accountBalances[accountBalance.account_id] = accountBalance.toAccountBalance() + } + + return WalletSummary( + accountBalances: accountBalances, + chainTipHeight: BlockHeight(summaryPtr.pointee.chain_tip_height), + fullyScannedHeight: BlockHeight(summaryPtr.pointee.fully_scanned_height), + scanProgress: summaryPtr.pointee.scan_progress?.pointee.toScanProgress(), + nextSaplingSubtreeIndex: UInt32(summaryPtr.pointee.next_sapling_subtree_index) + ) } func suggestScanRanges() async throws -> [ScanRange] { @@ -623,48 +595,90 @@ actor ZcashRustBackend: ZcashRustBackendWelding { return scanRanges } - func scanBlocks(fromHeight: Int32, limit: UInt32 = 0) async throws { + func scanBlocks(fromHeight: Int32, limit: UInt32 = 0) async throws -> ScanSummary { globalDBLock.lock() - let result = zcashlc_scan_blocks(fsBlockDbRoot.0, fsBlockDbRoot.1, dbData.0, dbData.1, fromHeight, limit, networkType.networkId) + let summaryPtr = zcashlc_scan_blocks(fsBlockDbRoot.0, fsBlockDbRoot.1, dbData.0, dbData.1, fromHeight, limit, networkType.networkId) globalDBLock.unlock() - guard result != 0 else { + guard let summaryPtr else { throw ZcashError.rustScanBlocks(lastErrorMessage(fallback: "`scanBlocks` failed with unknown error")) } + + defer { zcashlc_free_scan_summary(summaryPtr) } + + return ScanSummary( + scannedRange: Range(uncheckedBounds: ( + BlockHeight(summaryPtr.pointee.scanned_start), + BlockHeight(summaryPtr.pointee.scanned_end) + )), + spentSaplingNoteCount: summaryPtr.pointee.spent_sapling_note_count, + receivedSaplingNoteCount: summaryPtr.pointee.received_sapling_note_count + ) } - func shieldFunds( - usk: UnifiedSpendingKey, + func proposeShielding( + account: Int32, memo: MemoBytes?, shieldingThreshold: Zatoshi + ) async throws -> FfiProposal { + globalDBLock.lock() + let proposal = zcashlc_propose_shielding( + dbData.0, + dbData.1, + account, + memo?.bytes, + UInt64(shieldingThreshold.amount), + networkType.networkId, + minimumConfirmations, + useZIP317Fees + ) + globalDBLock.unlock() + + guard let proposal else { + throw ZcashError.rustShieldFunds(lastErrorMessage(fallback: "`proposeShielding` failed with unknown error")) + } + + defer { zcashlc_free_boxed_slice(proposal) } + + return try FfiProposal(contiguousBytes: Data( + bytes: proposal.pointee.ptr, + count: Int(proposal.pointee.len) + )) + } + + func createProposedTransaction( + proposal: FfiProposal, + usk: UnifiedSpendingKey ) async throws -> Data { var contiguousTxIdBytes = ContiguousArray([UInt8](repeating: 0x0, count: 32)) + let proposalBytes = try proposal.serializedData(partial: false).bytes + globalDBLock.lock() let success = contiguousTxIdBytes.withUnsafeMutableBufferPointer { txIdBytePtr in - usk.bytes.withUnsafeBufferPointer { uskBuffer in - zcashlc_shield_funds( - dbData.0, - dbData.1, - uskBuffer.baseAddress, - UInt(usk.bytes.count), - memo?.bytes, - UInt64(shieldingThreshold.amount), - spendParamsPath.0, - spendParamsPath.1, - outputParamsPath.0, - outputParamsPath.1, - networkType.networkId, - minimumConfirmations, - useZIP317Fees, - txIdBytePtr.baseAddress - ) + proposalBytes.withUnsafeBufferPointer { proposalPtr in + usk.bytes.withUnsafeBufferPointer { uskPtr in + zcashlc_create_proposed_transaction( + dbData.0, + dbData.1, + proposalPtr.baseAddress, + UInt(proposalBytes.count), + uskPtr.baseAddress, + UInt(usk.bytes.count), + spendParamsPath.0, + spendParamsPath.1, + outputParamsPath.0, + outputParamsPath.1, + networkType.networkId, + txIdBytePtr.baseAddress + ) + } } } globalDBLock.unlock() guard success else { - throw ZcashError.rustShieldFunds(lastErrorMessage(fallback: "`shieldFunds` failed with unknown error")) + throw ZcashError.rustCreateToAddress(lastErrorMessage(fallback: "`createToAddress` failed with unknown error")) } return contiguousTxIdBytes.withUnsafeBufferPointer { txIdBytePtr in @@ -778,3 +792,34 @@ extension Array where Element == FfiSubtreeRoot { } } } + +extension FfiBalance { + /// Converts an [`FfiBalance`] into a [`PoolBalance`]. + func toPoolBalance() -> PoolBalance { + .init( + spendableValue: Zatoshi(self.spendable_value), + changePendingConfirmation: Zatoshi(self.change_pending_confirmation), + valuePendingSpendability: Zatoshi(self.value_pending_spendability) + ) + } +} + +extension FfiAccountBalance { + /// Converts an [`FfiAccountBalance`] into a [`AccountBalance`]. + func toAccountBalance() -> AccountBalance { + .init( + saplingBalance: self.sapling_balance.toPoolBalance(), + unshielded: Zatoshi(self.unshielded) + ) + } +} + +extension FfiScanProgress { + /// Converts an [`FfiScanProgress`] into a [`ScanProgress`]. + func toScanProgress() -> ScanProgress { + .init( + numerator: self.numerator, + denominator: self.denominator + ) + } +} diff --git a/Sources/ZcashLightClientKit/Rust/ZcashRustBackendWelding.swift b/Sources/ZcashLightClientKit/Rust/ZcashRustBackendWelding.swift index a8ed2350..4d30e496 100644 --- a/Sources/ZcashLightClientKit/Rust/ZcashRustBackendWelding.swift +++ b/Sources/ZcashLightClientKit/Rust/ZcashRustBackendWelding.swift @@ -41,30 +41,12 @@ protocol ZcashRustBackendWelding { /// - Throws: `rustCreateAccount`. func createAccount(seed: [UInt8], treeState: TreeState, recoverUntil: UInt32?) async throws -> UnifiedSpendingKey - /// Creates a transaction to the given address from the given account - /// - Parameter usk: `UnifiedSpendingKey` for the account that controls the funds to be spent. - /// - Parameter to: recipient address - /// - Parameter value: transaction amount in Zatoshi - /// - Parameter memo: the `MemoBytes` for this transaction. pass `nil` when sending to transparent receivers - /// - Throws: `rustCreateToAddress`. - func createToAddress( - usk: UnifiedSpendingKey, - to address: String, - value: Int64, - memo: MemoBytes? - ) async throws -> Data - /// Scans a transaction for any information that can be decrypted by the accounts in the wallet, and saves it to the wallet. /// - parameter tx: the transaction to decrypt /// - parameter minedHeight: height on which this transaction was mined. this is used to fetch the consensus branch ID. /// - Throws: `rustDecryptAndStoreTransaction`. func decryptAndStoreTransaction(txBytes: [UInt8], minedHeight: Int32) async throws - /// Get the (unverified) balance from the given account. - /// - parameter account: index of the given account - /// - Throws: `rustGetBalance`. - func getBalance(account: Int32) async throws -> Int64 - /// Returns the most-recently-generated unified payment address for the specified account. /// - parameter account: index of the given account /// - Throws: @@ -120,11 +102,6 @@ protocol ZcashRustBackendWelding { /// - `rustListTransparentReceiversInvalidAddress` if transarent received generated by rust is invalid. func listTransparentReceivers(account: Int32) async throws -> [TransparentAddress] - /// Get the verified balance from the given account - /// - parameter account: index of the given account - /// - Throws: `rustGetVerifiedBalance` when rust layer throws error. - func getVerifiedBalance(account: Int32) async throws -> Int64 - /// Get the verified cached transparent balance for the given account /// - parameter account: account index to query the balance for. /// - Throws: @@ -167,8 +144,8 @@ protocol ZcashRustBackendWelding { /// height due to the fact that out-of-order scanning can leave gaps. func maxScannedHeight() async throws -> BlockHeight? - /// Returns the scan progress derived from the current wallet state. - func getScanProgress() async throws -> ScanProgress? + /// Returns the account balances and sync status of the wallet. + func getWalletSummary() async throws -> WalletSummary? /// Returns a list of suggested scan ranges based upon the current wallet state. /// @@ -198,7 +175,7 @@ protocol ZcashRustBackendWelding { /// - parameter fromHeight: scan starting from the given height. /// - parameter limit: scan up to limit blocks. /// - Throws: `rustScanBlocks` if rust layer returns error. - func scanBlocks(fromHeight: Int32, limit: UInt32) async throws + func scanBlocks(fromHeight: Int32, limit: UInt32) async throws -> ScanSummary /// Upserts a UTXO into the data db database /// - parameter txid: the txid bytes for the UTXO @@ -215,14 +192,42 @@ protocol ZcashRustBackendWelding { height: BlockHeight ) async throws - /// Creates a transaction to shield all found UTXOs in data db for the account the provided `UnifiedSpendingKey` has spend authority for. - /// - Parameter usk: `UnifiedSpendingKey` that spend transparent funds and where the funds will be shielded to. + /// Select transaction inputs, compute fees, and construct a proposal for a transaction + /// that can then be authorized and made ready for submission to the network with + /// `createProposedTransaction`. + /// + /// - parameter account: index of the given account + /// - Parameter to: recipient address + /// - Parameter value: transaction amount in Zatoshi + /// - Parameter memo: the `MemoBytes` for this transaction. pass `nil` when sending to transparent receivers + /// - Throws: `rustCreateToAddress`. + func proposeTransfer( + account: Int32, + to address: String, + value: Int64, + memo: MemoBytes? + ) async throws -> FfiProposal + + /// Constructs a transaction proposal to shield all found UTXOs in data db for the given account, + /// that can then be authorized and made ready for submission to the network with + /// `createProposedTransaction`. + /// + /// - parameter account: index of the given account /// - Parameter memo: the `Memo` for this transaction /// - Throws: `rustShieldFunds` if rust layer returns error. - func shieldFunds( - usk: UnifiedSpendingKey, + func proposeShielding( + account: Int32, memo: MemoBytes?, shieldingThreshold: Zatoshi + ) async throws -> FfiProposal + + /// Creates a transaction from the given proposal. + /// - Parameter proposal: the transaction proposal. + /// - Parameter usk: `UnifiedSpendingKey` for the account that controls the funds to be spent. + /// - Throws: `rustCreateToAddress`. + func createProposedTransaction( + proposal: FfiProposal, + usk: UnifiedSpendingKey ) async throws -> Data /// Gets the consensus branch id for the given height diff --git a/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift b/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift index dd18b8c0..0bfb2c5f 100644 --- a/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift +++ b/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift @@ -414,15 +414,13 @@ public class SDKSynchronizer: Synchronizer { } public func getShieldedBalance(accountIndex: Int = 0) async throws -> Zatoshi { - let balance = try await initializer.rustBackend.getBalance(account: Int32(accountIndex)) - - return Zatoshi(balance) + try await initializer.rustBackend.getWalletSummary()?.accountBalances[UInt32(accountIndex)]? + .saplingBalance.total() ?? Zatoshi.zero } public func getShieldedVerifiedBalance(accountIndex: Int = 0) async throws -> Zatoshi { - let balance = try await initializer.rustBackend.getVerifiedBalance(account: Int32(accountIndex)) - - return Zatoshi(balance) + try await initializer.rustBackend.getWalletSummary()?.accountBalances[UInt32(accountIndex)]? + .saplingBalance.spendableValue ?? Zatoshi.zero } public func getUnifiedAddress(accountIndex: Int) async throws -> UnifiedAddress { diff --git a/Sources/ZcashLightClientKit/Transaction/WalletTransactionEncoder.swift b/Sources/ZcashLightClientKit/Transaction/WalletTransactionEncoder.swift index 80a2edfa..f24927bd 100644 --- a/Sources/ZcashLightClientKit/Transaction/WalletTransactionEncoder.swift +++ b/Sources/ZcashLightClientKit/Transaction/WalletTransactionEncoder.swift @@ -85,13 +85,19 @@ class WalletTransactionEncoder: TransactionEncoder { throw ZcashError.walletTransEncoderCreateTransactionMissingSaplingParams } - let txId = try await rustBackend.createToAddress( - usk: spendingKey, + // TODO: Expose the proposal in a way that enables querying its fee. + let proposal = try await rustBackend.proposeTransfer( + account: Int32(spendingKey.account), to: address, value: zatoshi.amount, memo: memoBytes ) + let txId = try await rustBackend.createProposedTransaction( + proposal: proposal, + usk: spendingKey + ) + return txId } @@ -121,13 +127,19 @@ class WalletTransactionEncoder: TransactionEncoder { guard ensureParams(spend: self.spendParamsURL, output: self.outputParamsURL) else { throw ZcashError.walletTransEncoderShieldFundsMissingSaplingParams } - - let txId = try await rustBackend.shieldFunds( - usk: spendingKey, + + // TODO: Expose the proposal in a way that enables querying its fee. + let proposal = try await rustBackend.proposeShielding( + account: Int32(spendingKey.account), memo: memo, shieldingThreshold: shieldingThreshold ) - + + let txId = try await rustBackend.createProposedTransaction( + proposal: proposal, + usk: spendingKey + ) + return txId } diff --git a/Tests/TestUtils/Sourcery/GeneratedMocks/AutoMockable.generated.swift b/Tests/TestUtils/Sourcery/GeneratedMocks/AutoMockable.generated.swift index 14deef6f..9afb8f6e 100644 --- a/Tests/TestUtils/Sourcery/GeneratedMocks/AutoMockable.generated.swift +++ b/Tests/TestUtils/Sourcery/GeneratedMocks/AutoMockable.generated.swift @@ -1,4 +1,4 @@ -// Generated using Sourcery 2.0.3 — https://github.com/krzysztofzablocki/Sourcery +// Generated using Sourcery 2.1.4 — https://github.com/krzysztofzablocki/Sourcery // DO NOT EDIT import Combine @testable import ZcashLightClientKit @@ -1191,17 +1191,17 @@ class SDKMetricsMock: SDKMetrics { // MARK: - logCBPOverviewReport - var logCBPOverviewReportShieldedBalanceCallsCount = 0 - var logCBPOverviewReportShieldedBalanceCalled: Bool { - return logCBPOverviewReportShieldedBalanceCallsCount > 0 + var logCBPOverviewReportWalletSummaryCallsCount = 0 + var logCBPOverviewReportWalletSummaryCalled: Bool { + return logCBPOverviewReportWalletSummaryCallsCount > 0 } - var logCBPOverviewReportShieldedBalanceReceivedArguments: (logger: Logger, shieldedBalance: WalletBalance)? - var logCBPOverviewReportShieldedBalanceClosure: ((Logger, WalletBalance) async -> Void)? + var logCBPOverviewReportWalletSummaryReceivedArguments: (logger: Logger, walletSummary: WalletSummary?)? + var logCBPOverviewReportWalletSummaryClosure: ((Logger, WalletSummary?) async -> Void)? - func logCBPOverviewReport(_ logger: Logger, shieldedBalance: WalletBalance) async { - logCBPOverviewReportShieldedBalanceCallsCount += 1 - logCBPOverviewReportShieldedBalanceReceivedArguments = (logger: logger, shieldedBalance: shieldedBalance) - await logCBPOverviewReportShieldedBalanceClosure!(logger, shieldedBalance) + func logCBPOverviewReport(_ logger: Logger, walletSummary: WalletSummary?) async { + logCBPOverviewReportWalletSummaryCallsCount += 1 + logCBPOverviewReportWalletSummaryReceivedArguments = (logger: logger, walletSummary: walletSummary) + await logCBPOverviewReportWalletSummaryClosure!(logger, walletSummary) } } @@ -2179,39 +2179,6 @@ actor ZcashRustBackendWeldingMock: ZcashRustBackendWelding { } } - // MARK: - createToAddress - - var createToAddressUskToValueMemoThrowableError: Error? - func setCreateToAddressUskToValueMemoThrowableError(_ param: Error?) async { - createToAddressUskToValueMemoThrowableError = param - } - var createToAddressUskToValueMemoCallsCount = 0 - var createToAddressUskToValueMemoCalled: Bool { - return createToAddressUskToValueMemoCallsCount > 0 - } - var createToAddressUskToValueMemoReceivedArguments: (usk: UnifiedSpendingKey, address: String, value: Int64, memo: MemoBytes?)? - var createToAddressUskToValueMemoReturnValue: Data! - func setCreateToAddressUskToValueMemoReturnValue(_ param: Data) async { - createToAddressUskToValueMemoReturnValue = param - } - var createToAddressUskToValueMemoClosure: ((UnifiedSpendingKey, String, Int64, MemoBytes?) async throws -> Data)? - func setCreateToAddressUskToValueMemoClosure(_ param: ((UnifiedSpendingKey, String, Int64, MemoBytes?) async throws -> Data)?) async { - createToAddressUskToValueMemoClosure = param - } - - func createToAddress(usk: UnifiedSpendingKey, to address: String, value: Int64, memo: MemoBytes?) async throws -> Data { - if let error = createToAddressUskToValueMemoThrowableError { - throw error - } - createToAddressUskToValueMemoCallsCount += 1 - createToAddressUskToValueMemoReceivedArguments = (usk: usk, address: address, value: value, memo: memo) - if let closure = createToAddressUskToValueMemoClosure { - return try await closure(usk, address, value, memo) - } else { - return createToAddressUskToValueMemoReturnValue - } - } - // MARK: - decryptAndStoreTransaction var decryptAndStoreTransactionTxBytesMinedHeightThrowableError: Error? @@ -2237,39 +2204,6 @@ actor ZcashRustBackendWeldingMock: ZcashRustBackendWelding { try await decryptAndStoreTransactionTxBytesMinedHeightClosure!(txBytes, minedHeight) } - // MARK: - getBalance - - var getBalanceAccountThrowableError: Error? - func setGetBalanceAccountThrowableError(_ param: Error?) async { - getBalanceAccountThrowableError = param - } - var getBalanceAccountCallsCount = 0 - var getBalanceAccountCalled: Bool { - return getBalanceAccountCallsCount > 0 - } - var getBalanceAccountReceivedAccount: Int32? - var getBalanceAccountReturnValue: Int64! - func setGetBalanceAccountReturnValue(_ param: Int64) async { - getBalanceAccountReturnValue = param - } - var getBalanceAccountClosure: ((Int32) async throws -> Int64)? - func setGetBalanceAccountClosure(_ param: ((Int32) async throws -> Int64)?) async { - getBalanceAccountClosure = param - } - - func getBalance(account: Int32) async throws -> Int64 { - if let error = getBalanceAccountThrowableError { - throw error - } - getBalanceAccountCallsCount += 1 - getBalanceAccountReceivedAccount = account - if let closure = getBalanceAccountClosure { - return try await closure(account) - } else { - return getBalanceAccountReturnValue - } - } - // MARK: - getCurrentAddress var getCurrentAddressAccountThrowableError: Error? @@ -2501,39 +2435,6 @@ actor ZcashRustBackendWeldingMock: ZcashRustBackendWelding { } } - // MARK: - getVerifiedBalance - - var getVerifiedBalanceAccountThrowableError: Error? - func setGetVerifiedBalanceAccountThrowableError(_ param: Error?) async { - getVerifiedBalanceAccountThrowableError = param - } - var getVerifiedBalanceAccountCallsCount = 0 - var getVerifiedBalanceAccountCalled: Bool { - return getVerifiedBalanceAccountCallsCount > 0 - } - var getVerifiedBalanceAccountReceivedAccount: Int32? - var getVerifiedBalanceAccountReturnValue: Int64! - func setGetVerifiedBalanceAccountReturnValue(_ param: Int64) async { - getVerifiedBalanceAccountReturnValue = param - } - var getVerifiedBalanceAccountClosure: ((Int32) async throws -> Int64)? - func setGetVerifiedBalanceAccountClosure(_ param: ((Int32) async throws -> Int64)?) async { - getVerifiedBalanceAccountClosure = param - } - - func getVerifiedBalance(account: Int32) async throws -> Int64 { - if let error = getVerifiedBalanceAccountThrowableError { - throw error - } - getVerifiedBalanceAccountCallsCount += 1 - getVerifiedBalanceAccountReceivedAccount = account - if let closure = getVerifiedBalanceAccountClosure { - return try await closure(account) - } else { - return getVerifiedBalanceAccountReturnValue - } - } - // MARK: - getVerifiedTransparentBalance var getVerifiedTransparentBalanceAccountThrowableError: Error? @@ -2729,34 +2630,34 @@ actor ZcashRustBackendWeldingMock: ZcashRustBackendWelding { } } - // MARK: - getScanProgress + // MARK: - getWalletSummary - var getScanProgressThrowableError: Error? - func setGetScanProgressThrowableError(_ param: Error?) async { - getScanProgressThrowableError = param + var getWalletSummaryThrowableError: Error? + func setGetWalletSummaryThrowableError(_ param: Error?) async { + getWalletSummaryThrowableError = param } - var getScanProgressCallsCount = 0 - var getScanProgressCalled: Bool { - return getScanProgressCallsCount > 0 + var getWalletSummaryCallsCount = 0 + var getWalletSummaryCalled: Bool { + return getWalletSummaryCallsCount > 0 } - var getScanProgressReturnValue: ScanProgress? - func setGetScanProgressReturnValue(_ param: ScanProgress?) async { - getScanProgressReturnValue = param + var getWalletSummaryReturnValue: WalletSummary? + func setGetWalletSummaryReturnValue(_ param: WalletSummary?) async { + getWalletSummaryReturnValue = param } - var getScanProgressClosure: (() async throws -> ScanProgress?)? - func setGetScanProgressClosure(_ param: (() async throws -> ScanProgress?)?) async { - getScanProgressClosure = param + var getWalletSummaryClosure: (() async throws -> WalletSummary?)? + func setGetWalletSummaryClosure(_ param: (() async throws -> WalletSummary?)?) async { + getWalletSummaryClosure = param } - func getScanProgress() async throws -> ScanProgress? { - if let error = getScanProgressThrowableError { + func getWalletSummary() async throws -> WalletSummary? { + if let error = getWalletSummaryThrowableError { throw error } - getScanProgressCallsCount += 1 - if let closure = getScanProgressClosure { + getWalletSummaryCallsCount += 1 + if let closure = getWalletSummaryClosure { return try await closure() } else { - return getScanProgressReturnValue + return getWalletSummaryReturnValue } } @@ -2802,18 +2703,26 @@ actor ZcashRustBackendWeldingMock: ZcashRustBackendWelding { return scanBlocksFromHeightLimitCallsCount > 0 } var scanBlocksFromHeightLimitReceivedArguments: (fromHeight: Int32, limit: UInt32)? - var scanBlocksFromHeightLimitClosure: ((Int32, UInt32) async throws -> Void)? - func setScanBlocksFromHeightLimitClosure(_ param: ((Int32, UInt32) async throws -> Void)?) async { + var scanBlocksFromHeightLimitReturnValue: ScanSummary! + func setScanBlocksFromHeightLimitReturnValue(_ param: ScanSummary) async { + scanBlocksFromHeightLimitReturnValue = param + } + var scanBlocksFromHeightLimitClosure: ((Int32, UInt32) async throws -> ScanSummary)? + func setScanBlocksFromHeightLimitClosure(_ param: ((Int32, UInt32) async throws -> ScanSummary)?) async { scanBlocksFromHeightLimitClosure = param } - func scanBlocks(fromHeight: Int32, limit: UInt32) async throws { + func scanBlocks(fromHeight: Int32, limit: UInt32) async throws -> ScanSummary { if let error = scanBlocksFromHeightLimitThrowableError { throw error } scanBlocksFromHeightLimitCallsCount += 1 scanBlocksFromHeightLimitReceivedArguments = (fromHeight: fromHeight, limit: limit) - try await scanBlocksFromHeightLimitClosure!(fromHeight, limit) + if let closure = scanBlocksFromHeightLimitClosure { + return try await closure(fromHeight, limit) + } else { + return scanBlocksFromHeightLimitReturnValue + } } // MARK: - putUnspentTransparentOutput @@ -2841,36 +2750,102 @@ actor ZcashRustBackendWeldingMock: ZcashRustBackendWelding { try await putUnspentTransparentOutputTxidIndexScriptValueHeightClosure!(txid, index, script, value, height) } - // MARK: - shieldFunds + // MARK: - proposeTransfer - var shieldFundsUskMemoShieldingThresholdThrowableError: Error? - func setShieldFundsUskMemoShieldingThresholdThrowableError(_ param: Error?) async { - shieldFundsUskMemoShieldingThresholdThrowableError = param + var proposeTransferAccountToValueMemoThrowableError: Error? + func setProposeTransferAccountToValueMemoThrowableError(_ param: Error?) async { + proposeTransferAccountToValueMemoThrowableError = param } - var shieldFundsUskMemoShieldingThresholdCallsCount = 0 - var shieldFundsUskMemoShieldingThresholdCalled: Bool { - return shieldFundsUskMemoShieldingThresholdCallsCount > 0 + var proposeTransferAccountToValueMemoCallsCount = 0 + var proposeTransferAccountToValueMemoCalled: Bool { + return proposeTransferAccountToValueMemoCallsCount > 0 } - var shieldFundsUskMemoShieldingThresholdReceivedArguments: (usk: UnifiedSpendingKey, memo: MemoBytes?, shieldingThreshold: Zatoshi)? - var shieldFundsUskMemoShieldingThresholdReturnValue: Data! - func setShieldFundsUskMemoShieldingThresholdReturnValue(_ param: Data) async { - shieldFundsUskMemoShieldingThresholdReturnValue = param + var proposeTransferAccountToValueMemoReceivedArguments: (account: Int32, address: String, value: Int64, memo: MemoBytes?)? + var proposeTransferAccountToValueMemoReturnValue: FfiProposal! + func setProposeTransferAccountToValueMemoReturnValue(_ param: FfiProposal) async { + proposeTransferAccountToValueMemoReturnValue = param } - var shieldFundsUskMemoShieldingThresholdClosure: ((UnifiedSpendingKey, MemoBytes?, Zatoshi) async throws -> Data)? - func setShieldFundsUskMemoShieldingThresholdClosure(_ param: ((UnifiedSpendingKey, MemoBytes?, Zatoshi) async throws -> Data)?) async { - shieldFundsUskMemoShieldingThresholdClosure = param + var proposeTransferAccountToValueMemoClosure: ((Int32, String, Int64, MemoBytes?) async throws -> FfiProposal)? + func setProposeTransferAccountToValueMemoClosure(_ param: ((Int32, String, Int64, MemoBytes?) async throws -> FfiProposal)?) async { + proposeTransferAccountToValueMemoClosure = param } - func shieldFunds(usk: UnifiedSpendingKey, memo: MemoBytes?, shieldingThreshold: Zatoshi) async throws -> Data { - if let error = shieldFundsUskMemoShieldingThresholdThrowableError { + func proposeTransfer(account: Int32, to address: String, value: Int64, memo: MemoBytes?) async throws -> FfiProposal { + if let error = proposeTransferAccountToValueMemoThrowableError { throw error } - shieldFundsUskMemoShieldingThresholdCallsCount += 1 - shieldFundsUskMemoShieldingThresholdReceivedArguments = (usk: usk, memo: memo, shieldingThreshold: shieldingThreshold) - if let closure = shieldFundsUskMemoShieldingThresholdClosure { - return try await closure(usk, memo, shieldingThreshold) + proposeTransferAccountToValueMemoCallsCount += 1 + proposeTransferAccountToValueMemoReceivedArguments = (account: account, address: address, value: value, memo: memo) + if let closure = proposeTransferAccountToValueMemoClosure { + return try await closure(account, address, value, memo) } else { - return shieldFundsUskMemoShieldingThresholdReturnValue + return proposeTransferAccountToValueMemoReturnValue + } + } + + // MARK: - proposeShielding + + var proposeShieldingAccountMemoShieldingThresholdThrowableError: Error? + func setProposeShieldingAccountMemoShieldingThresholdThrowableError(_ param: Error?) async { + proposeShieldingAccountMemoShieldingThresholdThrowableError = param + } + var proposeShieldingAccountMemoShieldingThresholdCallsCount = 0 + var proposeShieldingAccountMemoShieldingThresholdCalled: Bool { + return proposeShieldingAccountMemoShieldingThresholdCallsCount > 0 + } + var proposeShieldingAccountMemoShieldingThresholdReceivedArguments: (account: Int32, memo: MemoBytes?, shieldingThreshold: Zatoshi)? + var proposeShieldingAccountMemoShieldingThresholdReturnValue: FfiProposal! + func setProposeShieldingAccountMemoShieldingThresholdReturnValue(_ param: FfiProposal) async { + proposeShieldingAccountMemoShieldingThresholdReturnValue = param + } + var proposeShieldingAccountMemoShieldingThresholdClosure: ((Int32, MemoBytes?, Zatoshi) async throws -> FfiProposal)? + func setProposeShieldingAccountMemoShieldingThresholdClosure(_ param: ((Int32, MemoBytes?, Zatoshi) async throws -> FfiProposal)?) async { + proposeShieldingAccountMemoShieldingThresholdClosure = param + } + + func proposeShielding(account: Int32, memo: MemoBytes?, shieldingThreshold: Zatoshi) async throws -> FfiProposal { + if let error = proposeShieldingAccountMemoShieldingThresholdThrowableError { + throw error + } + proposeShieldingAccountMemoShieldingThresholdCallsCount += 1 + proposeShieldingAccountMemoShieldingThresholdReceivedArguments = (account: account, memo: memo, shieldingThreshold: shieldingThreshold) + if let closure = proposeShieldingAccountMemoShieldingThresholdClosure { + return try await closure(account, memo, shieldingThreshold) + } else { + return proposeShieldingAccountMemoShieldingThresholdReturnValue + } + } + + // MARK: - createProposedTransaction + + var createProposedTransactionProposalUskThrowableError: Error? + func setCreateProposedTransactionProposalUskThrowableError(_ param: Error?) async { + createProposedTransactionProposalUskThrowableError = param + } + var createProposedTransactionProposalUskCallsCount = 0 + var createProposedTransactionProposalUskCalled: Bool { + return createProposedTransactionProposalUskCallsCount > 0 + } + var createProposedTransactionProposalUskReceivedArguments: (proposal: FfiProposal, usk: UnifiedSpendingKey)? + var createProposedTransactionProposalUskReturnValue: Data! + func setCreateProposedTransactionProposalUskReturnValue(_ param: Data) async { + createProposedTransactionProposalUskReturnValue = param + } + var createProposedTransactionProposalUskClosure: ((FfiProposal, UnifiedSpendingKey) async throws -> Data)? + func setCreateProposedTransactionProposalUskClosure(_ param: ((FfiProposal, UnifiedSpendingKey) async throws -> Data)?) async { + createProposedTransactionProposalUskClosure = param + } + + func createProposedTransaction(proposal: FfiProposal, usk: UnifiedSpendingKey) async throws -> Data { + if let error = createProposedTransactionProposalUskThrowableError { + throw error + } + createProposedTransactionProposalUskCallsCount += 1 + createProposedTransactionProposalUskReceivedArguments = (proposal: proposal, usk: usk) + if let closure = createProposedTransactionProposalUskClosure { + return try await closure(proposal, usk) + } else { + return createProposedTransactionProposalUskReturnValue } } diff --git a/Tests/TestUtils/Sourcery/generateMocks.sh b/Tests/TestUtils/Sourcery/generateMocks.sh index 503f7af7..c1326ec8 100755 --- a/Tests/TestUtils/Sourcery/generateMocks.sh +++ b/Tests/TestUtils/Sourcery/generateMocks.sh @@ -3,7 +3,7 @@ scriptDir=${0:a:h} cd "${scriptDir}" -sourcery_version=2.0.3 +sourcery_version=2.1.4 if which sourcery >/dev/null; then if [[ $(sourcery --version) != $sourcery_version ]]; then diff --git a/Tests/TestUtils/Stubs.swift b/Tests/TestUtils/Stubs.swift index 93cc36cf..c0d2b1d3 100644 --- a/Tests/TestUtils/Stubs.swift +++ b/Tests/TestUtils/Stubs.swift @@ -75,7 +75,6 @@ class RustBackendMockHelper { await rustBackendMock.setInitBlockMetadataDbClosure() { } await rustBackendMock.setWriteBlocksMetadataBlocksClosure() { _ in } await rustBackendMock.setGetTransparentBalanceAccountReturnValue(0) - await rustBackendMock.setGetVerifiedBalanceAccountReturnValue(0) await rustBackendMock.setListTransparentReceiversAccountReturnValue([]) await rustBackendMock.setGetCurrentAddressAccountThrowableError(ZcashError.rustGetCurrentAddress("mocked error")) await rustBackendMock.setGetNextAvailableAddressAccountThrowableError(ZcashError.rustGetNextAvailableAddress("mocked error")) @@ -84,22 +83,15 @@ class RustBackendMockHelper { await rustBackendMock.setInitDataDbSeedReturnValue(.seedRequired) await rustBackendMock.setGetNearestRewindHeightHeightReturnValue(-1) await rustBackendMock.setPutUnspentTransparentOutputTxidIndexScriptValueHeightClosure() { _, _, _, _, _ in } - await rustBackendMock.setCreateToAddressUskToValueMemoThrowableError(ZcashError.rustCreateToAddress("mocked error")) - await rustBackendMock.setShieldFundsUskMemoShieldingThresholdThrowableError(ZcashError.rustShieldFunds("mocked error")) + await rustBackendMock.setProposeTransferAccountToValueMemoThrowableError(ZcashError.rustCreateToAddress("mocked error")) + await rustBackendMock.setProposeShieldingAccountMemoShieldingThresholdThrowableError(ZcashError.rustShieldFunds("mocked error")) + await rustBackendMock.setCreateProposedTransactionProposalUskThrowableError(ZcashError.rustCreateToAddress("mocked error")) await rustBackendMock.setDecryptAndStoreTransactionTxBytesMinedHeightThrowableError(ZcashError.rustDecryptAndStoreTransaction("mock fail")) await rustBackendMock.setInitDataDbSeedClosure() { seed in return try await rustBackend.initDataDb(seed: seed) } - await rustBackendMock.setGetBalanceAccountClosure() { account in - return try await rustBackend.getBalance(account: account) - } - - await rustBackendMock.setGetVerifiedBalanceAccountClosure() { account in - return try await rustBackend.getVerifiedBalance(account: account) - } - await rustBackendMock.setRewindToHeightHeightClosure() { height in try await rustBackend.rewindToHeight(height: height) }