ZcashLightClientKit/Sources/ZcashLightClientKit/Initializer.swift

387 lines
14 KiB
Swift
Raw Normal View History

//
// Initializer.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 13/09/2019.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import Foundation
2021-09-17 06:49:58 -07:00
/**
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(Error)
case dataDbInitFailed(Error)
case accountInitFailed(Error)
2020-10-14 15:50:26 -07:00
case invalidViewingKey(key: String)
}
/**
2021-09-17 06:49:58 -07:00
Represents a lightwallet instance endpoint to connect to
*/
public struct LightWalletEndpoint {
public var host: String
public var port: Int
public var secure: Bool
2021-06-15 14:53:21 -07:00
public var singleCallTimeoutInMillis: Int64
public var streamingCallTimeoutInMillis: Int64
2021-09-17 06:49:58 -07:00
/**
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
- singleCallTimeoutInMillis: timeout for single calls in Milliseconds. Default 30 seconds
- streamingCallTimeoutInMillis: timeout for streaming calls in Milliseconds. Default 100 seconds
2021-09-17 06:49:58 -07:00
*/
public init(
address: String,
port: Int,
secure: Bool = true,
singleCallTimeoutInMillis: Int64 = 30000,
2021-09-17 06:49:58 -07:00
streamingCallTimeoutInMillis: Int64 = 100000
) {
self.host = address
self.port = port
self.secure = secure
2021-06-15 14:53:21 -07:00
self.singleCallTimeoutInMillis = singleCallTimeoutInMillis
self.streamingCallTimeoutInMillis = streamingCallTimeoutInMillis
}
}
/**
2021-09-17 06:49:58 -07:00
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 {
public enum InitializationResult {
case success
case seedRequired
}
private(set) var rustBackend: ZcashRustBackendWelding.Type
2020-10-14 15:50:26 -07:00
private(set) var alias: String
private(set) var endpoint: LightWalletEndpoint
private var lowerBoundHeight: BlockHeight
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(set) var lightWalletService: LightWalletService
private(set) var transactionRepository: TransactionRepository
2021-04-08 10:18:16 -07:00
private(set) var accountRepository: AccountRepository
private(set) var storage: CompactBlockStorage
private(set) var downloader: CompactBlockDownloader
2021-07-28 09:59:10 -07:00
private(set) var network: ZcashNetwork
private(set) public var viewingKeys: [UnifiedFullViewingKey]
/// The effective birthday of the wallet based on the height provided when initializing
/// and the checkpoints available on this SDK
private(set) public var walletBirthday: BlockHeight
2021-09-17 06:49:58 -07:00
/**
2021-09-17 06:49:58 -07:00
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
2021-09-17 06:49:58 -07:00
*/
convenience public init (
cacheDbURL: URL,
dataDbURL: URL,
pendingDbURL: URL,
endpoint: LightWalletEndpoint,
network: ZcashNetwork,
spendParamsURL: URL,
outputParamsURL: URL,
viewingKeys: [UnifiedFullViewingKey],
2021-09-17 06:49:58 -07:00
walletBirthday: BlockHeight,
alias: String = "",
loggerProxy: Logger? = nil
) {
let lwdService = LightWalletGRPCService(endpoint: endpoint)
2021-09-17 06:49:58 -07:00
self.init(
rustBackend: ZcashRustBackend.self,
lowerBoundHeight: walletBirthday,
network: network,
cacheDbURL: cacheDbURL,
dataDbURL: dataDbURL,
pendingDbURL: pendingDbURL,
endpoint: endpoint,
service: lwdService,
repository: TransactionRepositoryBuilder.build(dataDbURL: dataDbURL),
accountRepository: AccountRepositoryBuilder.build(
dataDbURL: dataDbURL,
readOnly: true,
caching: true
),
storage: CompactBlockStorage(url: cacheDbURL, readonly: false),
spendParamsURL: spendParamsURL,
outputParamsURL: outputParamsURL,
viewingKeys: viewingKeys,
walletBirthday: walletBirthday,
alias: alias,
loggerProxy: loggerProxy
)
}
/**
2021-09-17 06:49:58 -07:00
Internal for dependency injection purposes
*/
init(
rustBackend: ZcashRustBackendWelding.Type,
lowerBoundHeight: BlockHeight,
network: ZcashNetwork,
cacheDbURL: URL,
dataDbURL: URL,
pendingDbURL: URL,
endpoint: LightWalletEndpoint,
service: LightWalletService,
repository: TransactionRepository,
accountRepository: AccountRepository,
storage: CompactBlockStorage,
spendParamsURL: URL,
outputParamsURL: URL,
viewingKeys: [UnifiedFullViewingKey],
2021-09-17 06:49:58 -07:00
walletBirthday: BlockHeight,
alias: String = "",
loggerProxy: Logger? = nil
) {
logger = loggerProxy
self.rustBackend = rustBackend
self.lowerBoundHeight = lowerBoundHeight
self.cacheDbURL = cacheDbURL
self.dataDbURL = dataDbURL
self.pendingDbURL = pendingDbURL
2020-10-14 15:50:26 -07:00
self.endpoint = endpoint
self.spendParamsURL = spendParamsURL
self.outputParamsURL = outputParamsURL
2020-10-14 15:50:26 -07:00
self.alias = alias
self.lightWalletService = service
self.transactionRepository = repository
2021-04-08 10:18:16 -07:00
self.accountRepository = accountRepository
self.storage = storage
self.downloader = CompactBlockDownloader(service: service, storage: storage)
2021-05-05 12:08:57 -07:00
self.viewingKeys = viewingKeys
self.walletBirthday = walletBirthday
2021-07-26 16:22:30 -07:00
self.network = network
}
2021-09-17 06:49:58 -07:00
/// Initialize the wallet. The ZIP-32 seed bytes can optionally be passed to perform
/// database migrations. most of the times the seed won't be needed. If they do and are
/// not provided this will fail with `InitializationResult.seedRequired`. It could
/// be the case that this method is invoked by a wallet that does not contain the seed phrase
/// and is view-only, or by a wallet that does have the seed but the process does not have the
/// consent of the OS to fetch the keys from the secure storage, like on background tasks.
///
/// 'cache.db' and 'data.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
///
/// - Parameter seed: ZIP-32 Seed bytes for the wallet that will be initialized
/// - Throws: `InitializerError.dataDbInitFailed` if the creation of the dataDb fails
/// `InitializerError.accountInitFailed` if the account table can't be initialized.
public func initialize(with seed: [UInt8]?) throws -> InitializationResult {
do {
try storage.createTable()
} catch {
throw InitializerError.cacheDbInitFailed(error)
}
do {
if case .seedRequired = try rustBackend.initDataDb(dbData: dataDbURL, seed: seed, networkType: network.networkType) {
return .seedRequired
}
} catch {
throw InitializerError.dataDbInitFailed(error)
}
let checkpoint = Checkpoint.birthday(with: self.walletBirthday, network: network)
do {
2021-09-15 05:21:29 -07:00
try rustBackend.initBlocksTable(
dbData: dataDbURL,
height: Int32(checkpoint.height),
hash: checkpoint.hash,
time: checkpoint.time,
saplingTree: checkpoint.saplingTree,
2021-09-15 05:21:29 -07:00
networkType: network.networkType
)
} catch RustWeldingError.dataDbNotEmpty {
// this is fine
} catch {
throw InitializerError.dataDbInitFailed(error)
}
self.walletBirthday = checkpoint.height
let lastDownloaded = (try? downloader.storage.latestHeight()) ?? walletBirthday
// resume from last downloaded block
lowerBoundHeight = max(walletBirthday, lastDownloaded)
2020-12-08 06:19:41 -08:00
do {
try rustBackend.initAccountsTable(
2021-09-17 06:49:58 -07:00
dbData: dataDbURL,
ufvks: viewingKeys,
2021-09-17 06:49:58 -07:00
networkType: network.networkType
)
2020-12-08 06:19:41 -08:00
} catch RustWeldingError.dataDbNotEmpty {
// this is fine
} catch RustWeldingError.malformedStringInput {
throw RustWeldingError.malformedStringInput
2021-09-15 05:21:29 -07:00
} catch {
throw InitializerError.accountInitFailed(error)
}
let migrationManager = MigrationManager(
cacheDbConnection: SimpleConnectionProvider(path: cacheDbURL.path),
pendingDbConnection: SimpleConnectionProvider(path: pendingDbURL.path),
networkType: self.network.networkType
)
try migrationManager.performMigration()
return .success
}
/// get (unverified) balance from the given account index
/// - Parameter account: the index of the account
@available(*, deprecated, message: "This function will be removed soon. Use the function returning `Zatoshi` instead")
public func getBalance(account index: Int = 0) -> Int64 {
2021-07-28 09:59:10 -07:00
rustBackend.getBalance(dbData: dataDbURL, account: Int32(index), networkType: network.networkType)
}
/// get (unverified) balance from the given account index
/// - Parameter account: the index of the account
/// - Returns: balance in `Zatoshi`
public func getBalance(account index: Int = 0) -> Zatoshi {
Zatoshi(
rustBackend.getBalance(
dbData: dataDbURL,
account: Int32(index),
networkType: network.networkType
)
)
}
/// get verified balance from the given account index
/// - Parameter account: the index of the account
@available(*, deprecated, message: "This function will be removed soon. Use the one returning `Zatoshi` instead")
2020-01-14 14:25:14 -08:00
public func getVerifiedBalance(account index: Int = 0) -> Int64 {
2021-07-28 09:59:10 -07:00
rustBackend.getVerifiedBalance(dbData: dataDbURL, account: Int32(index), networkType: network.networkType)
2020-01-14 14:25:14 -08:00
}
/// get verified balance from the given account index
/// - Parameter account: the index of the account
/// - Returns: balance in `Zatoshi`
public func getVerifiedBalance(account index: Int = 0) -> Zatoshi {
Zatoshi(
rustBackend.getVerifiedBalance(
dbData: dataDbURL,
account: Int32(index),
networkType: network.networkType
)
)
}
2020-01-14 14:25:14 -08:00
/**
[#461] Adopt a Type-Safe Keys and Addresses API This PR creates data types for Addresses and Keys so that they are not represented by Strings anymore. This avoids mistakenly use the wrong keys because they are all alike for the type system. New Protocols: ============= StringEncoded -> Protocol that makes a type can be expressed in an string-encoded fashion either for UI or Interchange purposes. Undescribable -> A protocol that implements methods that override default decriptions used by debuggers, loggers and event trackers to avoid types conforming to it to be leaked to logs. Deleted Protocols: ================== UnifiedFullViewingKey --> turned into a struct. UnifiedAddress --> turned into a struct new Error Type: ================ ```` enum KeyEncodingError: Error { case invalidEncoding } ```` This error is thrown when an Address or Key type (addresses are public keys in the end) can be decoded from their String representation, typically upon initialization from a User input. New Types: ========= SaplingExtendedSpendingKey -> Type for Sapling Extended Full Viewing Keys this type will be replaced with Unified Spending Keys soon. SaplingExtendedFullViewingKey -> Extended Full Viewing Key for Sapling. Maintains existing funcionality. Will be probably deprecated in favor of UFVK. TransparentAccountPrivKey -> Private key for transparent account. Used only for shielding operations. Note: this will probably be deprecated soon. UnifiedFullViewingKey -> Replaces the protocol that had the same name. TransparentAddress -> Replaces a type alias with a struct SaplingAddress --> Represents a Sapling receiver address. Comonly called zAddress. This address corresponds to the Zcash Sapling shielded pool. Although this it is fully functional, we encourage developers to choose `UnifiedAddress` before Sapling or Transparent ones. UnifiedAddress -> Represents a UA. String-encodable and Equatable. Use of UAs must be favored instead of individual receivers for different pools. This type can't be decomposed into their Receiver types yet. Recipient -> This represents all valid receiver types to be used as inputs for outgoing transactions. ```` public enum Recipient: Equatable, StringEncoded { case transparent(TransparentAddress) case sapling(SaplingAddress) case unified(UnifiedAddress) ```` The wrapped concrete receiver is a valid receiver type. Deleted Type Aliases: ===================== The following aliases were deleted and turned into types ```` public typealias TransparentAddress = String public typealias SaplingShieldedAddress = String ```` Changes to Derivation Tool ========================== DerivationTool has been changed to accomodate this new types and remove Strings whenever possible. Changes to Synchronizer and CompactBlockProcessor ================================================= Accordingly these to components have been modified to accept the new types intead of strings when possible. Changes to Demo App =================== The demo App has been patch to compile and work with the new types. Developers must consider that the use (and abuse) of forced_try and forced unwrapping is a "license" that maintainers are using for the sake of brevity. We consider that clients of this SDK do know how to handle Errors and Optional and it is not the objective of the demo code to show good practices on those matters. Closes #461
2022-08-20 15:10:22 -07:00
checks if the provided address is a valid sapling address
2021-09-17 06:49:58 -07:00
*/
[#461] Adopt a Type-Safe Keys and Addresses API This PR creates data types for Addresses and Keys so that they are not represented by Strings anymore. This avoids mistakenly use the wrong keys because they are all alike for the type system. New Protocols: ============= StringEncoded -> Protocol that makes a type can be expressed in an string-encoded fashion either for UI or Interchange purposes. Undescribable -> A protocol that implements methods that override default decriptions used by debuggers, loggers and event trackers to avoid types conforming to it to be leaked to logs. Deleted Protocols: ================== UnifiedFullViewingKey --> turned into a struct. UnifiedAddress --> turned into a struct new Error Type: ================ ```` enum KeyEncodingError: Error { case invalidEncoding } ```` This error is thrown when an Address or Key type (addresses are public keys in the end) can be decoded from their String representation, typically upon initialization from a User input. New Types: ========= SaplingExtendedSpendingKey -> Type for Sapling Extended Full Viewing Keys this type will be replaced with Unified Spending Keys soon. SaplingExtendedFullViewingKey -> Extended Full Viewing Key for Sapling. Maintains existing funcionality. Will be probably deprecated in favor of UFVK. TransparentAccountPrivKey -> Private key for transparent account. Used only for shielding operations. Note: this will probably be deprecated soon. UnifiedFullViewingKey -> Replaces the protocol that had the same name. TransparentAddress -> Replaces a type alias with a struct SaplingAddress --> Represents a Sapling receiver address. Comonly called zAddress. This address corresponds to the Zcash Sapling shielded pool. Although this it is fully functional, we encourage developers to choose `UnifiedAddress` before Sapling or Transparent ones. UnifiedAddress -> Represents a UA. String-encodable and Equatable. Use of UAs must be favored instead of individual receivers for different pools. This type can't be decomposed into their Receiver types yet. Recipient -> This represents all valid receiver types to be used as inputs for outgoing transactions. ```` public enum Recipient: Equatable, StringEncoded { case transparent(TransparentAddress) case sapling(SaplingAddress) case unified(UnifiedAddress) ```` The wrapped concrete receiver is a valid receiver type. Deleted Type Aliases: ===================== The following aliases were deleted and turned into types ```` public typealias TransparentAddress = String public typealias SaplingShieldedAddress = String ```` Changes to Derivation Tool ========================== DerivationTool has been changed to accomodate this new types and remove Strings whenever possible. Changes to Synchronizer and CompactBlockProcessor ================================================= Accordingly these to components have been modified to accept the new types intead of strings when possible. Changes to Demo App =================== The demo App has been patch to compile and work with the new types. Developers must consider that the use (and abuse) of forced_try and forced unwrapping is a "license" that maintainers are using for the sake of brevity. We consider that clients of this SDK do know how to handle Errors and Optional and it is not the objective of the demo code to show good practices on those matters. Closes #461
2022-08-20 15:10:22 -07:00
public func isValidSaplingAddress(_ address: String) -> Bool {
rustBackend.isValidSaplingAddress(address, networkType: network.networkType)
}
2021-09-17 06:49:58 -07:00
/**
2021-09-17 06:49:58 -07:00
checks if the provided address is a transparent zAddress
*/
public func isValidTransparentAddress(_ address: String) -> Bool {
rustBackend.isValidTransparentAddress(address, networkType: network.networkType)
}
2020-10-07 16:34:55 -07:00
func isSpendParameterPresent() -> Bool {
2020-10-08 10:00:27 -07:00
FileManager.default.isReadableFile(atPath: self.spendParamsURL.path)
2020-10-07 16:34:55 -07:00
}
func isOutputParameterPresent() -> Bool {
2020-10-08 10:00:27 -07:00
FileManager.default.isReadableFile(atPath: self.outputParamsURL.path)
}
@discardableResult
func downloadParametersIfNeeded() async throws -> Bool {
2020-10-08 10:00:27 -07:00
let spendParameterPresent = isSpendParameterPresent()
let outputParameterPresent = isOutputParameterPresent()
if spendParameterPresent && outputParameterPresent {
return true
2020-10-08 10:00:27 -07:00
}
let outputURL = self.outputParamsURL
let spendURL = self.spendParamsURL
do {
if !outputParameterPresent && !spendParameterPresent {
async let outputURLRequest = SaplingParameterDownloader.downloadOutputParameter(outputURL)
async let spendURLRequest = SaplingParameterDownloader.downloadSpendParameter(spendURL)
_ = try await [outputURLRequest, spendURLRequest]
return false
} else if !outputParameterPresent {
try await SaplingParameterDownloader.downloadOutputParameter(outputURL)
return false
} else if !spendParameterPresent {
try await SaplingParameterDownloader.downloadSpendParameter(spendURL)
return false
2020-10-08 10:00:27 -07:00
}
} catch {
throw error
2020-10-08 10:00:27 -07:00
}
return true
2020-10-07 16:34:55 -07:00
}
}
2021-09-17 06:49:58 -07:00
enum CompactBlockProcessorBuilder {
// swiftlint:disable:next function_parameter_count
2021-09-15 05:21:29 -07:00
static func buildProcessor(
configuration: CompactBlockProcessor.Configuration,
service: LightWalletService,
storage: CompactBlockStorage,
transactionRepository: TransactionRepository,
accountRepository: AccountRepository,
backend: ZcashRustBackendWelding.Type
) -> CompactBlockProcessor {
return CompactBlockProcessor(
service: service,
storage: storage,
backend: backend,
config: configuration,
repository: transactionRepository,
accountRepository: accountRepository
)
}
}
extension InitializerError: LocalizedError {
public var errorDescription: String? {
switch self {
case .invalidViewingKey:
return "The provided viewing key is invalid"
case .cacheDbInitFailed(let error):
return "cacheDb Init failed with error: \(error.localizedDescription)"
case .dataDbInitFailed(let error):
return "dataDb init failed with error: \(error.localizedDescription)"
case .accountInitFailed(let error):
return "account table init failed with error: \(error.localizedDescription)"
}
}
}