diff --git a/CHANGELOG.md b/CHANGELOG.md index 360e55a3..d0e1ef97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,14 @@ and this library adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### [#1363] Account balances in the SynchronizerState `shieldedBalance: WalletBalance` has been replaced with `accountBalances: AccountBalance`. `AccountBalance` provides the same values as `shieldedBalance` but adds up a pending changes. Under the hood this calls rust's `getWalletSummary` which improved also the syncing initial values of % and balances. +## Added + +### [#1153] Allow runtime switch of lightwalletd servers +New API implemented that allows clients to change the `mainnet` endpoint. Use `func switchTo(endpoint: LightWalletEndpoint) async throws`. +Possible errors: +- `ZcashError.synchronizerServerSwitch`: endpoint fails, check the address, port and format address:port, +- Some `ZcashError` related to `synchronizer.Start()`: the switch calls `start()` at the end and that is the only throwing function except the validation. + # 2.0.8 - 2024-01-30 Adopt `zcash-light-client-ffi 0.5.1`. This fixes a serialization problem diff --git a/Example/ZcashLightClientSample/ZcashLightClientSample/Base.lproj/Main.storyboard b/Example/ZcashLightClientSample/ZcashLightClientSample/Base.lproj/Main.storyboard index 83913e94..d7d0b1c1 100644 --- a/Example/ZcashLightClientSample/ZcashLightClientSample/Base.lproj/Main.storyboard +++ b/Example/ZcashLightClientSample/ZcashLightClientSample/Base.lproj/Main.storyboard @@ -1,9 +1,9 @@ - + - + @@ -959,16 +959,16 @@ - + - + - + @@ -1399,7 +1399,7 @@ - + @@ -1408,10 +1408,10 @@ - + - + diff --git a/Example/ZcashLightClientSample/ZcashLightClientSample/DemoAppConfig.swift b/Example/ZcashLightClientSample/ZcashLightClientSample/DemoAppConfig.swift index c6501e5f..56ab8c39 100644 --- a/Example/ZcashLightClientSample/ZcashLightClientSample/DemoAppConfig.swift +++ b/Example/ZcashLightClientSample/ZcashLightClientSample/DemoAppConfig.swift @@ -21,15 +21,15 @@ enum DemoAppConfig { static let host = ZcashSDK.isMainnet ? "mainnet.lightwalletd.com" : "lightwalletd.testnet.electriccoin.co" static let port: Int = 9067 -// static let defaultBirthdayHeight: BlockHeight = ZcashSDK.isMainnet ? 935000 : 1386000 -// static let defaultSeed = try! Mnemonic.deterministicSeedBytes(from: """ -// live combine flight accident slow soda mind bright absent bid hen shy decade biology amazing mix enlist ensure biology rhythm snap duty soap armor -// """) + static let defaultBirthdayHeight: BlockHeight = ZcashSDK.isMainnet ? 935000 : 1386000 + +// static let defaultSeed = try! Mnemonic.deterministicSeedBytes(from: """ +// wish puppy smile loan doll curve hole maze file ginger hair nose key relax knife witness cannon grab despair throw review deal slush frame +// """) - static let defaultBirthdayHeight: BlockHeight = ZcashSDK.isMainnet ? 1935000 : 2170000 static let defaultSeed = try! Mnemonic.deterministicSeedBytes(from: """ - wish puppy smile loan doll curve hole maze file ginger hair nose key relax knife witness cannon grab despair throw review deal slush frame - """) + live combine flight accident slow soda mind bright absent bid hen shy decade biology amazing mix enlist ensure biology rhythm snap duty soap armor + """) static let otherSynchronizers: [SynchronizerInitData] = [ SynchronizerInitData( @@ -53,7 +53,7 @@ enum DemoAppConfig { static var address: String { "\(host):\(port)" } - + static var endpoint: LightWalletEndpoint { return LightWalletEndpoint(address: self.host, port: self.port, secure: true, streamingCallTimeoutInMillis: 10 * 60 * 60 * 1000) } diff --git a/Sources/ZcashLightClientKit/Block/Actions/DownloadAction.swift b/Sources/ZcashLightClientKit/Block/Actions/DownloadAction.swift index a2a17607..152356ff 100644 --- a/Sources/ZcashLightClientKit/Block/Actions/DownloadAction.swift +++ b/Sources/ZcashLightClientKit/Block/Actions/DownloadAction.swift @@ -9,7 +9,7 @@ import Foundation final class DownloadAction { let configProvider: CompactBlockProcessor.ConfigProvider - let downloader: BlockDownloader + var downloader: BlockDownloader let transactionRepository: TransactionRepository let logger: Logger diff --git a/Sources/ZcashLightClientKit/Block/Actions/EnhanceAction.swift b/Sources/ZcashLightClientKit/Block/Actions/EnhanceAction.swift index 40837011..32ecdb7a 100644 --- a/Sources/ZcashLightClientKit/Block/Actions/EnhanceAction.swift +++ b/Sources/ZcashLightClientKit/Block/Actions/EnhanceAction.swift @@ -8,7 +8,7 @@ import Foundation final class EnhanceAction { - let blockEnhancer: BlockEnhancer + var blockEnhancer: BlockEnhancer let configProvider: CompactBlockProcessor.ConfigProvider let logger: Logger diff --git a/Sources/ZcashLightClientKit/Block/Actions/FetchUTXOsAction.swift b/Sources/ZcashLightClientKit/Block/Actions/FetchUTXOsAction.swift index a109e5c7..6d027829 100644 --- a/Sources/ZcashLightClientKit/Block/Actions/FetchUTXOsAction.swift +++ b/Sources/ZcashLightClientKit/Block/Actions/FetchUTXOsAction.swift @@ -8,7 +8,7 @@ import Foundation final class FetchUTXOsAction { - let utxoFetcher: UTXOFetcher + var utxoFetcher: UTXOFetcher let logger: Logger init(container: DIContainer) { diff --git a/Sources/ZcashLightClientKit/Block/Actions/ProcessSuggestedScanRangesAction.swift b/Sources/ZcashLightClientKit/Block/Actions/ProcessSuggestedScanRangesAction.swift index 1132e379..603f9a04 100644 --- a/Sources/ZcashLightClientKit/Block/Actions/ProcessSuggestedScanRangesAction.swift +++ b/Sources/ZcashLightClientKit/Block/Actions/ProcessSuggestedScanRangesAction.swift @@ -9,7 +9,7 @@ import Foundation final class ProcessSuggestedScanRangesAction { let rustBackend: ZcashRustBackendWelding - let service: LightWalletService + var service: LightWalletService let logger: Logger let metrics: SDKMetrics diff --git a/Sources/ZcashLightClientKit/Block/Actions/RewindAction.swift b/Sources/ZcashLightClientKit/Block/Actions/RewindAction.swift index 9125326f..d5654d6d 100644 --- a/Sources/ZcashLightClientKit/Block/Actions/RewindAction.swift +++ b/Sources/ZcashLightClientKit/Block/Actions/RewindAction.swift @@ -8,9 +8,9 @@ import Foundation final class RewindAction { - let downloader: BlockDownloader + var downloader: BlockDownloader let rustBackend: ZcashRustBackendWelding - let downloaderService: BlockDownloaderService + var downloaderService: BlockDownloaderService let logger: Logger init(container: DIContainer) { diff --git a/Sources/ZcashLightClientKit/Block/Actions/ScanAction.swift b/Sources/ZcashLightClientKit/Block/Actions/ScanAction.swift index 6779bcf6..8ecbb9cb 100644 --- a/Sources/ZcashLightClientKit/Block/Actions/ScanAction.swift +++ b/Sources/ZcashLightClientKit/Block/Actions/ScanAction.swift @@ -15,7 +15,7 @@ final class ScanAction { let configProvider: CompactBlockProcessor.ConfigProvider let blockScanner: BlockScanner let rustBackend: ZcashRustBackendWelding - let latestBlocksDataProvider: LatestBlocksDataProvider + var latestBlocksDataProvider: LatestBlocksDataProvider let logger: Logger var progressReportReducer = 0 diff --git a/Sources/ZcashLightClientKit/Block/Actions/UpdateChainTipAction.swift b/Sources/ZcashLightClientKit/Block/Actions/UpdateChainTipAction.swift index 6b54bdca..6ad2639e 100644 --- a/Sources/ZcashLightClientKit/Block/Actions/UpdateChainTipAction.swift +++ b/Sources/ZcashLightClientKit/Block/Actions/UpdateChainTipAction.swift @@ -9,9 +9,9 @@ import Foundation final class UpdateChainTipAction { let rustBackend: ZcashRustBackendWelding - let downloader: BlockDownloader - let service: LightWalletService - let latestBlocksDataProvider: LatestBlocksDataProvider + var downloader: BlockDownloader + var service: LightWalletService + var latestBlocksDataProvider: LatestBlocksDataProvider let logger: Logger init(container: DIContainer) { diff --git a/Sources/ZcashLightClientKit/Block/Actions/UpdateSubtreeRootsAction.swift b/Sources/ZcashLightClientKit/Block/Actions/UpdateSubtreeRootsAction.swift index 89683412..40b6ca68 100644 --- a/Sources/ZcashLightClientKit/Block/Actions/UpdateSubtreeRootsAction.swift +++ b/Sources/ZcashLightClientKit/Block/Actions/UpdateSubtreeRootsAction.swift @@ -10,7 +10,7 @@ import Foundation final class UpdateSubtreeRootsAction { let configProvider: CompactBlockProcessor.ConfigProvider let rustBackend: ZcashRustBackendWelding - let service: LightWalletService + var service: LightWalletService let logger: Logger init(container: DIContainer, configProvider: CompactBlockProcessor.ConfigProvider) { diff --git a/Sources/ZcashLightClientKit/Block/Actions/ValidateServerAction.swift b/Sources/ZcashLightClientKit/Block/Actions/ValidateServerAction.swift index 6fb4aa48..0458913a 100644 --- a/Sources/ZcashLightClientKit/Block/Actions/ValidateServerAction.swift +++ b/Sources/ZcashLightClientKit/Block/Actions/ValidateServerAction.swift @@ -10,7 +10,7 @@ import Foundation final class ValidateServerAction { let configProvider: CompactBlockProcessor.ConfigProvider let rustBackend: ZcashRustBackendWelding - let service: LightWalletService + var service: LightWalletService init(container: DIContainer, configProvider: CompactBlockProcessor.ConfigProvider) { self.configProvider = configProvider diff --git a/Sources/ZcashLightClientKit/Block/CompactBlockProcessor.swift b/Sources/ZcashLightClientKit/Block/CompactBlockProcessor.swift index 9669677c..39ce519c 100644 --- a/Sources/ZcashLightClientKit/Block/CompactBlockProcessor.swift +++ b/Sources/ZcashLightClientKit/Block/CompactBlockProcessor.swift @@ -29,12 +29,12 @@ actor CompactBlockProcessor { private var afterSyncHooksManager = AfterSyncHooksManager() private let accountRepository: AccountRepository - let blockDownloaderService: BlockDownloaderService - private let latestBlocksDataProvider: LatestBlocksDataProvider + var blockDownloaderService: BlockDownloaderService + private var latestBlocksDataProvider: LatestBlocksDataProvider private let logger: Logger private let metrics: SDKMetrics private let rustBackend: ZcashRustBackendWelding - let service: LightWalletService + var service: LightWalletService let storage: CompactBlockRepository private let transactionRepository: TransactionRepository private let fileManager: ZcashFileManager @@ -412,6 +412,52 @@ extension CompactBlockProcessor { } } +// MARK: - Switch server + +extension CompactBlockProcessor { + func updateService(_ container: DIContainer) { + // LightWalletGRPCService + let updatedLWDService = container.resolve(LightWalletService.self) + + (actions[.processSuggestedScanRanges] as? ProcessSuggestedScanRangesAction)?.service = updatedLWDService + (actions[.updateChainTip] as? UpdateChainTipAction)?.service = updatedLWDService + (actions[.updateSubtreeRoots] as? UpdateSubtreeRootsAction)?.service = updatedLWDService + (actions[.validateServer] as? ValidateServerAction)?.service = updatedLWDService + self.service = updatedLWDService + + // BlockDownloaderService + let updatedDownloaderService = container.resolve(BlockDownloaderService.self) + + (actions[.rewind] as? RewindAction)?.downloaderService = updatedDownloaderService + self.blockDownloaderService = updatedDownloaderService + + // LatestBlocksDataProvider + let updatedLBDProvider = container.resolve(LatestBlocksDataProvider.self) + + (actions[.scan] as? ScanAction)?.latestBlocksDataProvider = updatedLBDProvider + (actions[.updateChainTip] as? UpdateChainTipAction)?.latestBlocksDataProvider = updatedLBDProvider + self.latestBlocksDataProvider = updatedLBDProvider + + // BlockDownloader + let updatedBlockDownloader = container.resolve(BlockDownloader.self) + + (actions[.download] as? DownloadAction)?.downloader = updatedBlockDownloader + (actions[.updateChainTip] as? UpdateChainTipAction)?.downloader = updatedBlockDownloader + (actions[.rewind] as? RewindAction)?.downloader = updatedBlockDownloader + self.blockDownloaderService = updatedDownloaderService + + // BlockEnhancer + let updatedEnhancer = container.resolve(BlockEnhancer.self) + + (actions[.enhance] as? EnhanceAction)?.blockEnhancer = updatedEnhancer + + // UTXOFetcher + let updatedUTXOFetcher = container.resolve(UTXOFetcher.self) + + (actions[.fetchUTXO] as? FetchUTXOsAction)?.utxoFetcher = updatedUTXOFetcher + } +} + // MARK: - Events extension CompactBlockProcessor { diff --git a/Sources/ZcashLightClientKit/Error/Sourcery/generateErrorCode.sh b/Sources/ZcashLightClientKit/Error/Sourcery/generateErrorCode.sh index aea4cade..b240aea1 100755 --- a/Sources/ZcashLightClientKit/Error/Sourcery/generateErrorCode.sh +++ b/Sources/ZcashLightClientKit/Error/Sourcery/generateErrorCode.sh @@ -3,7 +3,7 @@ scriptDir=${0:a:h} cd "${scriptDir}" -sourcery_version=2.0.3 +sourcery_version=2.1.7 if which sourcery >/dev/null; then if [[ $(sourcery --version) != $sourcery_version ]]; then diff --git a/Sources/ZcashLightClientKit/Error/ZcashError.swift b/Sources/ZcashLightClientKit/Error/ZcashError.swift index f2fce14c..ac83f893 100644 --- a/Sources/ZcashLightClientKit/Error/ZcashError.swift +++ b/Sources/ZcashLightClientKit/Error/ZcashError.swift @@ -1,4 +1,4 @@ -// Generated using Sourcery 2.0.3 — https://github.com/krzysztofzablocki/Sourcery +// Generated using Sourcery 2.1.7 — https://github.com/krzysztofzablocki/Sourcery // DO NOT EDIT /* @@ -121,6 +121,11 @@ 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 @@ -148,6 +153,11 @@ 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 @@ -287,6 +297,10 @@ 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 @@ -570,6 +584,9 @@ public enum ZcashError: Equatable, Error { /// Indicates that this Synchronizer is disconnected from its lightwalletd server. /// ZSYNCO0006 case synchronizerDisconnected + /// The attempt to switch endpoints failed. Check that the hostname and port are correct, and are formatted as :. + /// ZSYNCO0007 + case synchronizerServerSwitch public var message: String { switch self { @@ -604,6 +621,7 @@ 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" @@ -611,6 +629,7 @@ 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" @@ -648,6 +667,7 @@ 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" @@ -737,6 +757,7 @@ public enum ZcashError: Equatable, Error { case .synchronizerLatestUTXOsInvalidTAddress: return "LatestUTXOs for the address failed, invalid t-address." case .synchronizerRewindUnknownArchorHeight: return "Rewind failed, unknown archor height" case .synchronizerDisconnected: return "Indicates that this Synchronizer is disconnected from its lightwalletd server." + case .synchronizerServerSwitch: return "The attempt to switch endpoints failed. Check that the hostname and port are correct, and are formatted as :." } } @@ -773,6 +794,7 @@ 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 @@ -780,6 +802,7 @@ 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 @@ -817,6 +840,7 @@ 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 @@ -906,6 +930,7 @@ public enum ZcashError: Equatable, Error { case .synchronizerLatestUTXOsInvalidTAddress: return .synchronizerLatestUTXOsInvalidTAddress case .synchronizerRewindUnknownArchorHeight: return .synchronizerRewindUnknownArchorHeight case .synchronizerDisconnected: return .synchronizerDisconnected + case .synchronizerServerSwitch: return .synchronizerServerSwitch } } diff --git a/Sources/ZcashLightClientKit/Error/ZcashErrorCode.swift b/Sources/ZcashLightClientKit/Error/ZcashErrorCode.swift index 1047244a..2814f579 100644 --- a/Sources/ZcashLightClientKit/Error/ZcashErrorCode.swift +++ b/Sources/ZcashLightClientKit/Error/ZcashErrorCode.swift @@ -1,4 +1,4 @@ -// Generated using Sourcery 2.0.3 — https://github.com/krzysztofzablocki/Sourcery +// Generated using Sourcery 2.1.7 — https://github.com/krzysztofzablocki/Sourcery // DO NOT EDIT /* @@ -71,6 +71,8 @@ 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 @@ -85,6 +87,8 @@ 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 @@ -159,6 +163,8 @@ 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 @@ -337,4 +343,6 @@ public enum ZcashErrorCode: String { case synchronizerRewindUnknownArchorHeight = "ZSYNCO0005" /// Indicates that this Synchronizer is disconnected from its lightwalletd server. case synchronizerDisconnected = "ZSYNCO0006" + /// The attempt to switch endpoints failed. Check that the hostname and port are correct, and are formatted as :. + case synchronizerServerSwitch = "ZSYNCO0007" } diff --git a/Sources/ZcashLightClientKit/Error/ZcashErrorCodeDefinition.swift b/Sources/ZcashLightClientKit/Error/ZcashErrorCodeDefinition.swift index 812b1c62..730ce3c9 100644 --- a/Sources/ZcashLightClientKit/Error/ZcashErrorCodeDefinition.swift +++ b/Sources/ZcashLightClientKit/Error/ZcashErrorCodeDefinition.swift @@ -344,7 +344,11 @@ enum ZcashErrorDefinition { /// - `progress` value reported // sourcery: code="ZRUST0055" case rustScanProgressOutOfRange(_ progress: String) - + /// Error from rust layer when calling ZcashRustBackend.getWalletSummary + /// - `rustError` contains error generated by the rust layer. + // sourcery: code="ZRUST0056" + case rustGetWalletSummary(_ rustError: String) + // MARK: - Account DAO /// SQLite query failed when fetching all accounts from the database. @@ -659,4 +663,7 @@ enum ZcashErrorDefinition { /// Indicates that this Synchronizer is disconnected from its lightwalletd server. // sourcery: code="ZSYNCO0006" case synchronizerDisconnected + /// The attempt to switch endpoints failed. Check that the hostname and port are correct, and are formatted as :. + // sourcery: code="ZSYNCO0007" + case synchronizerServerSwitch } diff --git a/Sources/ZcashLightClientKit/Initializer.swift b/Sources/ZcashLightClientKit/Initializer.swift index fcdd1572..db4e3c2a 100644 --- a/Sources/ZcashLightClientKit/Initializer.swift +++ b/Sources/ZcashLightClientKit/Initializer.swift @@ -111,18 +111,18 @@ public class Initializer { let container: DIContainer let alias: ZcashSynchronizerAlias - let endpoint: LightWalletEndpoint + var endpoint: LightWalletEndpoint let fsBlockDbRoot: URL let generalStorageURL: URL let dataDbURL: URL let spendParamsURL: URL let outputParamsURL: URL let saplingParamsSourceURL: SaplingParamsSourceURL - let lightWalletService: LightWalletService + var lightWalletService: LightWalletService let transactionRepository: TransactionRepository let accountRepository: AccountRepository let storage: CompactBlockRepository - let blockDownloaderService: BlockDownloaderService + var blockDownloaderService: BlockDownloaderService let network: ZcashNetwork let logger: Logger let rustBackend: ZcashRustBackendWelding @@ -286,10 +286,6 @@ public class Initializer { self.logger = container.resolve(Logger.self) } - private static func makeLightWalletServiceFactory(endpoint: LightWalletEndpoint) -> LightWalletServiceFactory { - return LightWalletServiceFactory(endpoint: endpoint) - } - // swiftlint:disable:next function_parameter_count private static func setup( container: DIContainer, diff --git a/Sources/ZcashLightClientKit/Synchronizer.swift b/Sources/ZcashLightClientKit/Synchronizer.swift index 2183004b..83b58c16 100644 --- a/Sources/ZcashLightClientKit/Synchronizer.swift +++ b/Sources/ZcashLightClientKit/Synchronizer.swift @@ -295,6 +295,12 @@ public protocol Synchronizer: AnyObject { /// this happens it means that some path passed to `Initializer` is invalid. The SDK can't recover from this and this instance won't do anything. /// func wipe() -> AnyPublisher + + /// This API stops the synchronization and re-initalizes everything according to the new endpoint provided. + /// It can be called anytime. + /// - Throws: ZcashError when failures occur and related to `synchronizer.start(retry: Bool)`, it's the only throwing operation + /// during the whole endpoint change. + func switchTo(endpoint: LightWalletEndpoint) async throws } public enum SyncStatus: Equatable { diff --git a/Sources/ZcashLightClientKit/Synchronizer/Dependencies.swift b/Sources/ZcashLightClientKit/Synchronizer/Dependencies.swift index 3bd037fa..05c48053 100644 --- a/Sources/ZcashLightClientKit/Synchronizer/Dependencies.swift +++ b/Sources/ZcashLightClientKit/Synchronizer/Dependencies.swift @@ -18,7 +18,7 @@ enum Dependencies { enableBackendTracing: Bool = false ) { container.register(type: CheckpointSource.self, isSingleton: true) { _ in - return CheckpointSourceFactory.fromBundle(for: networkType) + CheckpointSourceFactory.fromBundle(for: networkType) } container.register(type: Logger.self, isSingleton: true) { _ in @@ -36,7 +36,7 @@ enum Dependencies { } container.register(type: ZcashRustBackendWelding.self, isSingleton: true) { _ in - return ZcashRustBackend( + ZcashRustBackend( dbData: urls.dataDbURL, fsBlockDbRoot: urls.fsBlockDbRoot, spendParamsPath: urls.spendParamsURL, @@ -47,7 +47,7 @@ enum Dependencies { } container.register(type: LightWalletService.self, isSingleton: true) { _ in - return LightWalletGRPCService(endpoint: endpoint) + LightWalletGRPCService(endpoint: endpoint) } container.register(type: TransactionRepository.self, isSingleton: true) { _ in diff --git a/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift b/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift index 3ba1e74f..b11c7c7f 100644 --- a/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift +++ b/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift @@ -44,7 +44,7 @@ public class SDKSynchronizer: Synchronizer { private let syncSessionIDGenerator: SyncSessionIDGenerator private let syncSession: SyncSession private let syncSessionTicker: SessionTicker - let latestBlocksDataProvider: LatestBlocksDataProvider + var latestBlocksDataProvider: LatestBlocksDataProvider /// Creates an SDKSynchronizer instance /// - Parameter initializer: a wallet Initializer object @@ -532,6 +532,90 @@ public class SDKSynchronizer: Synchronizer { return subject.eraseToAnyPublisher() } + // MARK: Server switch + + public func switchTo(endpoint: LightWalletEndpoint) async throws { + // Stop synchronization + let status = await self.status + if status != .stopped && status != .disconnected { + await blockProcessor.stop() + } + + // Validation of the server is first because any custom endpoint can be passed here + // Extra instance of the service is created with lower timeout ofr a single call + initializer.container.register(type: LightWalletService.self, isSingleton: true) { _ in + LightWalletGRPCService( + host: endpoint.host, + port: endpoint.port, + secure: endpoint.secure, + singleCallTimeout: 5000, + streamingCallTimeout: endpoint.streamingCallTimeoutInMillis + ) + } + + let validateSever = ValidateServerAction( + container: initializer.container, + configProvider: CompactBlockProcessor.ConfigProvider(config: await blockProcessor.config) + ) + + do { + _ = try await validateSever.run(with: ActionContextImpl(state: .idle)) { _ in } + } catch { + throw ZcashError.synchronizerServerSwitch + } + + // The `ValidateServerAction` confirmed the server is ok and we can continue + // final instance of the service will be instantiated and propagated to the all parties + + // SWITCH TO NEW ENDPOINT + + // LightWalletService dependency update + initializer.container.register(type: LightWalletService.self, isSingleton: true) { _ in + LightWalletGRPCService(endpoint: endpoint) + } + + // DEPENDENCIES + + // BlockDownloaderService dependency update + initializer.container.register(type: BlockDownloaderService.self, isSingleton: true) { di in + let service = di.resolve(LightWalletService.self) + let storage = di.resolve(CompactBlockRepository.self) + + return BlockDownloaderServiceImpl(service: service, storage: storage) + } + + // LatestBlocksDataProvider dependency update + initializer.container.register(type: LatestBlocksDataProvider.self, isSingleton: true) { di in + let service = di.resolve(LightWalletService.self) + let rustBackend = di.resolve(ZcashRustBackendWelding.self) + + return LatestBlocksDataProviderImpl(service: service, rustBackend: rustBackend) + } + + // CompactBlockProcessor dependency update + Dependencies.setupCompactBlockProcessor( + in: initializer.container, + config: await blockProcessor.config, + accountRepository: initializer.accountRepository + ) + + // INITIALIZER + initializer.lightWalletService = initializer.container.resolve(LightWalletService.self) + initializer.blockDownloaderService = initializer.container.resolve(BlockDownloaderService.self) + initializer.endpoint = endpoint + + // SELF + self.latestBlocksDataProvider = initializer.container.resolve(LatestBlocksDataProvider.self) + + // COMPACT BLOCK PROCESSOR + await blockProcessor.updateService(initializer.container) + + // Start synchronization + if status != .unprepared { + try await start(retry: true) + } + } + // MARK: notify state private func snapshotState(status: InternalSyncStatus) async -> SynchronizerState { diff --git a/Tests/TestUtils/Sourcery/GeneratedMocks/AutoMockable.generated.swift b/Tests/TestUtils/Sourcery/GeneratedMocks/AutoMockable.generated.swift index ba8805fc..4f14bb0b 100644 --- a/Tests/TestUtils/Sourcery/GeneratedMocks/AutoMockable.generated.swift +++ b/Tests/TestUtils/Sourcery/GeneratedMocks/AutoMockable.generated.swift @@ -1733,6 +1733,25 @@ class SynchronizerMock: Synchronizer { } } + // MARK: - switchTo + + var switchToEndpointThrowableError: Error? + var switchToEndpointCallsCount = 0 + var switchToEndpointCalled: Bool { + return switchToEndpointCallsCount > 0 + } + var switchToEndpointReceivedEndpoint: LightWalletEndpoint? + var switchToEndpointClosure: ((LightWalletEndpoint) async throws -> Void)? + + func switchTo(endpoint: LightWalletEndpoint) async throws { + if let error = switchToEndpointThrowableError { + throw error + } + switchToEndpointCallsCount += 1 + switchToEndpointReceivedEndpoint = endpoint + try await switchToEndpointClosure!(endpoint) + } + } class TransactionRepositoryMock: TransactionRepository {