231 lines
9.8 KiB
Swift
231 lines
9.8 KiB
Swift
//
|
|
// Initializer.swift
|
|
// ZcashLightClientKit
|
|
//
|
|
// Created by Francisco Gindre on 13/09/2019.
|
|
// Copyright © 2019 Electric Coin Company. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
/**
|
|
Wrapper for the Rust backend. This class basically represents all the Rust-wallet
|
|
capabilities and the supporting data required to exercise those abilities.
|
|
*/
|
|
|
|
public enum InitializerError: Error {
|
|
case cacheDbInitFailed
|
|
case dataDbInitFailed
|
|
case accountInitFailed
|
|
case falseStart
|
|
}
|
|
|
|
/**
|
|
Represents a lightwallet instance endpoint to connect to
|
|
*/
|
|
public struct LightWalletEndpoint {
|
|
public var address: String
|
|
public var port: String
|
|
public var secure: Bool
|
|
|
|
public var host: String {
|
|
"\(address):\(port)"
|
|
}
|
|
/**
|
|
initializes a LightWalletEndpoint
|
|
- Parameters:
|
|
- address: a String containing the host address
|
|
- port: string with the port of the host address
|
|
- secure: true if connecting through TLS. Default value is true
|
|
*/
|
|
public init(address: String, port: String, secure: Bool = true) {
|
|
self.address = address
|
|
self.port = port
|
|
self.secure = secure
|
|
}
|
|
}
|
|
|
|
/**
|
|
Wrapper for all the Rust backend functionality that does not involve processing blocks. This
|
|
class initializes the Rust backend and the supporting data required to exercise those abilities.
|
|
The [cash.z.wallet.sdk.block.CompactBlockProcessor] handles all the remaining Rust backend
|
|
functionality, related to processing blocks.
|
|
*/
|
|
public class Initializer {
|
|
|
|
private(set) var rustBackend: ZcashRustBackendWelding.Type = ZcashRustBackend.self
|
|
private var lowerBoundHeight: BlockHeight = ZcashSDK.SAPLING_ACTIVATION_HEIGHT
|
|
private(set) var cacheDbURL: URL
|
|
private(set) var dataDbURL: URL
|
|
private(set) var pendingDbURL: URL
|
|
private(set) var spendParamsURL: URL
|
|
private(set) var outputParamsURL: URL
|
|
private var walletBirthday: WalletBirthday?
|
|
private(set) var lightWalletService: LightWalletGRPCService
|
|
private(set) var transactionRepository: TransactionRepository
|
|
private(set) var downloader: CompactBlockDownloader
|
|
private(set) var processor: CompactBlockProcessor?
|
|
/**
|
|
the LightWalletEndpoint that this initializer is connecting to
|
|
*/
|
|
public private(set) var endpoint: LightWalletEndpoint
|
|
/**
|
|
Constructs the Initializer
|
|
- Parameters:
|
|
- cacheDbURL: location of the compact blocks cache db
|
|
- dataDbURL: Location of the data db
|
|
- pendingDbURL: location of the pending transactions database
|
|
- endpoint: the endpoint representing the lightwalletd instance you want to point to
|
|
- spendParamsURL: location of the spend parameters
|
|
- outputParamsURL: location of the output parameters
|
|
*/
|
|
public init (cacheDbURL: URL, dataDbURL: URL, pendingDbURL: URL, endpoint: LightWalletEndpoint, spendParamsURL: URL, outputParamsURL: URL, loggerProxy: Logger? = nil) {
|
|
|
|
logger = loggerProxy
|
|
self.cacheDbURL = cacheDbURL
|
|
self.dataDbURL = dataDbURL
|
|
self.endpoint = endpoint
|
|
self.pendingDbURL = pendingDbURL
|
|
self.spendParamsURL = spendParamsURL
|
|
self.outputParamsURL = outputParamsURL
|
|
self.lightWalletService = LightWalletGRPCService(endpoint: endpoint)
|
|
self.transactionRepository = TransactionRepositoryBuilder.build(dataDbURL: dataDbURL)
|
|
|
|
let storage = CompactBlockStorage(url: cacheDbURL, readonly: false)
|
|
try? storage.createTable()
|
|
self.downloader = CompactBlockDownloader(service: lightWalletService, storage: storage)
|
|
|
|
}
|
|
|
|
/**
|
|
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(seedProvider: SeedProvider, walletBirthdayHeight: BlockHeight, numberOfAccounts: Int = 1) throws -> [String]? {
|
|
|
|
do {
|
|
try rustBackend.initDataDb(dbData: dataDbURL)
|
|
} catch RustWeldingError.dataDbNotEmpty {
|
|
// this is fine
|
|
} catch {
|
|
throw InitializerError.dataDbInitFailed
|
|
}
|
|
|
|
self.walletBirthday = WalletBirthday.birthday(with: walletBirthdayHeight)
|
|
guard let birthday = self.walletBirthday else {
|
|
throw InitializerError.falseStart
|
|
}
|
|
|
|
do {
|
|
try rustBackend.initBlocksTable(dbData: dataDbURL, height: Int32(birthday.height), hash: birthday.hash, time: birthday.time, saplingTree: birthday.tree)
|
|
} catch RustWeldingError.dataDbNotEmpty {
|
|
// this is fine
|
|
} catch {
|
|
throw InitializerError.dataDbInitFailed
|
|
}
|
|
|
|
let lastDownloaded = (try? downloader.storage.latestHeight()) ?? self.walletBirthday?.height ?? ZcashSDK.SAPLING_ACTIVATION_HEIGHT
|
|
// resume from last downloaded block
|
|
lowerBoundHeight = max(birthday.height, lastDownloaded)
|
|
|
|
self.processor = CompactBlockProcessorBuilder.buildProcessor(configuration: CompactBlockProcessor.Configuration(cacheDb: cacheDbURL, dataDb: dataDbURL, walletBirthday: walletBirthday?.height ?? self.lowerBoundHeight), downloader: self.downloader, backend: rustBackend)
|
|
|
|
guard let accounts = rustBackend.initAccountsTable(dbData: dataDbURL, seed: seedProvider.seed(), accounts: Int32(numberOfAccounts)) else {
|
|
throw rustBackend.lastError() ?? InitializerError.accountInitFailed
|
|
}
|
|
|
|
return accounts
|
|
}
|
|
|
|
/**
|
|
get address from the given account index
|
|
- Parameter account: the index of the account
|
|
*/
|
|
public func getAddress(index account: Int = 0) -> String? {
|
|
rustBackend.getAddress(dbData: dataDbURL, account: Int32(account))
|
|
}
|
|
/**
|
|
get (unverified) balance from the given account index
|
|
- Parameter account: the index of the account
|
|
*/
|
|
public func getBalance(account index: Int = 0) -> Int64 {
|
|
rustBackend.getBalance(dbData: dataDbURL, account: Int32(index))
|
|
}
|
|
|
|
/**
|
|
get verified balance from the given account index
|
|
- Parameter account: the index of the account
|
|
*/
|
|
public func getVerifiedBalance(account index: Int = 0) -> Int64 {
|
|
rustBackend.getVerifiedBalance(dbData: dataDbURL, account: Int32(index))
|
|
}
|
|
|
|
/**
|
|
checks if the provided address is a valid shielded zAddress
|
|
*/
|
|
public func isValidShieldedAddress(_ address: String) -> Bool {
|
|
(try? rustBackend.isValidShieldedAddress(address)) ?? false
|
|
}
|
|
/**
|
|
checks if the provided address is a transparent zAddress
|
|
*/
|
|
public func isValidTransparentAddress(_ address: String) -> Bool {
|
|
(try? rustBackend.isValidTransparentAddress(address)) ?? false
|
|
}
|
|
|
|
/**
|
|
underlying CompactBlockProcessor for this initializer
|
|
|
|
Although it is recommended to always use the higher abstraction first, if you need a more fine grained control over synchronization, you can use a CompactBlockProcessor instead of a Synchronizer.
|
|
|
|
*/
|
|
public func blockProcessor() -> CompactBlockProcessor? {
|
|
self.processor
|
|
}
|
|
}
|
|
|
|
class CompactBlockProcessorBuilder {
|
|
static func buildProcessor(configuration: CompactBlockProcessor.Configuration, downloader: CompactBlockDownloader, backend: ZcashRustBackendWelding.Type) -> CompactBlockProcessor {
|
|
return CompactBlockProcessor(downloader: downloader, backend: backend, config: configuration)
|
|
}
|
|
}
|
|
|
|
/**
|
|
Represents the wallet's birthday which can be thought of as a checkpoint at the earliest moment in history where
|
|
transactions related to this wallet could exist. Ideally, this would correspond to the latest block height at the
|
|
time the wallet key was created. Worst case, the height of Sapling activation could be used (280000).
|
|
|
|
Knowing a wallet's birthday can significantly reduce the amount of data that it needs to download because none of
|
|
the data before that height needs to be scanned for transactions. However, we do need the Sapling tree data in
|
|
order to construct valid transactions from that point forward. This birthday contains that tree data, allowing us
|
|
to avoid downloading all the compact blocks required in order to generate it.
|
|
|
|
New wallets can ignore any blocks created before their birthday.
|
|
|
|
- Parameters:
|
|
- height: the height at the time the wallet was born
|
|
- hash: the block hash corresponding to the given height
|
|
- time: the time the wallet was born, in seconds
|
|
- tree: the sapling tree corresponding to the given height. This takes around 15 minutes of processing to
|
|
generate from scratch because all blocks since activation need to be considered. So when it is calculated in
|
|
advance it can save the user a lot of time.
|
|
*/
|
|
public struct WalletBirthday {
|
|
public private(set) var height: BlockHeight = -1
|
|
public private(set) var hash: String = ""
|
|
public private(set) var time: UInt32 = 0
|
|
public private(set) var tree: String = ""
|
|
}
|