diff --git a/CHANGELOG.md b/CHANGELOG.md index 457abfed..82209e11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ and this library adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 # Unreleased +## Added +- `Synchronizer.getExchangeRateUSD() -> NSDecimalNumber`, which fetches the latest USD/ZEC + exchange rate. Prices are queried over Tor (to hide the wallet's IP address) on Binance, + Coinbase, and Gemini. + # 2.1.9 - 2024-06-05 ## Fixed 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 3681b337..ff82819e 100644 --- a/Example/ZcashLightClientSample/ZcashLightClientSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Example/ZcashLightClientSample/ZcashLightClientSample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -176,8 +176,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/zcash-hackworks/zcash-light-client-ffi", "state" : { - "revision" : "9314c83d7a09d88e1c0bd3ff3738a50833325059", - "version" : "0.8.0" + "revision" : "5919afaa2c37fa11946d43b5c1e66b8463867f98" } } ], diff --git a/Example/ZcashLightClientSample/ZcashLightClientSample/AppDelegate.swift b/Example/ZcashLightClientSample/ZcashLightClientSample/AppDelegate.swift index c477a43d..a07bd91b 100644 --- a/Example/ZcashLightClientSample/ZcashLightClientSample/AppDelegate.swift +++ b/Example/ZcashLightClientSample/ZcashLightClientSample/AppDelegate.swift @@ -48,12 +48,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate { fsBlockDbRoot: try! fsBlockDbRootURLHelper(), generalStorageURL: try! generalStorageURLHelper(), dataDbURL: try! dataDbURLHelper(), + torDirURL: try! torDirURLHelper(), endpoint: DemoAppConfig.endpoint, network: kZcashNetwork, spendParamsURL: try! spendParamsURLHelper(), outputParamsURL: try! outputParamsURLHelper(), - saplingParamsSourceURL: SaplingParamsSourceURL.default, - enableBackendTracing: true + saplingParamsSourceURL: SaplingParamsSourceURL.default ) self.wallet = wallet @@ -199,6 +199,15 @@ func dataDbURLHelper() throws -> URL { ) } +func torDirURLHelper() throws -> URL { + try documentsDirectoryHelper() + .appendingPathComponent(kZcashNetwork.networkType.chainName) + .appendingPathComponent( + ZcashSDK.defaultTorDirName, + isDirectory: true + ) +} + func spendParamsURLHelper() throws -> URL { try documentsDirectoryHelper().appendingPathComponent("sapling-spend.params") } diff --git a/Example/ZcashLightClientSample/ZcashLightClientSample/Get Balance/GetBalanceViewController.swift b/Example/ZcashLightClientSample/ZcashLightClientSample/Get Balance/GetBalanceViewController.swift index ccee9d6a..37c7a902 100644 --- a/Example/ZcashLightClientSample/ZcashLightClientSample/Get Balance/GetBalanceViewController.swift +++ b/Example/ZcashLightClientSample/ZcashLightClientSample/Get Balance/GetBalanceViewController.swift @@ -19,10 +19,14 @@ class GetBalanceViewController: UIViewController { self.title = "Account 0 Balance" Task { @MainActor in - let balanceText = (try? await synchronizer.getAccountBalance()?.saplingBalance.total().formattedString) ?? "0.0" - let verifiedText = (try? await synchronizer.getAccountBalance()?.saplingBalance.spendableValue.formattedString) ?? "0.0" - self.balance.text = "\(balanceText) ZEC" - self.verified.text = "\(verifiedText) ZEC" + let balance = try? await synchronizer.getAccountBalance() + let balanceText = (balance?.saplingBalance.total().formattedString) ?? "0.0" + let verifiedText = (balance?.saplingBalance.spendableValue.formattedString) ?? "0.0" + let usdZecRate = try await synchronizer.getExchangeRateUSD() + let usdBalance = (balance?.saplingBalance.total().decimalValue ?? 0).multiplying(by: usdZecRate) + let usdVerified = (balance?.saplingBalance.spendableValue.decimalValue ?? 0).multiplying(by: usdZecRate) + self.balance.text = "\(balanceText) ZEC\n\(usdBalance) USD\n\n(\(usdZecRate) USD/ZEC)" + self.verified.text = "\(verifiedText) ZEC\n\(usdVerified) USD" } } } diff --git a/Example/ZcashLightClientSample/ZcashLightClientSample/Sync Blocks/SyncBlocksListViewController.swift b/Example/ZcashLightClientSample/ZcashLightClientSample/Sync Blocks/SyncBlocksListViewController.swift index 1904de45..acddf9ff 100644 --- a/Example/ZcashLightClientSample/ZcashLightClientSample/Sync Blocks/SyncBlocksListViewController.swift +++ b/Example/ZcashLightClientSample/ZcashLightClientSample/Sync Blocks/SyncBlocksListViewController.swift @@ -102,14 +102,14 @@ class SyncBlocksListViewController: UIViewController { fsBlockDbRoot: try! fsBlockDbRootURLHelper(), generalStorageURL: try! generalStorageURLHelper(), dataDbURL: try! dataDbURLHelper(), + torDirURL: try! torDirURLHelper(), endpoint: DemoAppConfig.endpoint, network: kZcashNetwork, spendParamsURL: try! spendParamsURLHelper(), outputParamsURL: try! outputParamsURLHelper(), saplingParamsSourceURL: SaplingParamsSourceURL.default, alias: data.alias, - loggingPolicy: .default(.debug), - enableBackendTracing: true + loggingPolicy: .default(.debug) ) return SDKSynchronizer(initializer: initializer) diff --git a/Package.resolved b/Package.resolved index c8615efd..82ceeeef 100644 --- a/Package.resolved +++ b/Package.resolved @@ -122,8 +122,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/zcash-hackworks/zcash-light-client-ffi", "state" : { - "revision" : "9314c83d7a09d88e1c0bd3ff3738a50833325059", - "version" : "0.8.0" + "revision" : "5919afaa2c37fa11946d43b5c1e66b8463867f98" } } ], diff --git a/Package.swift b/Package.swift index 8dbf116a..5f4a6871 100644 --- a/Package.swift +++ b/Package.swift @@ -16,7 +16,9 @@ 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", exact: "0.8.0") + //.package(url: "https://github.com/zcash-hackworks/zcash-light-client-ffi", exact: "0.8.0") + // Compiled from 7c7b797211a7bd0b847df6315faa9ae13c32a0f6 + .package(url: "https://github.com/zcash-hackworks/zcash-light-client-ffi", revision: "5919afaa2c37fa11946d43b5c1e66b8463867f98") ], targets: [ .target( diff --git a/Sources/ZcashLightClientKit/Block/CompactBlockProcessor.swift b/Sources/ZcashLightClientKit/Block/CompactBlockProcessor.swift index 4fc6d056..559232b5 100644 --- a/Sources/ZcashLightClientKit/Block/CompactBlockProcessor.swift +++ b/Sources/ZcashLightClientKit/Block/CompactBlockProcessor.swift @@ -56,6 +56,7 @@ actor CompactBlockProcessor { let saplingParamsSourceURL: SaplingParamsSourceURL let fsBlockCacheRoot: URL let dataDb: URL + let torDir: URL let spendParamsURL: URL let outputParamsURL: URL let enhanceBatchSize: Int @@ -79,6 +80,7 @@ actor CompactBlockProcessor { cacheDbURL: URL? = nil, fsBlockCacheRoot: URL, dataDb: URL, + torDir: URL, spendParamsURL: URL, outputParamsURL: URL, saplingParamsSourceURL: SaplingParamsSourceURL, @@ -94,6 +96,7 @@ actor CompactBlockProcessor { self.alias = alias self.fsBlockCacheRoot = fsBlockCacheRoot self.dataDb = dataDb + self.torDir = torDir self.spendParamsURL = spendParamsURL self.outputParamsURL = outputParamsURL self.saplingParamsSourceURL = saplingParamsSourceURL @@ -112,6 +115,7 @@ actor CompactBlockProcessor { alias: ZcashSynchronizerAlias, fsBlockCacheRoot: URL, dataDb: URL, + torDir: URL, spendParamsURL: URL, outputParamsURL: URL, saplingParamsSourceURL: SaplingParamsSourceURL, @@ -126,6 +130,7 @@ actor CompactBlockProcessor { self.alias = alias self.fsBlockCacheRoot = fsBlockCacheRoot self.dataDb = dataDb + self.torDir = torDir self.spendParamsURL = spendParamsURL self.outputParamsURL = outputParamsURL self.saplingParamsSourceURL = saplingParamsSourceURL @@ -151,6 +156,7 @@ actor CompactBlockProcessor { alias: initializer.alias, fsBlockCacheRoot: initializer.fsBlockDbRoot, dataDb: initializer.dataDbURL, + torDir: initializer.torDirURL, spendParamsURL: initializer.spendParamsURL, outputParamsURL: initializer.outputParamsURL, saplingParamsSourceURL: initializer.saplingParamsSourceURL, diff --git a/Sources/ZcashLightClientKit/ClosureSynchronizer.swift b/Sources/ZcashLightClientKit/ClosureSynchronizer.swift index 18d9b1ea..02fcc6d4 100644 --- a/Sources/ZcashLightClientKit/ClosureSynchronizer.swift +++ b/Sources/ZcashLightClientKit/ClosureSynchronizer.swift @@ -128,7 +128,10 @@ public protocol ClosureSynchronizer { func refreshUTXOs(address: TransparentAddress, from height: BlockHeight, completion: @escaping (Result) -> Void) func getAccountBalance(accountIndex: Int, completion: @escaping (Result) -> Void) - + + /// Fetches the latest ZEC-USD exchange rate. + func getExchangeRateUSD(completion: @escaping (Result) -> Void) + /* It can be missleading that these two methods are returning Publisher even this protocol is closure based. Reason is that Synchronizer doesn't provide different implementations for these two methods. So Combine it is even here. diff --git a/Sources/ZcashLightClientKit/CombineSynchronizer.swift b/Sources/ZcashLightClientKit/CombineSynchronizer.swift index 09aa0842..bbd24c0c 100644 --- a/Sources/ZcashLightClientKit/CombineSynchronizer.swift +++ b/Sources/ZcashLightClientKit/CombineSynchronizer.swift @@ -131,6 +131,9 @@ public protocol CombineSynchronizer { func getAccountBalance(accountIndex: Int) -> SinglePublisher + /// Fetches the latest ZEC-USD exchange rate. + func getExchangeRateUSD() -> SinglePublisher + func rewind(_ policy: RewindPolicy) -> CompletablePublisher func wipe() -> CompletablePublisher } diff --git a/Sources/ZcashLightClientKit/Constants/ZcashSDK.swift b/Sources/ZcashLightClientKit/Constants/ZcashSDK.swift index 22770e88..208e61cc 100644 --- a/Sources/ZcashLightClientKit/Constants/ZcashSDK.swift +++ b/Sources/ZcashLightClientKit/Constants/ZcashSDK.swift @@ -124,6 +124,9 @@ public enum ZcashSDK { /// Default Name for LibRustZcash data.db public static let defaultDataDbName = "data.db" + /// Default Name for Tor data directory + public static let defaultTorDirName = "tor" + /// Default Name for Compact Block file system based db public static let defaultFsCacheName = "fs_cache" @@ -154,6 +157,9 @@ public protocol NetworkConstants { /// Default Name for LibRustZcash data.db static var defaultDataDbName: String { get } + /// Default Name for Tor data directory + static var defaultTorDirName: String { get } + static var defaultFsBlockDbRootName: String { get } /// Default Name for Compact Block caches db @@ -181,6 +187,9 @@ public enum ZcashSDKMainnetConstants: NetworkConstants { /// Default Name for LibRustZcash data.db public static let defaultDataDbName = "data.db" + /// Default Name for Tor data directory + public static let defaultTorDirName = "tor" + public static let defaultFsBlockDbRootName = "fs_cache" /// Default Name for Compact Block caches db @@ -197,6 +206,9 @@ public enum ZcashSDKTestnetConstants: NetworkConstants { /// Default Name for LibRustZcash data.db public static let defaultDataDbName = "data.db" + /// Default Name for Tor data directory + public static let defaultTorDirName = "tor" + /// Default Name for Compact Block caches db public static let defaultCacheDbName = "caches.db" diff --git a/Sources/ZcashLightClientKit/Error/Sourcery/generateErrorCode.sh b/Sources/ZcashLightClientKit/Error/Sourcery/generateErrorCode.sh index b240aea1..722cfa5e 100755 --- a/Sources/ZcashLightClientKit/Error/Sourcery/generateErrorCode.sh +++ b/Sources/ZcashLightClientKit/Error/Sourcery/generateErrorCode.sh @@ -3,11 +3,11 @@ scriptDir=${0:a:h} cd "${scriptDir}" -sourcery_version=2.1.7 +sourcery_version=2.2.4 if which sourcery >/dev/null; then if [[ $(sourcery --version) != $sourcery_version ]]; then - echo "warning: Compatible sourcer version not installed. Install sourcer $sourcery_version. Currently installed version is $(sourcer --version)" + echo "warning: Compatible sourcer version not installed. Install sourcer $sourcery_version. Currently installed version is $(sourcery --version)" exit 1 fi diff --git a/Sources/ZcashLightClientKit/Error/ZcashError.swift b/Sources/ZcashLightClientKit/Error/ZcashError.swift index ebea735c..e4097da4 100644 --- a/Sources/ZcashLightClientKit/Error/ZcashError.swift +++ b/Sources/ZcashLightClientKit/Error/ZcashError.swift @@ -1,4 +1,4 @@ -// Generated using Sourcery 2.1.7 — https://github.com/krzysztofzablocki/Sourcery +// Generated using Sourcery 2.2.4 — https://github.com/krzysztofzablocki/Sourcery // DO NOT EDIT /* @@ -342,6 +342,16 @@ public enum ZcashError: Equatable, Error { /// sourcery: code="ZRUST0061" /// ZRUST0061 case rustPutOrchardSubtreeRoots(_ rustError: String) + /// Error from rust layer when calling TorClient.init + /// - `rustError` contains error generated by the rust layer. + /// sourcery: code="ZRUST0062" + /// ZRUST0062 + case rustTorClientInit(_ rustError: String) + /// Error from rust layer when calling TorClient.get + /// - `rustError` contains error generated by the rust layer. + /// sourcery: code="ZRUST0063" + /// ZRUST0063 + case rustTorClientGet(_ rustError: String) /// SQLite query failed when fetching all accounts from the database. /// - `sqliteError` is error produced by SQLite library. /// ZADAO0001 @@ -705,6 +715,8 @@ public enum ZcashError: Equatable, Error { case .rustIsSeedRelevantToAnyDerivedAccount: return "Error from rust layer when calling ZcashRustBackend.rustIsSeedRelevantToAnyDerivedAccount" case .rustPutOrchardSubtreeRootsAllocationProblem: return "Unable to allocate memory required to write blocks when calling ZcashRustBackend.putOrchardSubtreeRoots" case .rustPutOrchardSubtreeRoots: return "Error from rust layer when calling ZcashRustBackend.putOrchardSubtreeRoots" + case .rustTorClientInit: return "Error from rust layer when calling TorClient.init" + case .rustTorClientGet: return "Error from rust layer when calling TorClient.get" 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." @@ -885,6 +897,8 @@ public enum ZcashError: Equatable, Error { case .rustIsSeedRelevantToAnyDerivedAccount: return .rustIsSeedRelevantToAnyDerivedAccount case .rustPutOrchardSubtreeRootsAllocationProblem: return .rustPutOrchardSubtreeRootsAllocationProblem case .rustPutOrchardSubtreeRoots: return .rustPutOrchardSubtreeRoots + case .rustTorClientInit: return .rustTorClientInit + case .rustTorClientGet: return .rustTorClientGet 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 be9510df..b90dcc20 100644 --- a/Sources/ZcashLightClientKit/Error/ZcashErrorCode.swift +++ b/Sources/ZcashLightClientKit/Error/ZcashErrorCode.swift @@ -1,4 +1,4 @@ -// Generated using Sourcery 2.1.7 — https://github.com/krzysztofzablocki/Sourcery +// Generated using Sourcery 2.2.4 — https://github.com/krzysztofzablocki/Sourcery // DO NOT EDIT /* @@ -185,6 +185,10 @@ public enum ZcashErrorCode: String { case rustPutOrchardSubtreeRootsAllocationProblem = "ZRUST0060" /// Error from rust layer when calling ZcashRustBackend.putOrchardSubtreeRoots case rustPutOrchardSubtreeRoots = "ZRUST0061" + /// Error from rust layer when calling TorClient.init + case rustTorClientInit = "ZRUST0062" + /// Error from rust layer when calling TorClient.get + case rustTorClientGet = "ZRUST0063" /// 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/Error/ZcashErrorCodeDefinition.swift b/Sources/ZcashLightClientKit/Error/ZcashErrorCodeDefinition.swift index c85c97b4..24d95ea5 100644 --- a/Sources/ZcashLightClientKit/Error/ZcashErrorCodeDefinition.swift +++ b/Sources/ZcashLightClientKit/Error/ZcashErrorCodeDefinition.swift @@ -367,6 +367,14 @@ enum ZcashErrorDefinition { /// - `rustError` contains error generated by the rust layer. /// sourcery: code="ZRUST0061" case rustPutOrchardSubtreeRoots(_ rustError: String) + /// Error from rust layer when calling TorClient.init + /// - `rustError` contains error generated by the rust layer. + /// sourcery: code="ZRUST0062" + case rustTorClientInit(_ rustError: String) + /// Error from rust layer when calling TorClient.get + /// - `rustError` contains error generated by the rust layer. + /// sourcery: code="ZRUST0063" + case rustTorClientGet(_ rustError: String) // MARK: - Account DAO diff --git a/Sources/ZcashLightClientKit/Initializer.swift b/Sources/ZcashLightClientKit/Initializer.swift index 51b62f45..30772870 100644 --- a/Sources/ZcashLightClientKit/Initializer.swift +++ b/Sources/ZcashLightClientKit/Initializer.swift @@ -90,6 +90,7 @@ public class Initializer { struct URLs { let fsBlockDbRoot: URL let dataDbURL: URL + let torDirURL: URL let generalStorageURL: URL let spendParamsURL: URL let outputParamsURL: URL @@ -115,6 +116,7 @@ public class Initializer { let fsBlockDbRoot: URL let generalStorageURL: URL let dataDbURL: URL + let torDirURL: URL let spendParamsURL: URL let outputParamsURL: URL let saplingParamsSourceURL: SaplingParamsSourceURL @@ -158,14 +160,14 @@ public class Initializer { fsBlockDbRoot: URL, generalStorageURL: URL, dataDbURL: URL, + torDirURL: URL, endpoint: LightWalletEndpoint, network: ZcashNetwork, spendParamsURL: URL, outputParamsURL: URL, saplingParamsSourceURL: SaplingParamsSourceURL, alias: ZcashSynchronizerAlias = .default, - loggingPolicy: LoggingPolicy = .default(.debug), - enableBackendTracing: Bool = false + loggingPolicy: LoggingPolicy = .default(.debug) ) { let container = DIContainer() @@ -177,14 +179,14 @@ public class Initializer { fsBlockDbRoot: fsBlockDbRoot, generalStorageURL: generalStorageURL, dataDbURL: dataDbURL, + torDirURL: torDirURL, endpoint: endpoint, network: network, spendParamsURL: spendParamsURL, outputParamsURL: outputParamsURL, saplingParamsSourceURL: saplingParamsSourceURL, alias: alias, - loggingPolicy: loggingPolicy, - enableBackendTracing: enableBackendTracing + loggingPolicy: loggingPolicy ) self.init( @@ -207,14 +209,14 @@ public class Initializer { fsBlockDbRoot: URL, generalStorageURL: URL, dataDbURL: URL, + torDirURL: URL, endpoint: LightWalletEndpoint, network: ZcashNetwork, spendParamsURL: URL, outputParamsURL: URL, saplingParamsSourceURL: SaplingParamsSourceURL, alias: ZcashSynchronizerAlias = .default, - loggingPolicy: LoggingPolicy = .default(.debug), - enableBackendTracing: Bool = false + loggingPolicy: LoggingPolicy = .default(.debug) ) { // It's not possible to fail from constructor. Technically it's possible but it can be pain for the client apps to handle errors thrown // from constructor. So `parsingError` is just stored in initializer and `SDKSynchronizer.prepare()` throw this error if it exists. @@ -224,14 +226,14 @@ public class Initializer { fsBlockDbRoot: fsBlockDbRoot, generalStorageURL: generalStorageURL, dataDbURL: dataDbURL, + torDirURL: torDirURL, endpoint: endpoint, network: network, spendParamsURL: spendParamsURL, outputParamsURL: outputParamsURL, saplingParamsSourceURL: saplingParamsSourceURL, alias: alias, - loggingPolicy: loggingPolicy, - enableBackendTracing: enableBackendTracing + loggingPolicy: loggingPolicy ) self.init( @@ -264,6 +266,7 @@ public class Initializer { self.fsBlockDbRoot = urls.fsBlockDbRoot self.generalStorageURL = urls.generalStorageURL self.dataDbURL = urls.dataDbURL + self.torDirURL = urls.torDirURL self.endpoint = endpoint self.spendParamsURL = urls.spendParamsURL self.outputParamsURL = urls.outputParamsURL @@ -286,18 +289,19 @@ public class Initializer { fsBlockDbRoot: URL, generalStorageURL: URL, dataDbURL: URL, + torDirURL: URL, endpoint: LightWalletEndpoint, network: ZcashNetwork, spendParamsURL: URL, outputParamsURL: URL, saplingParamsSourceURL: SaplingParamsSourceURL, alias: ZcashSynchronizerAlias, - loggingPolicy: LoggingPolicy = .default(.debug), - enableBackendTracing: Bool = false + loggingPolicy: LoggingPolicy = .default(.debug) ) -> (URLs, ZcashError?) { let urls = URLs( fsBlockDbRoot: fsBlockDbRoot, dataDbURL: dataDbURL, + torDirURL: torDirURL, generalStorageURL: generalStorageURL, spendParamsURL: spendParamsURL, outputParamsURL: outputParamsURL @@ -313,8 +317,7 @@ public class Initializer { alias: alias, networkType: network.networkType, endpoint: endpoint, - loggingPolicy: loggingPolicy, - enableBackendTracing: enableBackendTracing + loggingPolicy: loggingPolicy ) return (updatedURLs, parsingError) @@ -362,6 +365,10 @@ public class Initializer { return .failure(.initializerCantUpdateURLWithAlias(urls.dataDbURL)) } + guard let updatedTorDirURL = urls.torDirURL.updateLastPathComponent(with: alias) else { + return .failure(.initializerCantUpdateURLWithAlias(urls.torDirURL)) + } + guard let updatedSpendParamsURL = urls.spendParamsURL.updateLastPathComponent(with: alias) else { return .failure(.initializerCantUpdateURLWithAlias(urls.spendParamsURL)) } @@ -378,6 +385,7 @@ public class Initializer { URLs( fsBlockDbRoot: updatedFsBlockDbRoot, dataDbURL: updatedDataDbURL, + torDirURL: updatedTorDirURL, generalStorageURL: updatedGeneralStorageURL, spendParamsURL: updatedSpendParamsURL, outputParamsURL: updateOutputParamsURL diff --git a/Sources/ZcashLightClientKit/Providers/ResourceProvider.swift b/Sources/ZcashLightClientKit/Providers/ResourceProvider.swift index a19441b9..e9ffcfd0 100644 --- a/Sources/ZcashLightClientKit/Providers/ResourceProvider.swift +++ b/Sources/ZcashLightClientKit/Providers/ResourceProvider.swift @@ -31,6 +31,16 @@ public struct DefaultResourceProvider: ResourceProvider { } } + public var torDirURL: URL { + let constants = network.constants + do { + let url = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true) + return url.appendingPathComponent(constants.defaultTorDirName) + } catch { + return URL(fileURLWithPath: "file://\(constants.defaultTorDirName)") + } + } + public var fsCacheURL: URL { let constants = network.constants do { diff --git a/Sources/ZcashLightClientKit/Rust/ZcashRustBackend.swift b/Sources/ZcashLightClientKit/Rust/ZcashRustBackend.swift index 6a0034f4..c01e4492 100644 --- a/Sources/ZcashLightClientKit/Rust/ZcashRustBackend.swift +++ b/Sources/ZcashLightClientKit/Rust/ZcashRustBackend.swift @@ -9,6 +9,21 @@ import Foundation import libzcashlc +enum RustLogging: String { + /// The logs are completely disabled. + case off + /// Logs very serious errors. + case error + /// Logs hazardous situations. + case warn + /// Logs useful information. + case info + /// Logs lower priority information. + case debug + /// Logs very low priority, often extremely verbose, information. + case trace +} + struct ZcashRustBackend: ZcashRustBackendWelding { let minimumConfirmations: UInt32 = 10 let minimumShieldingConfirmations: UInt32 = 1 @@ -22,7 +37,8 @@ struct ZcashRustBackend: ZcashRustBackendWelding { let networkType: NetworkType - static var tracingEnabled = false + static var rustInitialized = false + /// Creates instance of `ZcashRustBackend`. /// - Parameters: /// - dbData: `URL` pointing to file where data database will be. @@ -32,9 +48,17 @@ struct ZcashRustBackend: ZcashRustBackendWelding { /// - spendParamsPath: `URL` pointing to spend parameters file. /// - outputParamsPath: `URL` pointing to output parameters file. /// - networkType: Network type to use. - /// - enableTracing: this sets up whether the tracing system will dump logs onto the OSLogger system or not. - /// **Important note:** this will enable the tracing **for all instances** of ZcashRustBackend, not only for this one. - init(dbData: URL, fsBlockDbRoot: URL, spendParamsPath: URL, outputParamsPath: URL, networkType: NetworkType, enableTracing: Bool = false) { + /// - logLevel: this sets up whether the tracing system will dump logs onto the OSLogger system or not. + /// **Important note:** this will enable the tracing **for all instances** of ZcashRustBackend, not only for this one. + /// This is ignored after the first ZcashRustBackend instance is created. + init( + dbData: URL, + fsBlockDbRoot: URL, + spendParamsPath: URL, + outputParamsPath: URL, + networkType: NetworkType, + logLevel: RustLogging = RustLogging.off + ) { self.dbData = dbData.osStr() self.fsBlockDbRoot = fsBlockDbRoot.osPathStr() self.spendParamsPath = spendParamsPath.osPathStr() @@ -42,9 +66,9 @@ struct ZcashRustBackend: ZcashRustBackendWelding { self.networkType = networkType self.keyDeriving = ZcashKeyDerivationBackend(networkType: networkType) - if enableTracing && !Self.tracingEnabled { - Self.tracingEnabled = true - Self.enableTracing() + if !Self.rustInitialized { + Self.rustInitialized = true + Self.initializeRust(logLevel: logLevel) } } @@ -823,33 +847,33 @@ struct ZcashRustBackend: ZcashRustBackendWelding { } private extension ZcashRustBackend { - static func enableTracing() { - zcashlc_init_on_load(false) - } -} - -private extension ZcashRustBackend { - nonisolated func lastErrorMessage(fallback: String) -> String { - let errorLen = zcashlc_last_error_length() - defer { zcashlc_clear_last_error() } - - if errorLen > 0 { - let error = UnsafeMutablePointer.allocate(capacity: Int(errorLen)) - defer { error.deallocate() } - - zcashlc_error_message_utf8(error, errorLen) - if let errorMessage = String(validatingUTF8: error) { - return errorMessage - } else { - return fallback - } - } else { - return fallback + static func initializeRust(logLevel: RustLogging) { + logLevel.rawValue.utf8CString.withUnsafeBufferPointer { levelPtr in + zcashlc_init_on_load(levelPtr.baseAddress) } } } -private extension URL { +nonisolated func lastErrorMessage(fallback: String) -> String { + let errorLen = zcashlc_last_error_length() + defer { zcashlc_clear_last_error() } + + if errorLen > 0 { + let error = UnsafeMutablePointer.allocate(capacity: Int(errorLen)) + defer { error.deallocate() } + + zcashlc_error_message_utf8(error, errorLen) + if let errorMessage = String(validatingUTF8: error) { + return errorMessage + } else { + return fallback + } + } else { + return fallback + } +} + +extension URL { func osStr() -> (String, UInt) { let path = self.absoluteString return (path, UInt(path.lengthOfBytes(using: .utf8))) diff --git a/Sources/ZcashLightClientKit/Synchronizer.swift b/Sources/ZcashLightClientKit/Synchronizer.swift index 34057680..e584294f 100644 --- a/Sources/ZcashLightClientKit/Synchronizer.swift +++ b/Sources/ZcashLightClientKit/Synchronizer.swift @@ -309,6 +309,9 @@ public protocol Synchronizer: AnyObject { /// - Returns: `AccountBalance`, struct that holds sapling and unshielded balances or `nil` when no account is associated with `accountIndex` func getAccountBalance(accountIndex: Int) async throws -> AccountBalance? + /// Fetches the latest ZEC-USD exchange rate. + func getExchangeRateUSD() async throws -> NSDecimalNumber + /// Rescans the known blocks with the current keys. /// /// `rewind(policy:)` can be called anytime. If the sync process is in progress then it is stopped first. In this case, it make some significant diff --git a/Sources/ZcashLightClientKit/Synchronizer/ClosureSDKSynchronizer.swift b/Sources/ZcashLightClientKit/Synchronizer/ClosureSDKSynchronizer.swift index 1f2d31ae..2ce9f2f5 100644 --- a/Sources/ZcashLightClientKit/Synchronizer/ClosureSDKSynchronizer.swift +++ b/Sources/ZcashLightClientKit/Synchronizer/ClosureSDKSynchronizer.swift @@ -194,6 +194,12 @@ extension ClosureSDKSynchronizer: ClosureSynchronizer { } } + public func getExchangeRateUSD(completion: @escaping (Result) -> Void) { + AsyncToClosureGateway.executeThrowingAction(completion) { + try await self.synchronizer.getExchangeRateUSD() + } + } + /* It can be missleading that these two methods are returning Publisher even this protocol is closure based. Reason is that Synchronizer doesn't provide different implementations for these two methods. So Combine it is even here. diff --git a/Sources/ZcashLightClientKit/Synchronizer/CombineSDKSynchronizer.swift b/Sources/ZcashLightClientKit/Synchronizer/CombineSDKSynchronizer.swift index 1c8a1a91..9f09333c 100644 --- a/Sources/ZcashLightClientKit/Synchronizer/CombineSDKSynchronizer.swift +++ b/Sources/ZcashLightClientKit/Synchronizer/CombineSDKSynchronizer.swift @@ -196,6 +196,12 @@ extension CombineSDKSynchronizer: CombineSynchronizer { } } + public func getExchangeRateUSD() -> SinglePublisher { + AsyncToCombineGateway.executeThrowingAction() { + try await self.synchronizer.getExchangeRateUSD() + } + } + public func rewind(_ policy: RewindPolicy) -> CompletablePublisher { synchronizer.rewind(policy) } public func wipe() -> CompletablePublisher { synchronizer.wipe() } } diff --git a/Sources/ZcashLightClientKit/Synchronizer/Dependencies.swift b/Sources/ZcashLightClientKit/Synchronizer/Dependencies.swift index dd32a14f..ae3a46f7 100644 --- a/Sources/ZcashLightClientKit/Synchronizer/Dependencies.swift +++ b/Sources/ZcashLightClientKit/Synchronizer/Dependencies.swift @@ -14,8 +14,7 @@ enum Dependencies { alias: ZcashSynchronizerAlias, networkType: NetworkType, endpoint: LightWalletEndpoint, - loggingPolicy: Initializer.LoggingPolicy = .default(.debug), - enableBackendTracing: Bool = false + loggingPolicy: Initializer.LoggingPolicy = .default(.debug) ) { container.register(type: CheckpointSource.self, isSingleton: true) { _ in CheckpointSourceFactory.fromBundle(for: networkType) @@ -35,6 +34,36 @@ enum Dependencies { return logger } + let rustLogging: RustLogging + switch loggingPolicy { + case .default(let logLevel): + switch logLevel { + case .debug: + rustLogging = RustLogging.debug + case .info, .event: + rustLogging = RustLogging.info + case .warning: + rustLogging = RustLogging.warn + case .error: + rustLogging = RustLogging.error + } + case .custom(let logger): + switch logger.maxLogLevel() { + case .debug: + rustLogging = RustLogging.debug + case .info, .event: + rustLogging = RustLogging.info + case .warning: + rustLogging = RustLogging.warn + case .error: + rustLogging = RustLogging.error + case .none: + rustLogging = RustLogging.off + } + case .noLogging: + rustLogging = RustLogging.off + } + container.register(type: ZcashRustBackendWelding.self, isSingleton: true) { _ in ZcashRustBackend( dbData: urls.dataDbURL, @@ -42,7 +71,7 @@ enum Dependencies { spendParamsPath: urls.spendParamsURL, outputParamsPath: urls.outputParamsURL, networkType: networkType, - enableTracing: enableBackendTracing + logLevel: rustLogging ) } diff --git a/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift b/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift index 671c2d60..27a8f52c 100644 --- a/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift +++ b/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift @@ -508,6 +508,24 @@ public class SDKSynchronizer: Synchronizer { try await initializer.rustBackend.getWalletSummary()?.accountBalances[UInt32(accountIndex)] } + public func getExchangeRateUSD() async throws -> NSDecimalNumber { + logger.info("Bootstrapping Tor client for fetching exchange rates") + let tor: TorClient + do { + tor = try await TorClient(torDir: initializer.torDirURL) + } catch { + logger.error("failed to bootstrap Tor client: \(error)") + throw error + } + + do { + return try await tor.getExchangeRateUSD() + } catch { + logger.error("Failed to fetch exchange rate through Tor: \(error)") + throw error + } + } + public func getUnifiedAddress(accountIndex: Int) async throws -> UnifiedAddress { try await blockProcessor.getUnifiedAddress(accountIndex: accountIndex) } diff --git a/Sources/ZcashLightClientKit/Tor/TorClient.swift b/Sources/ZcashLightClientKit/Tor/TorClient.swift new file mode 100644 index 00000000..cb14b394 --- /dev/null +++ b/Sources/ZcashLightClientKit/Tor/TorClient.swift @@ -0,0 +1,48 @@ +// +// TorRuntime.swift +// +// +// Created by Jack Grigg on 04/06/2024. +// + +import Foundation +import libzcashlc + +public class TorClient { + private let runtime: OpaquePointer + + init(torDir: URL) async throws { + // Ensure that the directory exists. + let fileManager = FileManager() + if !fileManager.fileExists(atPath: torDir.path) { + do { + try fileManager.createDirectory(at: torDir, withIntermediateDirectories: true) + } catch { + throw ZcashError.blockRepositoryCreateBlocksCacheDirectory(torDir, error) + } + } + + let rawDir = torDir.osPathStr() + let runtimePtr = zcashlc_create_tor_runtime(rawDir.0, rawDir.1) + + guard let runtimePtr else { + throw ZcashError.rustTorClientInit(lastErrorMessage(fallback: "`TorClient` init failed with unknown error")) + } + + runtime = runtimePtr + } + + deinit { + zcashlc_free_tor_runtime(runtime) + } + + public func getExchangeRateUSD() async throws -> NSDecimalNumber { + let rate = zcashlc_get_exchange_rate_usd(runtime) + + if rate.is_sign_negative { + throw ZcashError.rustTorClientGet(lastErrorMessage(fallback: "`TorClient.get` failed with unknown error")) + } + + return NSDecimalNumber(mantissa: rate.mantissa, exponent: rate.exponent, isNegative: rate.is_sign_negative) + } +} diff --git a/Sources/ZcashLightClientKit/Utils/LoggingProxy.swift b/Sources/ZcashLightClientKit/Utils/LoggingProxy.swift index f8b1d56b..0d6f151f 100644 --- a/Sources/ZcashLightClientKit/Utils/LoggingProxy.swift +++ b/Sources/ZcashLightClientKit/Utils/LoggingProxy.swift @@ -11,6 +11,7 @@ import Foundation Represents what's expected from a logging entity */ public protocol Logger { + func maxLogLevel() -> OSLogger.LogLevel? func debug(_ message: String, file: StaticString, function: StaticString, line: Int) func info(_ message: String, file: StaticString, function: StaticString, line: Int) func event(_ message: String, file: StaticString, function: StaticString, line: Int) @@ -44,6 +45,9 @@ extension Logger { A concrete logger implementation that logs nothing at all */ struct NullLogger: Logger { + func maxLogLevel() -> OSLogger.LogLevel? { + nil + } func debug(_ message: String, file: StaticString, function: StaticString, line: Int) {} func info(_ message: String, file: StaticString, function: StaticString, line: Int) {} func event(_ message: String, file: StaticString, function: StaticString, line: Int) {} diff --git a/Sources/ZcashLightClientKit/Utils/OSLogger.swift b/Sources/ZcashLightClientKit/Utils/OSLogger.swift index e19a03d6..373f67cb 100644 --- a/Sources/ZcashLightClientKit/Utils/OSLogger.swift +++ b/Sources/ZcashLightClientKit/Utils/OSLogger.swift @@ -39,6 +39,10 @@ public class OSLogger: Logger { } } + public func maxLogLevel() -> LogLevel? { + self.level + } + public func debug( _ message: String, file: StaticString = #file, diff --git a/Tests/DarksideTests/TransactionEnhancementTests.swift b/Tests/DarksideTests/TransactionEnhancementTests.swift index 52ff65ed..7ef9b85b 100644 --- a/Tests/DarksideTests/TransactionEnhancementTests.swift +++ b/Tests/DarksideTests/TransactionEnhancementTests.swift @@ -60,6 +60,7 @@ class TransactionEnhancementTests: ZcashTestCase { alias: .default, fsBlockCacheRoot: testTempDirectory, dataDb: pathProvider.dataDbURL, + torDir: pathProvider.torDirURL, spendParamsURL: pathProvider.spendParamsURL, outputParamsURL: pathProvider.outputParamsURL, saplingParamsSourceURL: SaplingParamsSourceURL.tests, @@ -117,6 +118,7 @@ class TransactionEnhancementTests: ZcashTestCase { urls: Initializer.URLs( fsBlockDbRoot: testTempDirectory, dataDbURL: pathProvider.dataDbURL, + torDirURL: pathProvider.torDirURL, generalStorageURL: testGeneralStorageDirectory, spendParamsURL: pathProvider.spendParamsURL, outputParamsURL: pathProvider.outputParamsURL diff --git a/Tests/NetworkTests/BlockStreamingTest.swift b/Tests/NetworkTests/BlockStreamingTest.swift index 41504a04..54398a08 100644 --- a/Tests/NetworkTests/BlockStreamingTest.swift +++ b/Tests/NetworkTests/BlockStreamingTest.swift @@ -31,6 +31,7 @@ class BlockStreamingTest: ZcashTestCase { urls: Initializer.URLs( fsBlockDbRoot: testTempDirectory, dataDbURL: try! __dataDbURL(), + torDirURL: try! __torDirURL(), generalStorageURL: testGeneralStorageDirectory, spendParamsURL: try! __spendParamsURL(), outputParamsURL: try! __outputParamsURL() diff --git a/Tests/NetworkTests/CompactBlockProcessorTests.swift b/Tests/NetworkTests/CompactBlockProcessorTests.swift index 3f550f83..06cec319 100644 --- a/Tests/NetworkTests/CompactBlockProcessorTests.swift +++ b/Tests/NetworkTests/CompactBlockProcessorTests.swift @@ -35,6 +35,7 @@ class CompactBlockProcessorTests: ZcashTestCase { alias: .default, fsBlockCacheRoot: testTempDirectory, dataDb: pathProvider.dataDbURL, + torDir: pathProvider.torDirURL, spendParamsURL: pathProvider.spendParamsURL, outputParamsURL: pathProvider.outputParamsURL, saplingParamsSourceURL: SaplingParamsSourceURL.tests, @@ -71,6 +72,7 @@ class CompactBlockProcessorTests: ZcashTestCase { urls: Initializer.URLs( fsBlockDbRoot: testTempDirectory, dataDbURL: processorConfig.dataDb, + torDirURL: processorConfig.torDir, generalStorageURL: testGeneralStorageDirectory, spendParamsURL: processorConfig.spendParamsURL, outputParamsURL: processorConfig.outputParamsURL diff --git a/Tests/NetworkTests/CompactBlockReorgTests.swift b/Tests/NetworkTests/CompactBlockReorgTests.swift index 12b1e2ff..b4b13e51 100644 --- a/Tests/NetworkTests/CompactBlockReorgTests.swift +++ b/Tests/NetworkTests/CompactBlockReorgTests.swift @@ -36,6 +36,7 @@ class CompactBlockReorgTests: ZcashTestCase { alias: .default, fsBlockCacheRoot: testTempDirectory, dataDb: pathProvider.dataDbURL, + torDir: pathProvider.torDirURL, spendParamsURL: pathProvider.spendParamsURL, outputParamsURL: pathProvider.outputParamsURL, saplingParamsSourceURL: SaplingParamsSourceURL.tests, @@ -94,6 +95,7 @@ class CompactBlockReorgTests: ZcashTestCase { urls: Initializer.URLs( fsBlockDbRoot: testTempDirectory, dataDbURL: processorConfig.dataDb, + torDirURL: processorConfig.torDir, generalStorageURL: testGeneralStorageDirectory, spendParamsURL: processorConfig.spendParamsURL, outputParamsURL: processorConfig.outputParamsURL diff --git a/Tests/NetworkTests/DownloadTests.swift b/Tests/NetworkTests/DownloadTests.swift index 66b46859..56a7450d 100644 --- a/Tests/NetworkTests/DownloadTests.swift +++ b/Tests/NetworkTests/DownloadTests.swift @@ -23,6 +23,7 @@ class DownloadTests: ZcashTestCase { urls: Initializer.URLs( fsBlockDbRoot: testTempDirectory, dataDbURL: try! __dataDbURL(), + torDirURL: try! __torDirURL(), generalStorageURL: testGeneralStorageDirectory, spendParamsURL: try! __spendParamsURL(), outputParamsURL: try! __outputParamsURL() diff --git a/Tests/OfflineTests/CompactBlockProcessorActions/MigrateLegacyCacheDBActionTests.swift b/Tests/OfflineTests/CompactBlockProcessorActions/MigrateLegacyCacheDBActionTests.swift index 127e589a..ef902605 100644 --- a/Tests/OfflineTests/CompactBlockProcessorActions/MigrateLegacyCacheDBActionTests.swift +++ b/Tests/OfflineTests/CompactBlockProcessorActions/MigrateLegacyCacheDBActionTests.swift @@ -235,6 +235,7 @@ final class MigrateLegacyCacheDBActionTests: ZcashTestCase { cacheDbURL: underlyingCacheDbURL ?? defaultConfig.cacheDbURL, fsBlockCacheRoot: underlyingFsBlockCacheRoot ?? defaultConfig.fsBlockCacheRoot, dataDb: defaultConfig.dataDb, + torDir: defaultConfig.torDir, spendParamsURL: defaultConfig.spendParamsURL, outputParamsURL: defaultConfig.outputParamsURL, saplingParamsSourceURL: defaultConfig.saplingParamsSourceURL, diff --git a/Tests/OfflineTests/InitializerOfflineTests.swift b/Tests/OfflineTests/InitializerOfflineTests.swift index 1c781f29..80b1419a 100644 --- a/Tests/OfflineTests/InitializerOfflineTests.swift +++ b/Tests/OfflineTests/InitializerOfflineTests.swift @@ -20,6 +20,7 @@ class InitializerOfflineTests: XCTestCase { private func makeInitializer( fsBlockDbRoot: URL, dataDbURL: URL, + torDirURL: URL, generalStorageURL: URL, spendParamsURL: URL, outputParamsURL: URL, @@ -30,6 +31,7 @@ class InitializerOfflineTests: XCTestCase { fsBlockDbRoot: fsBlockDbRoot, generalStorageURL: generalStorageURL, dataDbURL: dataDbURL, + torDirURL: torDirURL, endpoint: LightWalletEndpointBuilder.default, network: ZcashNetworkBuilder.network(for: .testnet), spendParamsURL: spendParamsURL, @@ -54,6 +56,7 @@ class InitializerOfflineTests: XCTestCase { private func genericTestForURLsParsingFailures( fsBlockDbRoot: URL, dataDbURL: URL, + torDirURL: URL, generalStorageURL: URL, spendParamsURL: URL, outputParamsURL: URL, @@ -63,6 +66,7 @@ class InitializerOfflineTests: XCTestCase { let initializer = makeInitializer( fsBlockDbRoot: fsBlockDbRoot, dataDbURL: dataDbURL, + torDirURL: torDirURL, generalStorageURL: generalStorageURL, spendParamsURL: spendParamsURL, outputParamsURL: outputParamsURL, @@ -85,6 +89,7 @@ class InitializerOfflineTests: XCTestCase { let initializer = makeInitializer( fsBlockDbRoot: validDirectoryURL, dataDbURL: validFileURL, + torDirURL: validDirectoryURL, generalStorageURL: validDirectoryURL, spendParamsURL: validFileURL, outputParamsURL: validFileURL, @@ -102,6 +107,7 @@ class InitializerOfflineTests: XCTestCase { genericTestForURLsParsingFailures( fsBlockDbRoot: invalidPathURL, dataDbURL: validFileURL, + torDirURL: validDirectoryURL, generalStorageURL: validDirectoryURL, spendParamsURL: validFileURL, outputParamsURL: validFileURL, @@ -113,6 +119,19 @@ class InitializerOfflineTests: XCTestCase { genericTestForURLsParsingFailures( fsBlockDbRoot: validDirectoryURL, dataDbURL: invalidPathURL, + torDirURL: validDirectoryURL, + generalStorageURL: validDirectoryURL, + spendParamsURL: validFileURL, + outputParamsURL: validFileURL, + alias: .default + ) + } + + func test__defaultAlias__invalidTorDirURL__errorIsGenerated() { + genericTestForURLsParsingFailures( + fsBlockDbRoot: validDirectoryURL, + dataDbURL: validFileURL, + torDirURL: invalidPathURL, generalStorageURL: validDirectoryURL, spendParamsURL: validFileURL, outputParamsURL: validFileURL, @@ -124,6 +143,7 @@ class InitializerOfflineTests: XCTestCase { genericTestForURLsParsingFailures( fsBlockDbRoot: validDirectoryURL, dataDbURL: validFileURL, + torDirURL: validDirectoryURL, generalStorageURL: validDirectoryURL, spendParamsURL: invalidPathURL, outputParamsURL: validFileURL, @@ -135,6 +155,7 @@ class InitializerOfflineTests: XCTestCase { genericTestForURLsParsingFailures( fsBlockDbRoot: validDirectoryURL, dataDbURL: validFileURL, + torDirURL: validDirectoryURL, generalStorageURL: validDirectoryURL, spendParamsURL: validFileURL, outputParamsURL: invalidPathURL, @@ -146,6 +167,7 @@ class InitializerOfflineTests: XCTestCase { genericTestForURLsParsingFailures( fsBlockDbRoot: validDirectoryURL, dataDbURL: validFileURL, + torDirURL: validDirectoryURL, generalStorageURL: invalidPathURL, spendParamsURL: validFileURL, outputParamsURL: validFileURL, @@ -158,6 +180,7 @@ class InitializerOfflineTests: XCTestCase { let initializer = makeInitializer( fsBlockDbRoot: validDirectoryURL, dataDbURL: validFileURL, + torDirURL: validDirectoryURL, generalStorageURL: validDirectoryURL, spendParamsURL: validFileURL, outputParamsURL: validFileURL, @@ -175,6 +198,7 @@ class InitializerOfflineTests: XCTestCase { genericTestForURLsParsingFailures( fsBlockDbRoot: invalidPathURL, dataDbURL: validFileURL, + torDirURL: validDirectoryURL, generalStorageURL: validDirectoryURL, spendParamsURL: validFileURL, outputParamsURL: validFileURL, @@ -186,6 +210,19 @@ class InitializerOfflineTests: XCTestCase { genericTestForURLsParsingFailures( fsBlockDbRoot: validDirectoryURL, dataDbURL: invalidPathURL, + torDirURL: validDirectoryURL, + generalStorageURL: validDirectoryURL, + spendParamsURL: validFileURL, + outputParamsURL: validFileURL, + alias: .custom("alias") + ) + } + + func test__customAlias__invalidTorDirURL__errorIsGenerated() { + genericTestForURLsParsingFailures( + fsBlockDbRoot: validDirectoryURL, + dataDbURL: validFileURL, + torDirURL: invalidPathURL, generalStorageURL: validDirectoryURL, spendParamsURL: validFileURL, outputParamsURL: validFileURL, @@ -197,6 +234,7 @@ class InitializerOfflineTests: XCTestCase { genericTestForURLsParsingFailures( fsBlockDbRoot: validDirectoryURL, dataDbURL: validFileURL, + torDirURL: validDirectoryURL, generalStorageURL: validDirectoryURL, spendParamsURL: invalidPathURL, outputParamsURL: validFileURL, @@ -208,6 +246,7 @@ class InitializerOfflineTests: XCTestCase { genericTestForURLsParsingFailures( fsBlockDbRoot: validDirectoryURL, dataDbURL: validFileURL, + torDirURL: validDirectoryURL, generalStorageURL: validDirectoryURL, spendParamsURL: validFileURL, outputParamsURL: invalidPathURL, @@ -219,6 +258,7 @@ class InitializerOfflineTests: XCTestCase { genericTestForURLsParsingFailures( fsBlockDbRoot: validDirectoryURL, dataDbURL: validFileURL, + torDirURL: validDirectoryURL, generalStorageURL: invalidPathURL, spendParamsURL: validFileURL, outputParamsURL: validFileURL, diff --git a/Tests/OfflineTests/SynchronizerOfflineTests.swift b/Tests/OfflineTests/SynchronizerOfflineTests.swift index 3d701a14..181e1488 100644 --- a/Tests/OfflineTests/SynchronizerOfflineTests.swift +++ b/Tests/OfflineTests/SynchronizerOfflineTests.swift @@ -314,6 +314,7 @@ class SynchronizerOfflineTests: ZcashTestCase { fsBlockDbRoot: validDirectoryURL, generalStorageURL: validDirectoryURL, dataDbURL: invalidPathURL, + torDirURL: validDirectoryURL, endpoint: LightWalletEndpointBuilder.default, network: ZcashNetworkBuilder.network(for: .testnet), spendParamsURL: validFileURL, @@ -349,6 +350,7 @@ class SynchronizerOfflineTests: ZcashTestCase { fsBlockDbRoot: validDirectoryURL, generalStorageURL: validDirectoryURL, dataDbURL: invalidPathURL, + torDirURL: validDirectoryURL, endpoint: LightWalletEndpointBuilder.default, network: ZcashNetworkBuilder.network(for: .testnet), spendParamsURL: validFileURL, diff --git a/Tests/OfflineTests/WalletTests.swift b/Tests/OfflineTests/WalletTests.swift index ea6321fc..ce057bae 100644 --- a/Tests/OfflineTests/WalletTests.swift +++ b/Tests/OfflineTests/WalletTests.swift @@ -45,6 +45,7 @@ class WalletTests: ZcashTestCase { fsBlockDbRoot: testTempDirectory, generalStorageURL: testGeneralStorageDirectory, dataDbURL: try __dataDbURL(), + torDirURL: try __torDirURL(), endpoint: LightWalletEndpointBuilder.default, network: network, spendParamsURL: try __spendParamsURL(), diff --git a/Tests/PerformanceTests/SynchronizerTests.swift b/Tests/PerformanceTests/SynchronizerTests.swift index 6ca368db..9d526b65 100644 --- a/Tests/PerformanceTests/SynchronizerTests.swift +++ b/Tests/PerformanceTests/SynchronizerTests.swift @@ -64,6 +64,7 @@ class SynchronizerTests: ZcashTestCase { fsBlockDbRoot: databases.fsCacheDbRoot, generalStorageURL: testGeneralStorageDirectory, dataDbURL: databases.dataDB, + torDirURL: databases.torDir, endpoint: endpoint, network: network, spendParamsURL: try __spendParamsURL(), diff --git a/Tests/TestUtils/DarkSideWalletService.swift b/Tests/TestUtils/DarkSideWalletService.swift index 2b1fbb56..1f3e9c0c 100644 --- a/Tests/TestUtils/DarkSideWalletService.swift +++ b/Tests/TestUtils/DarkSideWalletService.swift @@ -206,7 +206,11 @@ enum DarksideWalletDConstants: NetworkConstants { static var defaultDataDbName: String { ZcashSDKMainnetConstants.defaultDataDbName } - + + static var defaultTorDirName: String { + ZcashSDKMainnetConstants.defaultTorDirName + } + static var defaultCacheDbName: String { ZcashSDKMainnetConstants.defaultCacheDbName } diff --git a/Tests/TestUtils/Sourcery/GeneratedMocks/AutoMockable.generated.swift b/Tests/TestUtils/Sourcery/GeneratedMocks/AutoMockable.generated.swift index 06383650..9e5b1276 100644 --- a/Tests/TestUtils/Sourcery/GeneratedMocks/AutoMockable.generated.swift +++ b/Tests/TestUtils/Sourcery/GeneratedMocks/AutoMockable.generated.swift @@ -1072,6 +1072,24 @@ class LoggerMock: Logger { ) { } + // MARK: - maxLogLevel + + var maxLogLevelCallsCount = 0 + var maxLogLevelCalled: Bool { + return maxLogLevelCallsCount > 0 + } + var maxLogLevelReturnValue: OSLogger.LogLevel? + var maxLogLevelClosure: (() -> OSLogger.LogLevel?)? + + func maxLogLevel() -> OSLogger.LogLevel? { + maxLogLevelCallsCount += 1 + if let closure = maxLogLevelClosure { + return closure() + } else { + return maxLogLevelReturnValue + } + } + // MARK: - debug var debugFileFunctionLineCallsCount = 0 @@ -1780,6 +1798,28 @@ class SynchronizerMock: Synchronizer { } } + // MARK: - getExchangeRateUSD + + var getExchangeRateUSDThrowableError: Error? + var getExchangeRateUSDCallsCount = 0 + var getExchangeRateUSDCalled: Bool { + return getExchangeRateUSDCallsCount > 0 + } + var getExchangeRateUSDReturnValue: Float64! + var getExchangeRateUSDClosure: (() async throws -> Float64)? + + func getExchangeRateUSD() async throws -> Float64 { + if let error = getExchangeRateUSDThrowableError { + throw error + } + getExchangeRateUSDCallsCount += 1 + if let closure = getExchangeRateUSDClosure { + return try await closure() + } else { + return getExchangeRateUSDReturnValue + } + } + // MARK: - rewind var rewindCallsCount = 0 diff --git a/Tests/TestUtils/Stubs.swift b/Tests/TestUtils/Stubs.swift index 62c70d3e..7bf86639 100644 --- a/Tests/TestUtils/Stubs.swift +++ b/Tests/TestUtils/Stubs.swift @@ -132,6 +132,7 @@ extension CompactBlockProcessor.Configuration { alias: alias, fsBlockCacheRoot: pathProvider.fsCacheURL, dataDb: pathProvider.dataDbURL, + torDir: pathProvider.torDirURL, spendParamsURL: pathProvider.spendParamsURL, outputParamsURL: pathProvider.outputParamsURL, saplingParamsSourceURL: SaplingParamsSourceURL.tests, diff --git a/Tests/TestUtils/TestCoordinator.swift b/Tests/TestUtils/TestCoordinator.swift index e17103aa..47a30ffd 100644 --- a/Tests/TestUtils/TestCoordinator.swift +++ b/Tests/TestUtils/TestCoordinator.swift @@ -72,6 +72,7 @@ class TestCoordinator { fsBlockDbRoot: databases.fsCacheDbRoot, generalStorageURL: databases.generalStorageURL, dataDbURL: databases.dataDB, + torDirURL: databases.torDir, endpoint: endpoint, network: network, spendParamsURL: try __spendParamsURL(), @@ -216,6 +217,7 @@ extension TestCoordinator { alias: config.alias, fsBlockCacheRoot: config.fsBlockCacheRoot, dataDb: config.dataDb, + torDir: config.torDir, spendParamsURL: config.spendParamsURL, outputParamsURL: config.outputParamsURL, saplingParamsSourceURL: config.saplingParamsSourceURL, @@ -246,6 +248,7 @@ extension TestCoordinator { struct TemporaryTestDatabases { var fsCacheDbRoot: URL let generalStorageURL: URL + var torDir: URL var dataDB: URL } @@ -257,6 +260,7 @@ enum TemporaryDbBuilder { return TemporaryTestDatabases( fsCacheDbRoot: tempUrl.appendingPathComponent("fs_cache_\(timestamp)"), generalStorageURL: tempUrl.appendingPathComponent("general_storage_\(timestamp)"), + torDir: tempUrl.appendingPathComponent("tor_\(timestamp)"), dataDB: tempUrl.appendingPathComponent("data_db_\(timestamp).db") ) } diff --git a/Tests/TestUtils/Tests+Utils.swift b/Tests/TestUtils/Tests+Utils.swift index 08c35345..65699a2a 100644 --- a/Tests/TestUtils/Tests+Utils.swift +++ b/Tests/TestUtils/Tests+Utils.swift @@ -96,6 +96,10 @@ func __dataDbURL() throws -> URL { try __documentsDirectory().appendingPathComponent("data.db", isDirectory: false) } +func __torDirURL() throws -> URL { + try __documentsDirectory().appendingPathComponent("tor", isDirectory: true) +} + func __spendParamsURL() throws -> URL { try __documentsDirectory().appendingPathComponent("sapling-spend.params") } diff --git a/Tests/TestUtils/TestsData.swift b/Tests/TestUtils/TestsData.swift index 058c6a7b..c3787a52 100644 --- a/Tests/TestUtils/TestsData.swift +++ b/Tests/TestUtils/TestsData.swift @@ -17,6 +17,7 @@ class TestsData { fsBlockDbRoot: URL(fileURLWithPath: "/"), generalStorageURL: URL(fileURLWithPath: "/"), dataDbURL: URL(fileURLWithPath: "/"), + torDirURL: URL(fileURLWithPath: "/"), endpoint: LightWalletEndpointBuilder.default, network: ZcashNetworkBuilder.network(for: networkType), spendParamsURL: URL(fileURLWithPath: "/"),