2019-11-14 06:38:54 -08:00
//
// S D K S y n c h r o n i z e r . s w i f t
// Z c a s h L i g h t C l i e n t K i t
//
// C r e a t e d b y F r a n c i s c o G i n d r e o n 1 1 / 6 / 1 9 .
// C o p y r i g h t © 2 0 1 9 E l e c t r i c C o i n C o m p a n y . A l l r i g h t s r e s e r v e d .
//
import Foundation
import UIKit
2019-12-16 14:25:45 -08:00
public extension Notification . Name {
2020-02-26 08:54:48 -08:00
/* *
Notification is posted whenever transactions are updated
- Important : not yet posted
*/
static let transactionsUpdated = Notification . Name ( " SDKSyncronizerTransactionUpdated " )
/* *
Posted when the synchronizer is started .
*/
2019-12-16 14:25:45 -08:00
static let synchronizerStarted = Notification . Name ( " SDKSyncronizerStarted " )
2020-02-26 08:54:48 -08:00
/* *
Posted when there are progress updates .
- Note : Query userInfo object for NotificationKeys . progress for Float progress percentage and NotificationKeys . blockHeight for the current progress height
*/
2019-12-16 14:25:45 -08:00
static let synchronizerProgressUpdated = Notification . Name ( " SDKSyncronizerProgressUpdated " )
2020-02-26 08:54:48 -08:00
2021-06-07 16:00:33 -07:00
static let synchronizerStatusWillUpdate = Notification . Name ( " SDKSynchronizerStatusWillUpdate " )
2020-02-26 08:54:48 -08:00
/* *
Posted when the synchronizer is synced to latest height
*/
2019-12-16 14:25:45 -08:00
static let synchronizerSynced = Notification . Name ( " SDKSyncronizerSynced " )
2020-02-26 08:54:48 -08:00
/* *
Posted when the synchronizer is stopped
*/
2019-12-16 14:25:45 -08:00
static let synchronizerStopped = Notification . Name ( " SDKSyncronizerStopped " )
2020-02-26 08:54:48 -08:00
/* *
Posted when the synchronizer loses connection
*/
2019-12-16 14:25:45 -08:00
static let synchronizerDisconnected = Notification . Name ( " SDKSyncronizerDisconnected " )
2020-02-26 08:54:48 -08:00
/* *
Posted when the synchronizer starts syncing
*/
2019-12-16 14:25:45 -08:00
static let synchronizerSyncing = Notification . Name ( " SDKSyncronizerSyncing " )
2021-06-07 16:00:33 -07:00
/* *
Posted when synchronizer starts downloading blocks
*/
static let synchronizerDownloading = Notification . Name ( " SDKSyncronizerDownloading " )
/* *
Posted when synchronizer starts validating blocks
*/
static let synchronizerValidating = Notification . Name ( " SDKSyncronizerValidating " )
/* *
Posted when synchronizer starts scanning blocks
*/
static let synchronizerScanning = Notification . Name ( " SDKSyncronizerScanning " )
/* *
Posted when the synchronizer starts Enhancing
*/
static let synchronizerEnhancing = Notification . Name ( " SDKSyncronizerEnhancing " )
/* *
Posted when the synchronizer starts fetching UTXOs
*/
static let synchronizerFetching = Notification . Name ( " SDKSyncronizerFetching " )
2020-02-26 08:54:48 -08:00
/* *
2020-10-20 07:06:31 -07:00
Posted when the synchronizer finds a pendingTransaction that hast been newly mined
2020-02-26 08:54:48 -08:00
- Note : query userInfo on NotificationKeys . minedTransaction for the transaction
*/
2019-12-17 12:11:21 -08:00
static let synchronizerMinedTransaction = Notification . Name ( " synchronizerMinedTransaction " )
2020-10-20 07:06:31 -07:00
/* *
Posted when the synchronizer finds a mined transaction
- Note : query userInfo on NotificationKeys . foundTransactions for the [ ConfirmedTransactionEntity ] . This notification could arrive in a background thread .
*/
static let synchronizerFoundTransactions = Notification . Name ( " synchronizerFoundTransactions " )
2020-02-26 08:54:48 -08:00
/* *
Posted when the synchronizer presents an error
- Note : query userInfo on NotificationKeys . error for an error
*/
2019-12-19 05:04:50 -08:00
static let synchronizerFailed = Notification . Name ( " SDKSynchronizerFailed " )
2021-06-14 16:38:05 -07:00
static let synchronizerConnectionStateChanged = Notification . Name ( " SynchronizerConnectionStateChanged " )
2019-12-16 14:25:45 -08:00
}
2019-11-14 06:38:54 -08:00
/* *
2020-03-26 07:27:55 -07:00
Synchronizer implementation for UIKit and iOS 12 +
2019-11-14 06:38:54 -08:00
*/
public class SDKSynchronizer : Synchronizer {
2021-03-08 10:47:36 -08:00
2019-12-16 14:25:45 -08:00
public struct NotificationKeys {
2019-12-17 12:11:21 -08:00
public static let progress = " SDKSynchronizer.progress "
public static let blockHeight = " SDKSynchronizer.blockHeight "
2021-05-11 15:27:22 -07:00
public static let blockDate = " SDKSynchronizer.blockDate "
2019-12-17 12:11:21 -08:00
public static let minedTransaction = " SDKSynchronizer.minedTransaction "
2020-10-20 07:06:31 -07:00
public static let foundTransactions = " SDKSynchronizer.foundTransactions "
2019-12-19 05:04:50 -08:00
public static let error = " SDKSynchronizer.error "
2021-06-07 16:00:33 -07:00
public static let currentStatus = " SDKSynchronizer.currentStatus "
public static let nextStatus = " SDKSynchronizer.nextStatus "
2021-06-14 16:38:05 -07:00
public static let currentConnectionState = " SDKSynchronizer.currentConnectionState "
public static let previousConnectionState = " SDKSynchronizer.previousConnectionState "
2019-12-16 14:25:45 -08:00
}
2021-06-14 16:38:05 -07:00
public private ( set ) var status : SyncStatus {
2019-12-16 14:25:45 -08:00
didSet {
notify ( status : status )
}
2021-06-07 16:00:33 -07:00
willSet {
notifyStatusChange ( newValue : newValue , oldValue : status )
}
2019-12-16 14:25:45 -08:00
}
2019-11-14 06:38:54 -08:00
public private ( set ) var progress : Float = 0.0
2021-03-08 10:47:36 -08:00
public private ( set ) var blockProcessor : CompactBlockProcessor
2019-11-14 06:38:54 -08:00
public private ( set ) var initializer : Initializer
2021-06-15 14:53:21 -07:00
public private ( set ) var connectionState : ConnectionState
2019-12-06 04:38:47 -08:00
private var transactionManager : OutboundTransactionManager
private var transactionRepository : TransactionRepository
2020-12-11 12:15:29 -08:00
private var utxoRepository : UnspentTransactionOutputRepository
2019-11-14 06:38:54 -08:00
2020-02-26 08:54:48 -08:00
/* *
Creates an SDKSynchronizer instance
- Parameter initializer : a wallet Initializer object
*/
2020-06-03 16:18:57 -07:00
public convenience init ( initializer : Initializer ) throws {
2021-05-05 12:08:57 -07:00
try self . init ( status : . unprepared ,
2020-06-03 16:18:57 -07:00
initializer : initializer ,
transactionManager : try OutboundTransactionManagerBuilder . build ( initializer : initializer ) ,
2020-12-11 12:15:29 -08:00
transactionRepository : initializer . transactionRepository ,
2021-03-08 10:47:36 -08:00
utxoRepository : try UTXORepositoryBuilder . build ( initializer : initializer ) ,
blockProcessor : CompactBlockProcessor ( initializer : initializer ) )
2020-06-03 16:18:57 -07:00
2019-11-14 06:38:54 -08:00
}
2021-06-14 16:38:05 -07:00
init ( status : SyncStatus ,
2020-06-03 16:18:57 -07:00
initializer : Initializer ,
transactionManager : OutboundTransactionManager ,
2020-12-11 12:15:29 -08:00
transactionRepository : TransactionRepository ,
2021-03-08 10:47:36 -08:00
utxoRepository : UnspentTransactionOutputRepository ,
blockProcessor : CompactBlockProcessor ) throws {
2021-06-15 14:53:21 -07:00
self . connectionState = . idle
2020-06-03 16:18:57 -07:00
self . status = status
self . initializer = initializer
self . transactionManager = transactionManager
self . transactionRepository = transactionRepository
2020-12-11 12:15:29 -08:00
self . utxoRepository = utxoRepository
2021-03-08 10:47:36 -08:00
self . blockProcessor = blockProcessor
self . subscribeToProcessorNotifications ( self . blockProcessor )
2020-06-03 16:18:57 -07:00
}
2019-11-14 06:38:54 -08:00
deinit {
NotificationCenter . default . removeObserver ( self )
2021-03-08 10:47:36 -08:00
self . blockProcessor . stop ( )
2019-11-14 06:38:54 -08:00
}
2020-11-24 11:33:58 -08:00
2021-05-05 12:08:57 -07:00
public func initialize ( ) throws {
try self . initializer . initialize ( )
try self . blockProcessor . setStartHeight ( initializer . walletBirthday . height )
2020-11-24 11:33:58 -08:00
}
2021-03-08 10:47:36 -08:00
2021-05-05 12:08:57 -07:00
public func prepare ( ) throws {
try self . initializer . initialize ( )
try self . blockProcessor . setStartHeight ( initializer . walletBirthday . height )
self . status = . disconnected
}
2020-02-26 08:54:48 -08:00
/* *
Starts the synchronizer
- Throws : CompactBlockProcessorError when failures occur
*/
2020-03-13 17:00:01 -07:00
public func start ( retry : Bool = false ) throws {
2021-05-05 12:08:57 -07:00
switch status {
case . unprepared :
throw SynchronizerError . notPrepared
2021-06-07 16:00:33 -07:00
case . downloading ,
. validating ,
. scanning ,
. enhancing ,
. fetching :
2021-07-15 10:43:42 -07:00
// a s s e r t ( f a l s e , " w a r n i n g : s y n c h r o n i z e r s t a r t e d w h e n a l r e a d y s t a r t e d " ) / / TODO: r e m o v e t h i s a s s e r t i o n s o m e t i m e i n t h e n e a r f u t u r e
LoggerProxy . warn ( " warning: synchronizer started when already started " )
2019-11-14 06:38:54 -08:00
return
2021-06-14 16:38:05 -07:00
case . stopped , . synced , . disconnected , . error :
2021-05-05 12:08:57 -07:00
do {
try blockProcessor . start ( retry : retry )
} catch {
throw mapError ( error )
}
2020-08-10 15:19:59 -07:00
}
2019-11-14 06:38:54 -08:00
}
2020-02-26 08:54:48 -08:00
/* *
Stops the synchronizer
*/
2020-08-10 15:19:59 -07:00
public func stop ( ) {
2020-12-05 13:28:10 -08:00
2021-05-05 12:08:57 -07:00
guard status != . stopped , status != . disconnected else {
LoggerProxy . info ( " attempted to stop when status was: \( status ) " )
return
}
2019-11-14 06:38:54 -08:00
2021-03-08 10:47:36 -08:00
blockProcessor . stop ( cancelTasks : true )
2021-07-15 10:43:42 -07:00
self . status = . stopped
2019-11-14 06:38:54 -08:00
}
2020-02-26 08:54:48 -08:00
2019-11-14 06:38:54 -08:00
private func subscribeToProcessorNotifications ( _ processor : CompactBlockProcessor ) {
let center = NotificationCenter . default
center . addObserver ( self ,
selector : #selector ( processorUpdated ( _ : ) ) ,
name : Notification . Name . blockProcessorUpdated ,
object : processor )
center . addObserver ( self ,
selector : #selector ( processorStartedDownloading ( _ : ) ) ,
name : Notification . Name . blockProcessorStartedDownloading ,
object : processor )
center . addObserver ( self ,
selector : #selector ( processorStartedValidating ( _ : ) ) ,
name : Notification . Name . blockProcessorStartedValidating ,
object : processor )
center . addObserver ( self ,
selector : #selector ( processorStartedScanning ( _ : ) ) ,
name : Notification . Name . blockProcessorStartedScanning ,
object : processor )
2021-07-14 16:43:02 -07:00
2021-06-07 16:00:33 -07:00
center . addObserver ( self ,
2021-07-14 16:43:02 -07:00
selector : #selector ( processorStartedEnhancing ( _ : ) ) ,
name : Notification . Name . blockProcessorStartedEnhancing ,
object : processor )
center . addObserver ( self ,
selector : #selector ( processorStartedFetching ( _ : ) ) ,
name : Notification . Name . blockProcessorStartedFetching ,
2021-06-07 16:00:33 -07:00
object : processor )
2021-07-14 16:43:02 -07:00
2019-11-14 06:38:54 -08:00
center . addObserver ( self ,
selector : #selector ( processorStopped ( _ : ) ) ,
name : Notification . Name . blockProcessorStopped ,
object : processor )
center . addObserver ( self , selector : #selector ( processorFailed ( _ : ) ) ,
name : Notification . Name . blockProcessorFailed ,
object : processor )
center . addObserver ( self ,
selector : #selector ( processorIdle ( _ : ) ) ,
name : Notification . Name . blockProcessorIdle ,
object : processor )
2019-12-16 14:25:45 -08:00
center . addObserver ( self ,
selector : #selector ( processorFinished ( _ : ) ) ,
name : Notification . Name . blockProcessorFinished ,
object : processor )
2019-11-14 06:38:54 -08:00
center . addObserver ( self ,
selector : #selector ( processorTransitionUnknown ( _ : ) ) ,
name : Notification . Name . blockProcessorUnknownTransition ,
object : processor )
2019-12-17 09:12:07 -08:00
center . addObserver ( self ,
selector : #selector ( reorgDetected ( _ : ) ) ,
name : Notification . Name . blockProcessorHandledReOrg ,
object : processor )
2020-10-20 07:06:31 -07:00
center . addObserver ( self ,
selector : #selector ( transactionsFound ( _ : ) ) ,
name : Notification . Name . blockProcessorFoundTransactions ,
object : processor )
2021-06-14 16:38:05 -07:00
center . addObserver ( self ,
selector : #selector ( connectivityStateChanged ( _ : ) ) ,
name : Notification . Name . blockProcessorConnectivityStateChanged ,
2021-06-15 14:53:21 -07:00
object : nil )
2019-11-14 06:38:54 -08:00
}
2019-12-16 14:25:45 -08:00
// MARK: B l o c k P r o c e s s o r n o t i f i c a t i o n s
2021-06-14 16:38:05 -07:00
@objc func connectivityStateChanged ( _ notification : Notification ) {
guard let userInfo = notification . userInfo ,
let previous = userInfo [ CompactBlockProcessorNotificationKey . previousConnectivityStatus ] as ? ConnectivityState ,
let current = userInfo [ CompactBlockProcessorNotificationKey . currentConnectivityStatus ] as ? ConnectivityState else {
LoggerProxy . error ( " found \( Notification . Name . blockProcessorConnectivityStateChanged ) but lacks dictionary information. this is probably a programming error " )
return
}
2021-06-15 14:53:21 -07:00
let currentState = ConnectionState ( current )
2021-06-14 16:38:05 -07:00
NotificationCenter . default . post (
name : . synchronizerConnectionStateChanged ,
object : self ,
userInfo : [
NotificationKeys . previousConnectionState : ConnectionState ( previous ) ,
2021-06-15 14:53:21 -07:00
NotificationKeys . currentConnectionState : currentState
2021-06-14 16:38:05 -07:00
] )
2021-06-15 14:53:21 -07:00
DispatchQueue . main . async { [ weak self ] in
self ? . connectionState = currentState
}
2021-06-14 16:38:05 -07:00
}
2019-12-16 14:25:45 -08:00
2020-10-20 07:06:31 -07:00
@objc func transactionsFound ( _ notification : Notification ) {
guard let userInfo = notification . userInfo ,
let foundTransactions = userInfo [ CompactBlockProcessorNotificationKey . foundTransactions ] as ? [ ConfirmedTransactionEntity ] else {
return
}
NotificationCenter . default . post ( name : . synchronizerFoundTransactions , object : self , userInfo : [ NotificationKeys . foundTransactions : foundTransactions ] )
}
2019-12-17 09:12:07 -08:00
@objc func reorgDetected ( _ notification : Notification ) {
guard let userInfo = notification . userInfo ,
let progress = userInfo [ CompactBlockProcessorNotificationKey . reorgHeight ] as ? BlockHeight ,
let rewindHeight = userInfo [ CompactBlockProcessorNotificationKey . rewindHeight ] as ? BlockHeight else {
2020-03-09 13:25:27 -07:00
LoggerProxy . debug ( " error processing reorg notification " )
2019-12-17 09:12:07 -08:00
return }
2020-03-09 13:25:27 -07:00
LoggerProxy . debug ( " handling reorg at: \( progress ) with rewind height: \( rewindHeight ) " )
2019-12-17 09:12:07 -08:00
do {
2020-01-29 21:34:03 -08:00
try transactionManager . handleReorg ( at : rewindHeight )
2019-12-17 09:12:07 -08:00
} catch {
2020-03-09 13:25:27 -07:00
LoggerProxy . debug ( " error handling reorg: \( error ) " )
2019-12-19 05:04:50 -08:00
notifyFailure ( error )
2019-12-17 09:12:07 -08:00
}
}
2019-11-14 06:38:54 -08:00
@objc func processorUpdated ( _ notification : Notification ) {
2019-12-16 14:25:45 -08:00
guard let userInfo = notification . userInfo ,
2021-06-07 16:00:33 -07:00
let progress = userInfo [ CompactBlockProcessorNotificationKey . progress ] as ? CompactBlockProgress else {
2020-01-29 21:34:03 -08:00
return
2019-11-14 06:38:54 -08:00
}
2021-06-07 16:00:33 -07:00
self . notify ( progress : progress )
2019-11-14 06:38:54 -08:00
}
@objc func processorStartedDownloading ( _ notification : Notification ) {
2020-02-26 08:54:48 -08:00
DispatchQueue . main . async { [ weak self ] in
2021-06-14 16:38:05 -07:00
guard let self = self , self . status != . downloading ( NullProgress ( ) ) else { return }
self . status = . downloading ( NullProgress ( ) )
2020-02-26 08:54:48 -08:00
}
2019-11-14 06:38:54 -08:00
}
@objc func processorStartedValidating ( _ notification : Notification ) {
2020-02-26 08:54:48 -08:00
DispatchQueue . main . async { [ weak self ] in
2021-06-14 16:38:05 -07:00
guard let self = self , self . status != . validating else { return }
2021-06-07 16:00:33 -07:00
self . status = . validating
2020-02-26 08:54:48 -08:00
}
2019-11-14 06:38:54 -08:00
}
@objc func processorStartedScanning ( _ notification : Notification ) {
2020-02-26 08:54:48 -08:00
DispatchQueue . main . async { [ weak self ] in
2021-06-14 16:38:05 -07:00
guard let self = self , self . status != . scanning ( NullProgress ( ) ) else { return }
self . status = . scanning ( NullProgress ( ) )
2021-06-07 16:00:33 -07:00
}
}
@objc func processorStartedEnhancing ( _ notification : Notification ) {
DispatchQueue . main . async { [ weak self ] in
2021-06-14 16:38:05 -07:00
guard let self = self , self . status != . enhancing ( NullEnhancementProgress ( ) ) else { return }
self . status = . enhancing ( NullEnhancementProgress ( ) )
2021-06-07 16:00:33 -07:00
}
}
@objc func processorStartedFetching ( _ notification : Notification ) {
DispatchQueue . main . async { [ weak self ] in
2021-06-14 16:38:05 -07:00
guard let self = self , self . status != . fetching else { return }
2021-06-07 16:00:33 -07:00
self . status = . fetching
2020-02-26 08:54:48 -08:00
}
2019-11-14 06:38:54 -08:00
}
@objc func processorStopped ( _ notification : Notification ) {
2020-02-26 08:54:48 -08:00
DispatchQueue . main . async { [ weak self ] in
2021-06-14 16:38:05 -07:00
guard let self = self , self . status != . stopped else { return }
2020-02-26 08:54:48 -08:00
self . status = . stopped
}
2019-11-14 06:38:54 -08:00
}
@objc func processorFailed ( _ notification : Notification ) {
2020-03-11 19:17:32 -07:00
2020-02-26 08:54:48 -08:00
DispatchQueue . main . async { [ weak self ] in
guard let self = self else { return }
2020-03-11 19:17:32 -07:00
if let error = notification . userInfo ? [ CompactBlockProcessorNotificationKey . error ] as ? Error {
self . notifyFailure ( error )
2021-06-14 16:38:05 -07:00
self . status = . error ( self . mapError ( error ) )
2020-08-10 15:19:59 -07:00
} else {
self . notifyFailure ( CompactBlockProcessorError . generalError ( message : " This is strange. processorFailed Call received no error message " ) )
2021-06-14 16:38:05 -07:00
self . status = . error ( SynchronizerError . generalError ( message : " This is strange. processorFailed Call received no error message " ) )
2020-03-11 19:17:32 -07:00
}
2020-02-26 08:54:48 -08:00
}
2019-11-14 06:38:54 -08:00
}
@objc func processorIdle ( _ notification : Notification ) {
2020-02-26 08:54:48 -08:00
DispatchQueue . main . async { [ weak self ] in
guard let self = self else { return }
self . status = . disconnected
}
2019-12-16 14:25:45 -08:00
}
2020-01-29 21:34:03 -08:00
2019-12-16 14:25:45 -08:00
@objc func processorFinished ( _ notification : Notification ) {
2020-07-22 12:32:07 -07:00
// F I X : P e n d i n g t r a n s a c t i o n u p d a t e s f a i l i f d o n e f r o m a n o t h e r t h r e a d . I m p r o v e m e n t n e e d e d : e x p l i c i t l y d e f i n e q u e u e s f o r s q l r e p o s i t o r i e s
DispatchQueue . main . async { [ weak self ] in
guard let self = self else { return }
self . refreshPendingTransactions ( )
2019-12-17 13:16:26 -08:00
self . status = . synced
}
2019-11-14 06:38:54 -08:00
}
@objc func processorTransitionUnknown ( _ notification : Notification ) {
self . status = . disconnected
}
2019-12-06 04:38:47 -08:00
// MARK: S y n c h r o n i z e r m e t h o d s
public func sendToAddress ( spendingKey : String , zatoshi : Int64 , toAddress : String , memo : String ? , from accountIndex : Int , resultBlock : @ escaping ( Result < PendingTransactionEntity , Error > ) -> Void ) {
2020-10-08 10:00:27 -07:00
initializer . downloadParametersIfNeeded { ( downloadResult ) in
DispatchQueue . main . async { [ weak self ] in
switch downloadResult {
case . success :
self ? . createToAddress ( spendingKey : spendingKey , zatoshi : zatoshi , toAddress : toAddress , memo : memo , from : accountIndex , resultBlock : resultBlock )
case . failure ( let error ) :
resultBlock ( . failure ( SynchronizerError . parameterMissing ( underlyingError : error ) ) )
}
}
}
}
2020-12-23 15:01:09 -08:00
public func shieldFunds ( spendingKey : String , transparentSecretKey : String , memo : String ? , from accountIndex : Int , resultBlock : @ escaping ( Result < PendingTransactionEntity , Error > ) -> Void ) {
// l e t ' s s e e i f t h e r e a r e f u n d s t o s h i e l d
let derivationTool = DerivationTool . default
do {
let tAddr = try derivationTool . deriveTransparentAddressFromPrivateKey ( transparentSecretKey )
2021-01-22 13:51:48 -08:00
let tBalance = try utxoRepository . balance ( address : tAddr , latestHeight : self . latestDownloadedHeight ( ) )
2020-12-23 15:01:09 -08:00
2021-03-08 10:47:36 -08:00
guard tBalance . verified >= ZcashSDK . shieldingThreshold else {
2020-12-23 15:01:09 -08:00
resultBlock ( . failure ( ShieldFundsError . insuficientTransparentFunds ) )
return
}
let vk = try derivationTool . deriveViewingKey ( spendingKey : spendingKey )
let zAddr = try derivationTool . deriveShieldedAddress ( viewingKey : vk )
2021-03-08 10:47:36 -08:00
let shieldingSpend = try transactionManager . initSpend ( zatoshi : Int ( tBalance . verified ) , toAddress : zAddr , memo : memo , from : 0 )
2020-12-23 15:01:09 -08:00
transactionManager . encodeShieldingTransaction ( spendingKey : spendingKey , tsk : transparentSecretKey , pendingTransaction : shieldingSpend ) { [ weak self ] ( result ) in
guard let self = self else { return }
switch result {
case . success ( let tx ) :
self . transactionManager . submit ( pendingTransaction : tx ) { ( submitResult ) in
switch submitResult {
case . success ( let submittedTx ) :
resultBlock ( . success ( submittedTx ) )
case . failure ( let submissionError ) :
DispatchQueue . main . async {
resultBlock ( . failure ( submissionError ) )
}
}
}
case . failure ( let error ) :
resultBlock ( . failure ( error ) )
}
}
} catch {
resultBlock ( . failure ( error ) )
return
}
}
2020-10-08 10:00:27 -07:00
func createToAddress ( spendingKey : String , zatoshi : Int64 , toAddress : String , memo : String ? , from accountIndex : Int , resultBlock : @ escaping ( Result < PendingTransactionEntity , Error > ) -> Void ) {
2019-12-06 04:38:47 -08:00
do {
let spend = try transactionManager . initSpend ( zatoshi : Int ( zatoshi ) , toAddress : toAddress , memo : memo , from : accountIndex )
transactionManager . encode ( spendingKey : spendingKey , pendingTransaction : spend ) { [ weak self ] ( result ) in
guard let self = self else { return }
switch result {
case . success ( let tx ) :
self . transactionManager . submit ( pendingTransaction : tx ) { ( submitResult ) in
switch submitResult {
case . success ( let submittedTx ) :
resultBlock ( . success ( submittedTx ) )
2019-12-16 14:25:45 -08:00
case . failure ( let submissionError ) :
2019-12-06 04:38:47 -08:00
DispatchQueue . main . async {
2019-12-16 14:25:45 -08:00
resultBlock ( . failure ( submissionError ) )
2019-12-06 04:38:47 -08:00
}
}
}
case . failure ( let error ) :
resultBlock ( . failure ( error ) )
}
}
} catch {
resultBlock ( . failure ( error ) )
}
}
public func cancelSpend ( transaction : PendingTransactionEntity ) -> Bool {
transactionManager . cancel ( pendingTransaction : transaction )
}
2020-01-29 21:34:03 -08:00
public func allReceivedTransactions ( ) throws -> [ ConfirmedTransactionEntity ] {
try transactionRepository . findAllReceivedTransactions ( offset : 0 , limit : Int . max ) ? ? [ ConfirmedTransactionEntity ] ( )
2019-12-06 04:38:47 -08:00
}
2020-01-29 21:34:03 -08:00
public func allPendingTransactions ( ) throws -> [ PendingTransactionEntity ] {
try transactionManager . allPendingTransactions ( ) ? ? [ PendingTransactionEntity ] ( )
2019-12-06 04:38:47 -08:00
}
2020-01-29 21:34:03 -08:00
public func allClearedTransactions ( ) throws -> [ ConfirmedTransactionEntity ] {
try transactionRepository . findAll ( offset : 0 , limit : Int . max ) ? ? [ ConfirmedTransactionEntity ] ( )
2019-12-06 04:38:47 -08:00
}
2020-01-29 21:34:03 -08:00
public func allSentTransactions ( ) throws -> [ ConfirmedTransactionEntity ] {
2020-02-26 08:54:48 -08:00
try transactionRepository . findAllSentTransactions ( offset : 0 , limit : Int . max ) ? ? [ ConfirmedTransactionEntity ] ( )
2019-12-16 14:25:45 -08:00
}
2020-10-19 17:01:46 -07:00
public func allConfirmedTransactions ( from transaction : ConfirmedTransactionEntity ? , limit : Int ) throws -> [ ConfirmedTransactionEntity ] ? {
try transactionRepository . findAll ( from : transaction , limit : limit )
}
2019-12-16 14:25:45 -08:00
public func paginatedTransactions ( of kind : TransactionKind = . all ) -> PaginatedTransactionRepository {
PagedTransactionRepositoryBuilder . build ( initializer : initializer , kind : . all )
}
2020-10-08 10:00:27 -07:00
public func latestDownloadedHeight ( ) throws -> BlockHeight {
2020-10-06 16:35:17 -07:00
try initializer . downloader . lastDownloadedBlockHeight ( )
}
public func latestHeight ( result : @ escaping ( Result < BlockHeight , Error > ) -> Void ) {
initializer . downloader . latestBlockHeight ( result : result )
}
public func latestHeight ( ) throws -> BlockHeight {
try initializer . downloader . latestBlockHeight ( )
}
2020-12-11 12:15:29 -08:00
public func latestUTXOs ( address : String , result : @ escaping ( Result < [ UnspentTransactionOutputEntity ] , Error > ) -> Void ) {
guard initializer . isValidTransparentAddress ( address ) else {
result ( . failure ( SynchronizerError . generalError ( message : " invalid t-address " ) ) )
return
}
2021-03-08 10:47:36 -08:00
initializer . lightWalletService . fetchUTXOs ( for : address , height : ZcashSDK . SAPLING_ACTIVATION_HEIGHT , result : { [ weak self ] r in
2020-12-11 12:15:29 -08:00
guard let self = self else { return }
switch r {
case . success ( let utxos ) :
do {
try self . utxoRepository . clearAll ( address : address )
try self . utxoRepository . store ( utxos : utxos )
result ( . success ( utxos ) )
} catch {
result ( . failure ( SynchronizerError . generalError ( message : " \( error ) " ) ) )
}
case . failure ( let error ) :
result ( . failure ( SynchronizerError . connectionFailed ( message : error ) ) )
}
} )
}
2021-04-01 07:27:26 -07:00
2021-03-08 10:47:36 -08:00
public func refreshUTXOs ( address : String , from height : BlockHeight = ZcashSDK . SAPLING_ACTIVATION_HEIGHT , result : @ escaping ( Result < RefreshedUTXOs , Error > ) -> Void ) {
2021-04-08 10:18:16 -07:00
self . blockProcessor . refreshUTXOs ( tAddress : address , startHeight : height , result : result )
2021-03-08 10:47:36 -08:00
}
public func getShieldedBalance ( accountIndex : Int = 0 ) -> Int64 {
initializer . getBalance ( account : accountIndex )
}
public func getShieldedVerifiedBalance ( accountIndex : Int = 0 ) -> Int64 {
initializer . getVerifiedBalance ( account : accountIndex )
}
2021-04-08 10:18:16 -07:00
public func getShieldedAddress ( accountIndex : Int ) -> SaplingShieldedAddress ? {
blockProcessor . getShieldedAddress ( accountIndex : accountIndex )
}
public func getUnifiedAddress ( accountIndex : Int ) -> UnifiedAddress ? {
blockProcessor . getUnifiedAddres ( accountIndex : accountIndex )
}
public func getTransparentAddress ( accountIndex : Int ) -> TransparentAddress ? {
blockProcessor . getTransparentAddress ( accountIndex : accountIndex )
}
public func getTransparentBalance ( accountIndex : Int ) throws -> WalletBalance {
try blockProcessor . getTransparentBalance ( accountIndex : accountIndex )
}
2021-01-22 13:51:48 -08:00
/* *
gets the last stored unshielded balance
*/
2021-02-17 15:02:25 -08:00
public func getTransparentBalance ( address : String ) throws -> WalletBalance {
2021-01-22 13:51:48 -08:00
do {
2021-03-08 10:47:36 -08:00
return try self . blockProcessor . utxoCacheBalance ( tAddress : address )
2021-01-22 13:51:48 -08:00
} catch {
throw SynchronizerError . uncategorized ( underlyingError : error )
}
}
2021-03-29 10:53:01 -07:00
public func rewind ( _ policy : RewindPolicy ) throws {
self . stop ( )
2021-03-26 15:56:51 -07:00
var height : BlockHeight ?
2021-05-24 07:56:05 -07:00
2021-03-26 15:56:51 -07:00
switch policy {
2021-05-24 07:56:05 -07:00
case . quick :
break
2021-03-26 15:56:51 -07:00
case . birthday :
2021-03-29 10:53:01 -07:00
let birthday = self . blockProcessor . config . walletBirthday
2021-03-26 15:56:51 -07:00
height = birthday
case . height ( let rewindHeight ) :
height = rewindHeight
case . transaction ( let tx ) :
guard let txHeight = tx . anchor else {
throw SynchronizerError . rewindErrorUnknownArchorHeight
}
height = txHeight
}
do {
2021-05-24 07:56:05 -07:00
let rewindHeight = try self . blockProcessor . rewindTo ( height )
try self . transactionManager . handleReorg ( at : rewindHeight )
2021-03-26 15:56:51 -07:00
} catch {
throw SynchronizerError . rewindError ( underlyingError : error )
}
}
2019-12-16 14:25:45 -08:00
// MARK: n o t i f y s t a t e
2021-06-07 16:00:33 -07:00
private func notify ( progress : CompactBlockProgress ) {
2021-05-11 15:27:22 -07:00
2021-06-15 07:41:11 -07:00
var userInfo = [ AnyHashable : Any ] ( )
2021-05-11 15:27:22 -07:00
userInfo [ NotificationKeys . progress ] = progress
2021-06-07 16:00:33 -07:00
userInfo [ NotificationKeys . blockHeight ] = progress . progressHeight
2021-07-07 07:42:45 -07:00
2021-07-06 11:31:04 -07:00
self . status = SyncStatus ( progress )
2021-05-11 15:27:22 -07:00
NotificationCenter . default . post ( name : Notification . Name . synchronizerProgressUpdated , object : self , userInfo : userInfo )
2019-12-16 14:25:45 -08:00
}
2021-06-14 16:38:05 -07:00
private func notifyStatusChange ( newValue : SyncStatus , oldValue : SyncStatus ) {
2021-06-07 16:00:33 -07:00
NotificationCenter . default . post ( name : . synchronizerStatusWillUpdate ,
object : self ,
userInfo :
[ NotificationKeys . currentStatus : oldValue ,
NotificationKeys . nextStatus : newValue ] )
}
2021-06-14 16:38:05 -07:00
private func notify ( status : SyncStatus ) {
2019-12-16 14:25:45 -08:00
switch status {
case . disconnected :
NotificationCenter . default . post ( name : Notification . Name . synchronizerDisconnected , object : self )
case . stopped :
NotificationCenter . default . post ( name : Notification . Name . synchronizerStopped , object : self )
case . synced :
NotificationCenter . default . post ( name : Notification . Name . synchronizerSynced , object : self )
2021-06-07 16:00:33 -07:00
2021-05-05 12:08:57 -07:00
case . unprepared :
break
2021-06-07 16:00:33 -07:00
case . downloading :
NotificationCenter . default . post ( name : Notification . Name . synchronizerDownloading , object : self )
case . validating :
NotificationCenter . default . post ( name : Notification . Name . synchronizerValidating , object : self )
case . scanning :
NotificationCenter . default . post ( name : Notification . Name . synchronizerScanning , object : self )
case . enhancing :
NotificationCenter . default . post ( name : Notification . Name . synchronizerEnhancing , object : self )
case . fetching :
NotificationCenter . default . post ( name : Notification . Name . synchronizerFetching , object : self )
2021-06-14 16:38:05 -07:00
case . error ( let e ) :
self . notifyFailure ( e )
2019-12-16 14:25:45 -08:00
}
}
// MARK: b o o k k e e p i n g
2019-12-17 13:16:26 -08:00
private func updateMinedTransactions ( ) throws {
2020-01-29 21:34:03 -08:00
try transactionManager . allPendingTransactions ( ) ? . filter ( { $0 . isSubmitSuccess && ! $0 . isMined } ) . forEach ( { pendingTx in
2019-12-17 13:16:26 -08:00
guard let rawId = pendingTx . rawTransactionId else { return }
let tx = try transactionRepository . findBy ( rawId : rawId )
guard let minedHeight = tx ? . minedHeight else { return }
let minedTx = try transactionManager . applyMinedHeight ( pendingTransaction : pendingTx , minedHeight : minedHeight )
notifyMinedTransaction ( minedTx )
} )
}
private func removeConfirmedTransactions ( ) throws {
let latestHeight = try transactionRepository . lastScannedHeight ( )
2020-07-22 12:32:07 -07:00
try transactionManager . allPendingTransactions ( ) ? . filter ( {
$0 . minedHeight > 0 && abs ( $0 . minedHeight - latestHeight ) >= ZcashSDK . DEFAULT_STALE_TOLERANCE }
) . forEach ( {
try transactionManager . delete ( pendingTransaction : $0 )
} )
2019-12-17 13:16:26 -08:00
}
2019-12-16 14:25:45 -08:00
private func refreshPendingTransactions ( ) {
do {
2019-12-17 13:16:26 -08:00
try updateMinedTransactions ( )
try removeConfirmedTransactions ( )
2019-12-16 14:25:45 -08:00
} catch {
2020-03-09 13:25:27 -07:00
LoggerProxy . debug ( " error refreshing pending transactions: \( error ) " )
2019-12-16 14:25:45 -08:00
}
2019-12-06 04:38:47 -08:00
}
2019-12-17 12:11:21 -08:00
private func notifyMinedTransaction ( _ tx : PendingTransactionEntity ) {
DispatchQueue . main . async {
2020-02-26 08:54:48 -08:00
[ weak self ] in
guard let self = self else { return }
2019-12-17 12:11:21 -08:00
NotificationCenter . default . post ( name : Notification . Name . synchronizerMinedTransaction , object : self , userInfo : [ NotificationKeys . minedTransaction : tx ] )
}
}
2019-12-19 05:04:50 -08:00
2020-03-13 17:00:01 -07:00
private func mapError ( _ error : Error ) -> Error {
2020-04-06 08:54:31 -07:00
if let compactBlockProcessorError = error as ? CompactBlockProcessorError {
2020-03-13 17:00:01 -07:00
switch compactBlockProcessorError {
case . dataDbInitFailed ( let path ) :
return SynchronizerError . initFailed ( message : " DataDb init failed at path: \( path ) " )
case . connectionError ( let message ) :
return SynchronizerError . connectionFailed ( message : message )
case . invalidConfiguration :
return SynchronizerError . generalError ( message : " Invalid Configuration " )
case . missingDbPath ( let path ) :
return SynchronizerError . initFailed ( message : " missing Db path: \( path ) " )
case . generalError ( let message ) :
return SynchronizerError . generalError ( message : message )
case . maxAttemptsReached ( attempts : let attempts ) :
2021-07-14 16:43:02 -07:00
return SynchronizerError . maxRetryAttemptsReached ( attempts : attempts )
2020-04-09 15:25:43 -07:00
case . grpcError ( let statusCode , let message ) :
return SynchronizerError . connectionError ( status : statusCode , message : message )
2020-08-10 15:19:59 -07:00
case . connectionTimeout :
return SynchronizerError . networkTimeout
case . unspecifiedError ( let underlyingError ) :
return SynchronizerError . uncategorized ( underlyingError : underlyingError )
case . criticalError :
return SynchronizerError . criticalError
2021-04-08 10:18:16 -07:00
case . invalidAccount :
return SynchronizerError . invalidAccount
2021-05-18 07:49:57 -07:00
case . wrongConsensusBranchId :
2021-05-17 14:14:59 -07:00
return SynchronizerError . lightwalletdValidationFailed ( underlyingError : compactBlockProcessorError )
2021-05-18 07:49:57 -07:00
case . networkMismatch :
2021-05-17 14:14:59 -07:00
return SynchronizerError . lightwalletdValidationFailed ( underlyingError : compactBlockProcessorError )
2021-05-18 07:49:57 -07:00
case . saplingActivationMismatch :
2021-05-17 14:14:59 -07:00
return SynchronizerError . lightwalletdValidationFailed ( underlyingError : compactBlockProcessorError )
2020-03-13 17:00:01 -07:00
}
}
2020-08-10 15:19:59 -07:00
return SynchronizerError . uncategorized ( underlyingError : error )
2020-03-13 17:00:01 -07:00
}
2019-12-19 05:04:50 -08:00
private func notifyFailure ( _ error : Error ) {
2020-03-13 17:00:01 -07:00
2019-12-19 05:04:50 -08:00
DispatchQueue . main . async {
2020-02-26 08:54:48 -08:00
[ weak self ] in
guard let self = self else { return }
2020-03-13 17:00:01 -07:00
NotificationCenter . default . post ( name : Notification . Name . synchronizerFailed , object : self , userInfo : [ NotificationKeys . error : self . mapError ( error ) ] )
2019-12-19 05:04:50 -08:00
}
}
2019-11-14 06:38:54 -08:00
}
2020-01-29 21:34:03 -08:00
extension SDKSynchronizer {
public var pendingTransactions : [ PendingTransactionEntity ] {
( try ? self . allPendingTransactions ( ) ) ? ? [ PendingTransactionEntity ] ( )
}
public var clearedTransactions : [ ConfirmedTransactionEntity ] {
( try ? self . allClearedTransactions ( ) ) ? ? [ ConfirmedTransactionEntity ] ( )
}
public var sentTransactions : [ ConfirmedTransactionEntity ] {
( try ? self . allSentTransactions ( ) ) ? ? [ ConfirmedTransactionEntity ] ( )
}
public var receivedTransactions : [ ConfirmedTransactionEntity ] {
( try ? self . allReceivedTransactions ( ) ) ? ? [ ConfirmedTransactionEntity ] ( )
}
}
2021-06-14 16:38:05 -07:00
import GRPC
extension ConnectionState {
init ( _ connectivityState : ConnectivityState ) {
switch connectivityState {
case . connecting :
self = . connecting
case . idle :
self = . idle
case . ready :
self = . online
case . shutdown :
self = . shutdown
case . transientFailure :
self = . reconnecting
}
}
}
fileprivate struct NullEnhancementProgress : EnhancementProgress {
var totalTransactions : Int { 0 }
var enhancedTransactions : Int { 0 }
var lastFoundTransaction : ConfirmedTransactionEntity ? { nil }
var range : CompactBlockRange { 0 . . . 0 }
}
fileprivate struct NullProgress : BlockProgressReporting {
var startHeight : BlockHeight {
0
}
var targetHeight : BlockHeight {
0
}
var progressHeight : BlockHeight {
0
}
}
2021-07-14 16:43:02 -07:00