ZcashLightClientKit/Sources/ZcashLightClientKit/Synchronizer.swift

358 lines
14 KiB
Swift
Raw Normal View History

//
// Synchronizer.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 11/5/19.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import Foundation
/// Represents errors thrown by a Synchronizer
public enum SynchronizerError: Error {
case initFailed(message: String) // ZcashLightClientKit.SynchronizerError error 0.
case notPrepared // ZcashLightClientKit.SynchronizerError error 9.
case syncFailed // ZcashLightClientKit.SynchronizerError error 10.
case connectionFailed(message: Error) // ZcashLightClientKit.SynchronizerError error 1.
case generalError(message: String) // ZcashLightClientKit.SynchronizerError error 2.
case maxRetryAttemptsReached(attempts: Int) // ZcashLightClientKit.SynchronizerError error 3.
case connectionError(status: Int, message: String) // ZcashLightClientKit.SynchronizerError error 4.
case networkTimeout // ZcashLightClientKit.SynchronizerError error 11.
case uncategorized(underlyingError: Error) // ZcashLightClientKit.SynchronizerError error 5.
case criticalError // ZcashLightClientKit.SynchronizerError error 12.
case parameterMissing(underlyingError: Error) // ZcashLightClientKit.SynchronizerError error 6.
case rewindError(underlyingError: Error) // ZcashLightClientKit.SynchronizerError error 7.
case rewindErrorUnknownArchorHeight // ZcashLightClientKit.SynchronizerError error 13.
case invalidAccount // ZcashLightClientKit.SynchronizerError error 14.
case lightwalletdValidationFailed(underlyingError: Error) // ZcashLightClientKit.SynchronizerError error 8.
}
2020-12-23 15:01:09 -08:00
public enum ShieldFundsError: Error {
case noUTXOFound
case insuficientTransparentFunds
case shieldingFailed(underlyingError: Error)
}
2021-02-15 11:15:50 -08:00
extension ShieldFundsError: LocalizedError {
public var errorDescription: String? {
switch self {
case .noUTXOFound:
return "Could not find UTXOs for the given t-address"
case .insuficientTransparentFunds:
return "You don't have enough confirmed transparent funds to perform a shielding transaction."
case .shieldingFailed(let underlyingError):
return "Shielding transaction failed. Reason: \(underlyingError)"
}
}
}
/// Represent the connection state to the lightwalletd server
2021-06-14 16:38:05 -07:00
public enum ConnectionState {
/// not in use
2021-06-14 16:38:05 -07:00
case idle
2021-09-17 06:49:58 -07:00
/// there's a connection being attempted from a non error state
2021-06-14 16:38:05 -07:00
case connecting
2021-09-17 06:49:58 -07:00
/// connection is established, ready to use or in use
2021-06-14 16:38:05 -07:00
case online
2021-09-17 06:49:58 -07:00
/// the connection is being re-established after losing it temporarily
2021-06-14 16:38:05 -07:00
case reconnecting
2021-09-17 06:49:58 -07:00
/// the connection has been closed
2021-06-14 16:38:05 -07:00
case shutdown
}
/// Primary interface for interacting with the SDK. Defines the contract that specific
/// implementations like SdkSynchronizer fulfill.
public protocol Synchronizer {
/// Value representing the Status of this Synchronizer. As the status changes, it will be also notified
2021-06-14 16:38:05 -07:00
var status: SyncStatus { get }
/// reflects current connection state to LightwalletEndpoint
2021-06-15 14:53:21 -07:00
var connectionState: ConnectionState { get }
/// 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 prepare(with seed: [UInt8]?) async throws -> Initializer.InitializationResult
///Starts this synchronizer within the given scope.
///
///Implementations should leverage structured concurrency and
///cancel all jobs when this scope completes.
func start(retry: Bool) throws
/// Stop this synchronizer. Implementations should ensure that calling this method cancels all jobs that were created by this instance.
func stop() throws
/// Gets the sapling shielded address for the given account.
/// - Parameter accountIndex: the optional accountId whose address is of interest. By default, the first account is used.
/// - Returns the address or nil if account index is incorrect
2022-10-31 05:57:10 -07:00
func getSaplingAddress(accountIndex: Int) async -> SaplingAddress?
/// Gets the unified address for the given account.
/// - Parameter accountIndex: the optional accountId whose address is of interest. By default, the first account is used.
/// - Returns the address or nil if account index is incorrect
func getUnifiedAddress(accountIndex: Int) async -> UnifiedAddress?
/// Gets the transparent address for the given account.
/// - Parameter accountIndex: the optional accountId whose address is of interest. By default, the first account is used.
/// - Returns the address or nil if account index is incorrect
func getTransparentAddress(accountIndex: Int) async -> TransparentAddress?
/// Sends zatoshi.
/// - Parameter spendingKey: the `UnifiedSpendingKey` that allows spends to occur.
/// - Parameter zatoshi: the amount to send in Zatoshi.
/// - Parameter toAddress: the recipient's address.
/// - Parameter memo: an `Optional<Memo>`with the memo to include as part of the transaction. send `nil` when sending to transparent receivers otherwise the function will throw an error
// swiftlint:disable:next function_parameter_count
func sendToAddress(
spendingKey: UnifiedSpendingKey,
zatoshi: Zatoshi,
[#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
toAddress: Recipient,
memo: Memo?
) async throws -> PendingTransactionEntity
/// Shields transparent funds from the given private key into the best shielded pool of the account associated to the given `UnifiedSpendingKey`.
/// - Parameter spendingKey: the `UnifiedSpendingKey` that allows to spend transparent funds
/// - Parameter memo: the optional memo to include as part of the transaction.
2021-09-17 06:49:58 -07:00
func shieldFunds(
spendingKey: UnifiedSpendingKey,
memo: Memo
) async throws -> PendingTransactionEntity
/// Attempts to cancel a transaction that is about to be sent. Typically, cancellation is only
/// an option if the transaction has not yet been submitted to the server.
/// - Parameter transaction: the transaction to cancel.
/// - Returns: true when the cancellation request was successful. False when it is too late.
func cancelSpend(transaction: PendingTransactionEntity) -> Bool
/// all outbound pending transactions that have been sent but are awaiting confirmations
var pendingTransactions: [PendingTransactionEntity] { get }
/// all the transactions that are on the blockchain
var clearedTransactions: [ConfirmedTransactionEntity] { get }
/// All transactions that are related to sending funds
var sentTransactions: [ConfirmedTransactionEntity] { get }
/// all transactions related to receiving funds
var receivedTransactions: [ConfirmedTransactionEntity] { get }
/// A repository serving transactions in a paginated manner
/// - Parameter kind: Transaction Kind expected from this PaginatedTransactionRepository
func paginatedTransactions(of kind: TransactionKind) -> PaginatedTransactionRepository
/// Returns a list of confirmed transactions that preceed the given transaction with a limit count.
/// - Parameters:
/// - from: the confirmed transaction from which the query should start from or nil to retrieve from the most recent transaction
/// - limit: the maximum amount of items this should return if available
/// - Returns: an array with the given Transactions or nil
func allConfirmedTransactions(from transaction: ConfirmedTransactionEntity?, limit: Int) throws -> [ConfirmedTransactionEntity]?
/// Returns the latest downloaded height from the compact block cache
func latestDownloadedHeight() async throws -> BlockHeight
2020-10-06 16:35:17 -07:00
/// Returns the latest block height from the provided Lightwallet endpoint
2020-10-06 16:35:17 -07:00
func latestHeight(result: @escaping (Result<BlockHeight, Error>) -> Void)
/// Returns the latest block height from the provided Lightwallet endpoint
/// Blocking
func latestHeight() async throws -> BlockHeight
2020-12-11 12:15:29 -08:00
/// Returns the latests UTXOs for the given address from the specified height on
func refreshUTXOs(address: TransparentAddress, from height: BlockHeight) async throws -> RefreshedUTXOs
[#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
/// Returns the last stored transparent balance
func getTransparentBalance(accountIndex: Int) async throws -> WalletBalance
/// Returns the shielded total balance (includes verified and unverified balance)
@available(*, deprecated, message: "This function will be removed soon, use the one returning a `Zatoshi` value instead")
func getShieldedBalance(accountIndex: Int) -> Int64
/// Returns the shielded total balance (includes verified and unverified balance)
func getShieldedBalance(accountIndex: Int) -> Zatoshi
/// Returns the shielded verified balance (anchor is 10 blocks back)
@available(*, deprecated, message: "This function will be removed soon, use the one returning a `Zatoshi` value instead")
func getShieldedVerifiedBalance(accountIndex: Int) -> Int64
/// Returns the shielded verified balance (anchor is 10 blocks back)
func getShieldedVerifiedBalance(accountIndex: Int) -> Zatoshi
/// Stops the synchronizer and rescans the known blocks with the current keys.
/// - Parameter policy: the rewind policy
/// - Throws rewindErrorUnknownArchorHeight when the rewind points to an invalid height
/// - Throws rewindError for other errors
/// - Note rewind does not trigger notifications as a reorg would. You need to restart the synchronizer afterwards
func rewind(_ policy: RewindPolicy) async throws
}
2021-06-14 16:38:05 -07:00
public enum SyncStatus: Equatable {
/// Indicates that this Synchronizer is actively preparing to start,
/// which usually involves setting up database tables, migrations or
/// taking other maintenance steps that need to occur after an upgrade.
2021-05-05 12:08:57 -07:00
case unprepared
2021-09-17 06:49:58 -07:00
/// Indicates that this Synchronizer is actively downloading new blocks from the server.
2021-09-17 06:49:58 -07:00
case downloading(_ status: BlockProgress)
/// Indicates that this Synchronizer is actively validating new blocks that were downloaded
/// from the server. Blocks need to be verified before they are scanned. This confirms that
/// each block is chain-sequential, thereby detecting missing blocks and reorgs.
2021-06-07 16:00:33 -07:00
case validating
2021-09-17 06:49:58 -07:00
/// Indicates that this Synchronizer is actively scanning new valid blocks that were
/// downloaded from the server.
2021-09-17 06:49:58 -07:00
case scanning(_ progress: BlockProgress)
/// Indicates that this Synchronizer is actively enhancing newly scanned blocks
/// with additional transaction details, fetched from the server.
2021-06-14 16:38:05 -07:00
case enhancing(_ progress: EnhancementProgress)
2021-09-17 06:49:58 -07:00
/// fetches the transparent balance and stores it locally
2021-06-07 16:00:33 -07:00
case fetching
2021-09-17 06:49:58 -07:00
/// Indicates that this Synchronizer is fully up to date and ready for all wallet functions.
/// When set, a UI element may want to turn green.
case synced
2021-09-17 06:49:58 -07:00
/// Indicates that [stop] has been called on this Synchronizer and it will no longer be used.
2021-06-14 16:38:05 -07:00
case stopped
2021-09-17 06:49:58 -07:00
/// Indicates that this Synchronizer is disconnected from its lightwalletd server.
/// When set, a UI element may want to turn red.
2021-06-14 16:38:05 -07:00
case disconnected
2021-09-17 06:49:58 -07:00
2021-06-14 16:38:05 -07:00
case error(_ error: Error)
2021-06-07 16:00:33 -07:00
public var isSyncing: Bool {
switch self {
2021-06-14 16:38:05 -07:00
case .downloading, .validating, .scanning, .enhancing, .fetching:
2021-06-07 16:00:33 -07:00
return true
2021-06-14 16:38:05 -07:00
default:
return false
2021-06-07 16:00:33 -07:00
}
}
2021-06-15 14:53:21 -07:00
public var isSynced: Bool {
switch self {
2021-09-17 06:49:58 -07:00
case .synced: return true
default: return false
2021-06-15 14:53:21 -07:00
}
}
}
/// Kind of transactions handled by a Synchronizer
public enum TransactionKind {
case sent
case received
case all
}
/// Type of rewind available
/// -birthday: rewinds the local state to this wallet's birthday
/// -height: rewinds to the nearest blockheight to the one given as argument.
/// -transaction: rewinds to the nearest height based on the anchor of the provided transaction.
public enum RewindPolicy {
case birthday
case height(blockheight: BlockHeight)
case transaction(_ transaction: TransactionEntity)
case quick
}
2021-06-14 16:38:05 -07:00
2021-09-17 06:49:58 -07:00
extension SyncStatus {
// swiftlint:disable cyclomatic_complexity
2021-06-14 16:38:05 -07:00
public static func == (lhs: SyncStatus, rhs: SyncStatus) -> Bool {
switch lhs {
case .unprepared:
if case .unprepared = rhs {
return true
} else {
return false
}
case .disconnected:
if case .disconnected = rhs {
return true
} else {
return false
}
case .downloading:
if case .downloading = rhs {
return true
} else {
return false
}
case .validating:
if case .validating = rhs {
return true
} else {
return false
}
case .scanning:
if case .scanning = rhs {
return true
} else {
return false
}
case .enhancing:
if case .enhancing = rhs {
return true
} else {
return false
}
case .fetching:
if case .fetching = rhs {
return true
} else {
return false
}
case .synced:
if case .synced = rhs {
return true
} else {
return false
}
case .stopped:
if case .stopped = rhs {
return true
} else {
return false
}
case .error:
if case .error = rhs {
return true
} else {
return false
}
}
}
}
extension SyncStatus {
init(_ blockProcessorProgress: CompactBlockProgress) {
switch blockProcessorProgress {
2021-09-17 06:49:58 -07:00
case .download(let progressReport):
self = SyncStatus.downloading(progressReport)
case .validate:
self = .validating
case .scan(let progressReport):
self = .scanning(progressReport)
case .enhance(let enhancingReport):
self = .enhancing(enhancingReport)
case .fetch:
self = .fetching
}
}
}