diff --git a/ZcashLightClientKit/Initializer.swift b/ZcashLightClientKit/Initializer.swift index 4d21ef5d..29ff658a 100644 --- a/ZcashLightClientKit/Initializer.swift +++ b/ZcashLightClientKit/Initializer.swift @@ -66,6 +66,7 @@ public class Initializer { private(set) var transactionRepository: TransactionRepository private(set) var accountRepository: AccountRepository private(set) var downloader: CompactBlockDownloader + private(set) public var viewingKeys: [UnifiedViewingKey] private(set) public var walletBirthday: WalletBirthday /** Constructs the Initializer @@ -83,6 +84,7 @@ public class Initializer { endpoint: LightWalletEndpoint, spendParamsURL: URL, outputParamsURL: URL, + viewingKeys: [UnifiedViewingKey], walletBirthday: BlockHeight = ZcashSDK.SAPLING_ACTIVATION_HEIGHT, alias: String = "", loggerProxy: Logger? = nil) { @@ -104,6 +106,7 @@ public class Initializer { downloader: CompactBlockDownloader(service: lwdService, storage: storage), spendParamsURL: spendParamsURL, outputParamsURL: outputParamsURL, + viewingKeys: viewingKeys, walletBirthday: walletBirthday, alias: alias, loggerProxy: loggerProxy @@ -125,6 +128,7 @@ public class Initializer { downloader: CompactBlockDownloader, spendParamsURL: URL, outputParamsURL: URL, + viewingKeys: [UnifiedViewingKey], walletBirthday: BlockHeight, alias: String = "", loggerProxy: Logger? = nil @@ -144,69 +148,13 @@ public class Initializer { self.transactionRepository = repository self.accountRepository = accountRepository self.downloader = downloader + self.viewingKeys = viewingKeys self.walletBirthday = WalletBirthday.birthday(with: walletBirthday) } - /** - Initialize the wallet with the given seed and return the related private keys for each - account specified or null if the wallet was previously initialized and block data exists on - disk. When this method returns null, that signals that the wallet will need to retrieve the - private keys from its own secure storage. In other words, the private keys are only given out - once for each set of database files. Subsequent calls to [initialize] will only load the Rust - library and return null. - - 'compactBlockCache.db' and 'transactionData.db' files are created by this function (if they - do not already exist). These files can be given a prefix for scenarios where multiple wallets - operate in one app--for instance, when sweeping funds from another wallet seed. - - Parameters: - - seedProvider: the seed to use for initializing this wallet. - - walletBirthdayHeight: the height corresponding to when the wallet seed was created. If null, this signals that the wallet is being born. - - numberOfAccounts: the number of accounts to create from this seed. - */ - - public func initialize(seedBytes: [UInt8], numberOfAccounts: Int = 1) throws -> [String]? { - - do { - try rustBackend.initDataDb(dbData: dataDbURL) - } catch RustWeldingError.dataDbNotEmpty { - // this is fine - } catch { - throw InitializerError.dataDbInitFailed - } - - do { - try rustBackend.initBlocksTable(dbData: dataDbURL, height: Int32(walletBirthday.height), hash: walletBirthday.hash, time: walletBirthday.time, saplingTree: walletBirthday.tree) - } catch RustWeldingError.dataDbNotEmpty { - // this is fine - } catch { - throw InitializerError.dataDbInitFailed - } - - let lastDownloaded = (try? downloader.storage.latestHeight()) ?? self.walletBirthday.height - // resume from last downloaded block - lowerBoundHeight = max(walletBirthday.height, lastDownloaded) - - var accounts: [String]? = nil - do { - guard let a = rustBackend.initAccountsTable(dbData: dataDbURL, seed: seedBytes, accounts: Int32(numberOfAccounts)) else { - throw rustBackend.lastError() ?? InitializerError.accountInitFailed - } - accounts = a - } catch RustWeldingError.dataDbNotEmpty { - // this is fine - } catch { - throw InitializerError.dataDbInitFailed - } - let migrationManager = MigrationManager(cacheDbConnection: SimpleConnectionProvider(path: cacheDbURL.path), - dataDbConnection: SimpleConnectionProvider(path: dataDbURL.path), pendingDbConnection: SimpleConnectionProvider(path: pendingDbURL.path)) - - try migrationManager.performMigration(seedBytes: seedBytes) - return accounts - } /** - - __TEMPORARILY UNAVAILABLE__ + Initialize the wallet with the given seed and return the related private keys for each account specified or null if the wallet was previously initialized and block data exists on disk. When this method returns null, that signals that the wallet will need to retrieve the @@ -221,7 +169,7 @@ public class Initializer { - viewingKeys: Extended Full Viewing Keys to initialize the DBs with */ - public func initialize(unifiedViewingKeys: [UnifiedViewingKey], walletBirthday: BlockHeight) throws { + public func initialize() throws { do { try rustBackend.initDataDb(dbData: dataDbURL) @@ -230,23 +178,21 @@ public class Initializer { } catch { throw InitializerError.dataDbInitFailed } - - let birthday = WalletBirthday.birthday(with: walletBirthday) - self.walletBirthday = birthday + do { - try rustBackend.initBlocksTable(dbData: dataDbURL, height: Int32(birthday.height), hash: birthday.hash, time: birthday.time, saplingTree: birthday.tree) + try rustBackend.initBlocksTable(dbData: dataDbURL, height: Int32(walletBirthday.height), hash: walletBirthday.hash, time: walletBirthday.time, saplingTree: walletBirthday.tree) } catch RustWeldingError.dataDbNotEmpty { // this is fine } catch { throw InitializerError.dataDbInitFailed } - let lastDownloaded = (try? downloader.storage.latestHeight()) ?? birthday.height + let lastDownloaded = (try? downloader.storage.latestHeight()) ?? walletBirthday.height // resume from last downloaded block - lowerBoundHeight = max(birthday.height, lastDownloaded) + lowerBoundHeight = max(walletBirthday.height, lastDownloaded) do { - guard try rustBackend.initAccountsTable(dbData: dataDbURL, uvks: unifiedViewingKeys) else { + guard try rustBackend.initAccountsTable(dbData: dataDbURL, uvks: viewingKeys) else { throw rustBackend.lastError() ?? InitializerError.accountInitFailed } } catch RustWeldingError.dataDbNotEmpty { @@ -258,7 +204,7 @@ public class Initializer { let migrationManager = MigrationManager(cacheDbConnection: SimpleConnectionProvider(path: cacheDbURL.path), dataDbConnection: SimpleConnectionProvider(path: dataDbURL.path), pendingDbConnection: SimpleConnectionProvider(path: pendingDbURL.path)) - try migrationManager.performMigration(uvks: unifiedViewingKeys) + try migrationManager.performMigration(uvks: viewingKeys) } /** diff --git a/ZcashLightClientKit/Synchronizer.swift b/ZcashLightClientKit/Synchronizer.swift index fe329f80..9e78c11a 100644 --- a/ZcashLightClientKit/Synchronizer.swift +++ b/ZcashLightClientKit/Synchronizer.swift @@ -13,6 +13,7 @@ Represents errors thrown by a Synchronizer */ public enum SynchronizerError: Error { case initFailed(message: String) + case notPrepared case syncFailed case connectionFailed(message: Error) case generalError(message: String) @@ -68,14 +69,9 @@ public protocol Synchronizer { var progress: Float { get } /** - Initialize the internal state with the given Extended Viewing Keys and a wallet birthday - - Parameter viewingKeys: an array containing the viewing keys to initialize the internal state - - Parameter walletBirthday: the eldest when the different keys differ in birthday - - Throws: Initializer Errors and RustWeldingError if fails - - Note: The subsequent initializations don't have any effect or failure + prepares this initializer to operate. Initializes the internal state with the given Extended Viewing Keys and a wallet birthday found in the initializer object */ - func initialize(unifiedViewingKeys: [UnifiedViewingKey], walletBirthday: BlockHeight) throws - + func prepare() throws /** Starts this synchronizer within the given scope. @@ -222,6 +218,10 @@ public protocol Synchronizer { The Status of the synchronizer */ public enum Status { + /** + This synchronizer is not ready to start + */ + case unprepared /** Indicates that [stop] has been called on this Synchronizer and it will no longer be used. */ diff --git a/ZcashLightClientKit/UIKit/Synchronizer/SDKSynchronizer.swift b/ZcashLightClientKit/UIKit/Synchronizer/SDKSynchronizer.swift index af7d9a08..79334578 100644 --- a/ZcashLightClientKit/UIKit/Synchronizer/SDKSynchronizer.swift +++ b/ZcashLightClientKit/UIKit/Synchronizer/SDKSynchronizer.swift @@ -98,7 +98,7 @@ public class SDKSynchronizer: Synchronizer { */ public convenience init(initializer: Initializer) throws { - try self.init(status: .disconnected, + try self.init(status: .unprepared, initializer: initializer, transactionManager: try OutboundTransactionManagerBuilder.build(initializer: initializer), transactionRepository: initializer.transactionRepository, @@ -128,26 +128,36 @@ public class SDKSynchronizer: Synchronizer { self.blockProcessor.stop() } - public func initialize(unifiedViewingKeys: [UnifiedViewingKey], walletBirthday: BlockHeight) throws { - try self.initializer.initialize(unifiedViewingKeys: unifiedViewingKeys, walletBirthday: walletBirthday) - try self.blockProcessor.setStartHeight(WalletBirthday.birthday(with: walletBirthday).height) + public func initialize() throws { + try self.initializer.initialize() + try self.blockProcessor.setStartHeight(initializer.walletBirthday.height) } + public func prepare() throws { + try self.initializer.initialize() + try self.blockProcessor.setStartHeight(initializer.walletBirthday.height) + self.status = .disconnected + } /** Starts the synchronizer - Throws: CompactBlockProcessorError when failures occur */ public func start(retry: Bool = false) throws { - - guard status == .stopped || status == .disconnected || status == .synced else { - assert(true,"warning: synchronizer started when already started") // TODO: remove this assertion sometime in the near future - return - } - do { - try blockProcessor.start(retry: retry) - } catch { - throw mapError(error) + switch status { + case .unprepared: + throw SynchronizerError.notPrepared + case .syncing: + assert(true,"warning: synchronizer started when already started") // TODO: remove this assertion sometime in the near future + LoggerProxy.debug("warning: synchronizer started when already started") + return + default: + do { + try blockProcessor.start(retry: retry) + } catch { + throw mapError(error) + } + } } @@ -156,7 +166,10 @@ public class SDKSynchronizer: Synchronizer { */ public func stop() { - guard status != .stopped, status != .disconnected else { return } + guard status != .stopped, status != .disconnected else { + LoggerProxy.info("attempted to stop when status was: \(status)") + return + } blockProcessor.stop(cancelTasks: true) } @@ -562,7 +575,8 @@ public class SDKSynchronizer: Synchronizer { NotificationCenter.default.post(name: Notification.Name.synchronizerSynced, object: self) case .syncing: NotificationCenter.default.post(name: Notification.Name.synchronizerSyncing, object: self) - + case .unprepared: + break } } // MARK: book keeping