2019-10-18 11:45:19 -07:00
//
// C o m p a c t B l o c k P r o c e s s o 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 8 / 0 9 / 2 0 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 .
//
2021-09-15 05:21:29 -07:00
// s w i f t l i n t : d i s a b l e f i l e _ l e n g t h t y p e _ b o d y _ l e n g t h
2019-10-18 11:45:19 -07:00
import Foundation
2020-04-09 15:25:43 -07:00
import GRPC
2021-03-08 10:47:36 -08:00
public typealias RefreshedUTXOs = ( inserted : [ UnspentTransactionOutputEntity ] , skipped : [ UnspentTransactionOutputEntity ] )
2021-09-17 06:49:58 -07:00
2020-02-26 08:54:48 -08:00
/* *
2021-09-17 06:49:58 -07:00
Errors thrown by CompactBlock Processor
*/
2019-11-04 15:18:07 -08:00
public enum CompactBlockProcessorError : Error {
2019-10-18 11:45:19 -07:00
case invalidConfiguration
case missingDbPath ( path : String )
2019-10-30 13:18:57 -07:00
case dataDbInitFailed ( path : String )
2020-08-10 15:19:59 -07:00
case connectionError ( underlyingError : Error )
2020-04-09 15:25:43 -07:00
case grpcError ( statusCode : Int , message : String )
2020-08-10 15:19:59 -07:00
case connectionTimeout
2020-03-13 17:00:01 -07:00
case generalError ( message : String )
case maxAttemptsReached ( attempts : Int )
2020-08-10 15:19:59 -07:00
case unspecifiedError ( underlyingError : Error )
case criticalError
2021-04-08 10:18:16 -07:00
case invalidAccount
2021-05-17 14:14:59 -07:00
case wrongConsensusBranchId ( expectedLocally : ConsensusBranchID , found : ConsensusBranchID )
2021-07-26 16:22:30 -07:00
case networkMismatch ( expected : NetworkType , found : NetworkType )
2021-05-17 14:14:59 -07:00
case saplingActivationMismatch ( expected : BlockHeight , found : BlockHeight )
2022-09-01 05:58:41 -07:00
case unknown
2019-10-18 11:45:19 -07:00
}
2021-09-17 06:49:58 -07:00
2020-02-26 08:54:48 -08:00
/* *
2021-09-17 06:49:58 -07:00
CompactBlockProcessor notification userInfo object keys .
check Notification . Name extensions for more details .
*/
2021-09-15 05:21:29 -07:00
public enum CompactBlockProcessorNotificationKey {
2019-11-04 15:18:07 -08:00
public static let progress = " CompactBlockProcessorNotificationKey.progress "
2021-05-11 15:27:22 -07:00
public static let progressBlockTime = " CompactBlockProcessorNotificationKey.progressBlockTime "
2019-11-14 06:38:54 -08:00
public static let reorgHeight = " CompactBlockProcessorNotificationKey.reorgHeight "
2019-12-16 14:25:45 -08:00
public static let latestScannedBlockHeight = " CompactBlockProcessorNotificationKey.latestScannedBlockHeight "
2019-12-17 09:12:07 -08:00
public static let rewindHeight = " CompactBlockProcessorNotificationKey.rewindHeight "
2020-04-23 10:11:03 -07:00
public static let foundTransactions = " CompactBlockProcessorNotificationKey.foundTransactions "
2021-04-01 07:27:26 -07:00
public static let foundBlocks = " CompactBlockProcessorNotificationKey.foundBlocks "
2020-10-28 15:31:41 -07:00
public static let foundTransactionsRange = " CompactBlockProcessorNotificationKey.foundTransactionsRange "
2020-02-26 08:54:48 -08:00
public static let error = " error "
2021-04-01 07:27:26 -07:00
public static let refreshedUTXOs = " CompactBlockProcessorNotificationKey.refreshedUTXOs "
2021-06-02 14:32:05 -07:00
public static let enhancementProgress = " CompactBlockProcessorNotificationKey.enhancementProgress "
2021-06-07 16:00:33 -07:00
public static let previousStatus = " CompactBlockProcessorNotificationKey.previousStatus "
public static let newStatus = " CompactBlockProcessorNotificationKey.newStatus "
2021-06-14 08:07:01 -07:00
public static let currentConnectivityStatus = " CompactBlockProcessorNotificationKey.currentConnectivityStatus "
public static let previousConnectivityStatus = " CompactBlockProcessorNotificationKey.previousConnectivityStatus "
2021-06-07 16:00:33 -07:00
}
public enum CompactBlockProgress {
2021-09-17 06:49:58 -07:00
case download ( _ progress : BlockProgress )
2021-06-07 16:00:33 -07:00
case validate
2021-09-17 06:49:58 -07:00
case scan ( _ progress : BlockProgress )
2021-06-07 16:00:33 -07:00
case enhance ( _ progress : EnhancementStreamProgress )
case fetch
public var progress : Float {
switch self {
2021-09-17 06:49:58 -07:00
case . download ( let blockProgress ) , . scan ( let blockProgress ) :
2021-09-15 05:21:29 -07:00
return blockProgress . progress
case . enhance ( let enhancementProgress ) :
return enhancementProgress . progress
2021-06-07 16:00:33 -07:00
default :
return 0
}
}
public var progressHeight : BlockHeight ? {
switch self {
2021-09-17 06:49:58 -07:00
case . download ( let blockProgress ) , . scan ( let blockProgress ) :
2021-09-15 05:21:29 -07:00
return blockProgress . progressHeight
case . enhance ( let enhancementProgress ) :
return enhancementProgress . lastFoundTransaction ? . minedHeight
2021-06-07 16:00:33 -07:00
default :
return 0
}
}
public var blockDate : Date ? {
2021-09-17 06:49:58 -07:00
if case . enhance ( let enhancementProgress ) = self , let time = enhancementProgress . lastFoundTransaction ? . blockTimeInSeconds {
2021-06-07 16:00:33 -07:00
return Date ( timeIntervalSince1970 : time )
}
2021-09-17 06:49:58 -07:00
2021-06-07 16:00:33 -07:00
return nil
}
2021-07-07 07:42:45 -07:00
public var targetHeight : BlockHeight ? {
switch self {
2021-09-15 05:21:29 -07:00
case . download ( let blockProgress ) , . scan ( let blockProgress ) :
return blockProgress . targetHeight
2021-07-07 07:42:45 -07:00
default :
return nil
}
}
2021-06-07 16:00:33 -07:00
}
protocol EnhancementStreamDelegate : AnyObject {
func transactionEnhancementProgressUpdated ( _ progress : EnhancementProgress )
2019-10-31 10:33:21 -07:00
}
2021-06-07 16:00:33 -07:00
public protocol EnhancementProgress {
var totalTransactions : Int { get }
var enhancedTransactions : Int { get }
var lastFoundTransaction : ConfirmedTransactionEntity ? { get }
var range : CompactBlockRange { get }
}
2021-06-14 08:07:01 -07:00
2021-06-07 16:00:33 -07:00
public struct EnhancementStreamProgress : EnhancementProgress {
public var totalTransactions : Int
public var enhancedTransactions : Int
public var lastFoundTransaction : ConfirmedTransactionEntity ?
public var range : CompactBlockRange
public var progress : Float {
totalTransactions > 0 ? Float ( enhancedTransactions ) / Float ( totalTransactions ) : 0
}
}
2019-11-04 15:18:07 -08:00
public extension Notification . Name {
2019-10-31 10:33:21 -07:00
/* *
2021-09-17 06:49:58 -07:00
Processing progress update
2020-02-26 08:54:48 -08:00
2021-09-17 06:49:58 -07:00
Query the userInfo object for the key CompactBlockProcessorNotificationKey . progress for a CompactBlockProgress struct
*/
2019-10-18 11:45:19 -07:00
static let blockProcessorUpdated = Notification . Name ( rawValue : " CompactBlockProcessorUpdated " )
2020-02-26 08:54:48 -08:00
2021-06-07 16:00:33 -07:00
/* *
2021-09-17 06:49:58 -07:00
notification sent when processor status changed
*/
2021-06-07 16:00:33 -07:00
static let blockProcessorStatusChanged = Notification . Name ( rawValue : " CompactBlockProcessorStatusChanged " )
2021-09-17 06:49:58 -07:00
2020-02-26 08:54:48 -08:00
/* *
2021-09-17 06:49:58 -07:00
Notification sent when a compact block processor starts downloading
*/
2019-10-30 13:18:57 -07:00
static let blockProcessorStartedDownloading = Notification . Name ( rawValue : " CompactBlockProcessorStartedDownloading " )
2020-02-26 08:54:48 -08:00
/* *
2021-09-17 06:49:58 -07:00
Notification sent when the compact block processor starts validating the chain state
*/
2019-10-30 13:18:57 -07:00
static let blockProcessorStartedValidating = Notification . Name ( rawValue : " CompactBlockProcessorStartedValidating " )
2020-02-26 08:54:48 -08:00
/* *
2021-09-17 06:49:58 -07:00
Notification sent when the compact block processor starts scanning blocks from the cache
*/
2019-10-30 13:18:57 -07:00
static let blockProcessorStartedScanning = Notification . Name ( rawValue : " CompactBlockProcessorStartedScanning " )
2020-02-26 08:54:48 -08:00
/* *
2021-09-17 06:49:58 -07:00
Notification sent when the compact block processor stop ( ) method is called
*/
2019-10-18 13:09:13 -07:00
static let blockProcessorStopped = Notification . Name ( rawValue : " CompactBlockProcessorStopped " )
2020-02-26 08:54:48 -08:00
/* *
2021-09-17 06:49:58 -07:00
Notification sent when the compact block processor presented an error .
2020-02-26 08:54:48 -08:00
2021-09-17 06:49:58 -07:00
Query userInfo object on the key CompactBlockProcessorNotificationKey . error
*/
2019-10-30 13:18:57 -07:00
static let blockProcessorFailed = Notification . Name ( rawValue : " CompactBlockProcessorFailed " )
2021-09-17 06:49:58 -07:00
2020-02-26 08:54:48 -08:00
/* *
2021-09-17 06:49:58 -07:00
Notification sent when the compact block processor has finished syncing the blockchain to latest height
*/
2019-12-16 14:25:45 -08:00
static let blockProcessorFinished = Notification . Name ( rawValue : " CompactBlockProcessorFinished " )
2021-09-17 06:49:58 -07:00
2020-02-26 08:54:48 -08:00
/* *
2021-09-17 06:49:58 -07:00
Notification sent when the compact block processor is doing nothing
*/
2019-10-30 13:18:57 -07:00
static let blockProcessorIdle = Notification . Name ( rawValue : " CompactBlockProcessorIdle " )
2021-09-17 06:49:58 -07:00
2020-02-26 08:54:48 -08:00
/* *
2021-09-17 06:49:58 -07:00
Notification sent when something odd happened . probably going from a state to another state that shouldn ' t be the next state .
*/
2019-10-30 13:18:57 -07:00
static let blockProcessorUnknownTransition = Notification . Name ( rawValue : " CompactBlockProcessorTransitionUnknown " )
2021-09-17 06:49:58 -07:00
2020-02-26 08:54:48 -08:00
/* *
2021-09-17 06:49:58 -07:00
Notification sent when the compact block processor handled a ReOrg .
2020-02-26 08:54:48 -08:00
2021-09-17 06:49:58 -07:00
Query the userInfo object on the key CompactBlockProcessorNotificationKey . reorgHeight for the height on which the reorg was detected . CompactBlockProcessorNotificationKey . rewindHeight for the height that the processor backed to in order to solve the Reorg
*/
2019-11-14 06:38:54 -08:00
static let blockProcessorHandledReOrg = Notification . Name ( rawValue : " CompactBlockProcessorHandledReOrg " )
2020-04-23 10:11:03 -07:00
/* *
2021-09-17 06:49:58 -07:00
Notification sent when the compact block processor enhanced a bunch of transactions
2020-10-28 15:31:41 -07:00
Query the user info object for CompactBlockProcessorNotificationKey . foundTransactions which will contain an [ ConfirmedTransactionEntity ] Array with the found transactions and CompactBlockProcessorNotificationKey . foundTransactionsrange
2021-09-17 06:49:58 -07:00
*/
2020-04-23 10:11:03 -07:00
static let blockProcessorFoundTransactions = Notification . Name ( rawValue : " CompactBlockProcessorFoundTransactions " )
2021-04-01 07:27:26 -07:00
/* *
2021-09-17 06:49:58 -07:00
Notification sent when the compact block processor fetched utxos from lightwalletd attempted to store them
Query the user info object for CompactBlockProcessorNotificationKey . blockProcessorStoredUTXOs which will contain a RefreshedUTXOs tuple with the collection of UTXOs stored or skipped
*/
2021-04-01 07:27:26 -07:00
static let blockProcessorStoredUTXOs = Notification . Name ( rawValue : " CompactBlockProcessorStoredUTXOs " )
2021-06-02 14:32:05 -07:00
static let blockProcessorStartedEnhancing = Notification . Name ( rawValue : " CompactBlockProcessorStartedEnhancing " )
static let blockProcessorEnhancementProgress = Notification . Name ( " CompactBlockProcessorEnhancementProgress " )
static let blockProcessorStartedFetching = Notification . Name ( rawValue : " CompactBlockProcessorStartedFetching " )
2021-06-14 08:07:01 -07:00
/* *
2021-09-17 06:49:58 -07:00
Notification sent when the grpc service connection detects a change . Query the user info object for status change details ` currentConnectivityStatus ` for current and previous with ` previousConnectivityStatus `
*/
2021-06-14 08:07:01 -07:00
static let blockProcessorConnectivityStateChanged = Notification . Name ( " CompactBlockProcessorConnectivityStateChanged " )
2019-10-18 11:45:19 -07:00
}
2022-07-29 06:07:08 -07:00
// / T h e c o m p a c t b l o c k p r o c e s s o r i s i n c h a r g e o f o r c h e s t r a t i n g t h e d o w n l o a d a n d c a c h i n g o f c o m p a c t b l o c k s f r o m a L i g h t W a l l e t E n d p o i n t
// / w h e n s t a r t e d t h e p r o c e s s o r d o w n l o a d s d o e s a d o w n l o a d - v a l i d a t e - s c a n c y c l e u n t i l i t r e a c h e s l a t e s t h e i g h t o n t h e b l o c k c h a i n .
2019-11-04 15:18:07 -08:00
public class CompactBlockProcessor {
2022-07-29 06:07:08 -07:00
// / C o m p a c t B l o c k P r o c e s s o r c o n f i g u r a t i o n
// /
// / P r o p e r t y : c a c h e D b P a t h a b s o l u t e f i l e p a t h o f t h e D B w h e r e r a w , u n p r o c e s s e d c o m p a c t b l o c k s a r e s t o r e d .
// / P r o p e r t y : d a t a D b P a t h a b s o l u t e f i l e p a t h o f t h e D B w h e r e a l l i n f o r m a t i o n d e r i v e d f r o m t h e c a c h e D B i s s t o r e d .
2019-11-04 15:18:07 -08:00
public struct Configuration {
public var cacheDb : URL
public var dataDb : URL
2022-07-29 06:07:08 -07:00
public var downloadBatchSize = ZcashSDK . DefaultDownloadBatch
public var scanningBatchSize = ZcashSDK . DefaultScanningBatch
2021-09-15 05:21:29 -07:00
public var retries = ZcashSDK . defaultRetries
public var maxBackoffInterval = ZcashSDK . defaultMaxBackOffInterval
public var rewindDistance = ZcashSDK . defaultRewindDistance
2019-12-16 14:25:45 -08:00
public var walletBirthday : BlockHeight
2022-07-30 16:01:18 -07:00
public private ( set ) var downloadBufferSize : Int = 10
2021-07-28 09:59:10 -07:00
private ( set ) var network : ZcashNetwork
2020-06-03 16:18:57 -07:00
private ( set ) var saplingActivation : BlockHeight
2021-09-17 06:49:58 -07:00
public var blockPollInterval : TimeInterval {
TimeInterval . random ( in : ZcashSDK . defaultPollInterval / 2 . . . ZcashSDK . defaultPollInterval * 1.5 )
}
2020-06-03 16:18:57 -07:00
init (
2021-07-26 16:22:30 -07:00
cacheDb : URL ,
dataDb : URL ,
downloadBatchSize : Int ,
retries : Int ,
maxBackoffInterval : TimeInterval ,
rewindDistance : Int ,
walletBirthday : BlockHeight ,
saplingActivation : BlockHeight ,
2021-07-28 09:59:10 -07:00
network : ZcashNetwork
2021-09-17 06:49:58 -07:00
) {
2020-06-03 16:18:57 -07:00
self . cacheDb = cacheDb
self . dataDb = dataDb
2021-07-26 16:22:30 -07:00
self . network = network
2020-06-03 16:18:57 -07:00
self . downloadBatchSize = downloadBatchSize
self . retries = retries
self . maxBackoffInterval = maxBackoffInterval
self . rewindDistance = rewindDistance
self . walletBirthday = walletBirthday
self . saplingActivation = saplingActivation
}
2019-11-04 15:18:07 -08:00
2021-09-17 06:49:58 -07:00
public init ( cacheDb : URL , dataDb : URL , walletBirthday : BlockHeight , network : ZcashNetwork ) {
2019-11-04 15:18:07 -08:00
self . cacheDb = cacheDb
self . dataDb = dataDb
2019-12-16 14:25:45 -08:00
self . walletBirthday = walletBirthday
2021-09-15 05:21:29 -07:00
self . saplingActivation = network . constants . saplingActivationHeight
2021-07-28 09:59:10 -07:00
self . network = network
2019-11-04 15:18:07 -08:00
}
2019-10-30 13:18:57 -07:00
}
2021-09-17 06:49:58 -07:00
2020-02-26 08:54:48 -08:00
/* *
2021-09-17 06:49:58 -07:00
Represents the possible states of a CompactBlockProcessor
*/
2019-11-04 15:18:07 -08:00
public enum State {
2019-10-18 11:45:19 -07:00
/* *
2021-09-17 06:49:58 -07:00
connected and downloading blocks
*/
2019-10-30 13:18:57 -07:00
case downloading
2019-10-18 11:45:19 -07:00
/* *
2021-09-17 06:49:58 -07:00
was doing something but was paused
*/
2019-10-18 11:45:19 -07:00
case stopped
2021-09-17 06:49:58 -07:00
2019-10-18 11:45:19 -07:00
/* *
2021-09-17 06:49:58 -07:00
processor is validating
*/
2019-10-30 13:18:57 -07:00
case validating
2021-09-17 06:49:58 -07:00
2019-10-30 13:18:57 -07:00
/* *
2021-09-17 06:49:58 -07:00
processor is scanning
*/
2019-10-18 11:45:19 -07:00
case scanning
2021-06-02 14:32:05 -07:00
/* *
2021-09-17 06:49:58 -07:00
Processor is Enhancing transactions
*/
2021-06-02 14:32:05 -07:00
case enhancing
/* *
2021-09-17 06:49:58 -07:00
fetching utxos
*/
2021-06-02 14:32:05 -07:00
case fetching
2021-09-17 06:49:58 -07:00
2019-10-18 11:45:19 -07:00
/* *
2021-09-17 06:49:58 -07:00
was processing but erred
*/
2019-10-30 13:18:57 -07:00
case error ( _ e : Error )
2019-10-18 11:45:19 -07:00
/* *
2021-09-17 06:49:58 -07:00
Processor is up to date with the blockchain and you can now make transactions .
*/
2019-10-30 13:18:57 -07:00
case synced
2019-10-18 11:45:19 -07:00
}
2022-09-01 05:58:41 -07:00
// TODO: t h i s i s n ' t a n A c t o r e v e n t h o u g h i t l o o k s l i k e a g o o d c a n d i d a t e , t h e r e a s o n :
// ` s t a t e ` l i v e s i n b o t h s y n c a n d a s y n c e n v i r o n m e n t s . A n A c t o r i s d e m a n d i n g a s y n c c o n t e x t o n l y
// s o w e c a n ' t t a k e t h e a d v a n t a g e u n l e s s w e e n c a p s u l a t e a l l ` s t a t e ` r e a d s / w r i t e s t o a s y n c c o n t e x t .
// T h e r e f o r e s o l u t i o n w i t h c l a s s + l o c k w o r k s f o r u s b u t r e v e n t u a l l y w i l l b e r e p l a c e d .
// T h e f u t u r e o f C o m p a c t B l o c k P r o c e s s o r i s a n a c t o r ( w e w o n ' t n e e d t o e n c a p s u l a t e t h e s t a t e s e p a r a t e l y ) , i s s u e 5 2 3 ,
// h t t p s : / / g i t h u b . c o m / z c a s h / Z c a s h L i g h t C l i e n t K i t / i s s u e s / 5 2 3
public class ThreadSafeState {
private var state : State = . stopped
let lock = NSLock ( )
func setState ( _ newState : State ) {
lock . lock ( )
defer { lock . unlock ( ) }
state = newState
}
public func getState ( ) -> State {
lock . lock ( )
defer { lock . unlock ( ) }
return state
2019-10-30 13:18:57 -07:00
}
}
2022-09-01 05:58:41 -07:00
public internal ( set ) var state = ThreadSafeState ( )
2021-09-17 06:49:58 -07:00
var config : Configuration {
willSet {
self . stop ( )
}
}
2022-07-29 11:20:55 -07:00
2021-09-17 06:49:58 -07:00
var maxAttemptsReached : Bool {
self . retryAttempts >= self . config . retries
}
2022-07-29 11:20:55 -07:00
2021-09-17 06:49:58 -07:00
var shouldStart : Bool {
2022-09-01 05:58:41 -07:00
switch self . state . getState ( ) {
2021-09-17 06:49:58 -07:00
case . stopped , . synced , . error :
return ! maxAttemptsReached
default :
return false
}
}
2022-09-01 05:58:41 -07:00
var service : LightWalletService
2022-07-29 11:20:55 -07:00
private ( set ) var downloader : CompactBlockDownloading
2022-09-01 05:58:41 -07:00
var storage : CompactBlockStorage
var transactionRepository : TransactionRepository
var accountRepository : AccountRepository
var rustBackend : ZcashRustBackendWelding . Type
2019-11-14 06:38:54 -08:00
private var retryAttempts : Int = 0
2019-10-18 11:45:19 -07:00
private var backoffTimer : Timer ?
2019-11-14 06:38:54 -08:00
private var lowerBoundHeight : BlockHeight ?
private var latestBlockHeight : BlockHeight
private var lastChainValidationFailure : BlockHeight ?
private var consecutiveChainValidationErrors : Int = 0
2022-09-01 05:58:41 -07:00
var processingError : Error ?
2021-09-17 06:49:58 -07:00
private var foundBlocks = false
2019-10-18 11:45:19 -07:00
private var maxAttempts : Int {
config . retries
}
private var batchSize : BlockHeight {
2019-10-30 13:18:57 -07:00
BlockHeight ( self . config . downloadBatchSize )
2019-10-18 11:45:19 -07:00
}
2021-09-17 06:49:58 -07:00
2022-09-01 05:58:41 -07:00
private var cancelableTask : Task < Void , Error > ?
2022-07-29 06:07:08 -07:00
// / I n i t i a l i z e s a C o m p a c t B l o c k P r o c e s s o r i n s t a n c e
// / - P a r a m e t e r s :
// / - s e r v i c e : c o n c r e t e i m p l e m e n t a t i o n o f ` L i g h t W a l l e t S e r v i c e ` p r o t o c o l
// / - s t o r a g e : c o n c r e t e i m p l e m e n t a t i o n o f ` C o m p a c t B l o c k S t o r a g e ` p r o t o c o l
// / - b a c k e n d : a c l a s s t h a t c o m p l i e s t o ` Z c a s h R u s t B a c k e n d W e l d i n g `
// / - c o n f i g : ` C o n f i g u r a t i o n ` s t r u c t f o r t h i s p r o c e s s o r
2021-09-15 05:21:29 -07:00
convenience init (
service : LightWalletService ,
storage : CompactBlockStorage ,
backend : ZcashRustBackendWelding . Type ,
config : Configuration
) {
self . init (
service : service ,
storage : storage ,
backend : backend ,
config : config ,
repository : TransactionRepositoryBuilder . build (
dataDbURL : config . dataDb
) ,
2021-09-17 06:49:58 -07:00
accountRepository : AccountRepositoryBuilder . build ( dataDbURL : config . dataDb , readOnly : true )
)
2020-04-23 10:11:03 -07:00
}
2022-07-29 06:07:08 -07:00
// / I n i t i a l i z e s a C o m p a c t B l o c k P r o c e s s o r i n s t a n c e f r o m a n I n i t i a l i z e d o b j e c t
// / - P a r a m e t e r s :
// / - i n i t i a l i z e r : a n i n s t a n c e t h a t c o m p l i e s t o C o m p a c t B l o c k D o w n l o a d i n g p r o t o c o l
2021-04-01 14:08:00 -07:00
public convenience init ( initializer : Initializer ) {
2021-09-15 05:21:29 -07:00
self . init (
service : initializer . lightWalletService ,
storage : initializer . storage ,
backend : initializer . rustBackend ,
config : Configuration (
cacheDb : initializer . cacheDbURL ,
dataDb : initializer . dataDbURL ,
2022-07-12 12:36:12 -07:00
walletBirthday : Checkpoint . birthday (
with : initializer . walletBirthday ,
network : initializer . network
) . height ,
2021-09-15 05:21:29 -07:00
network : initializer . network
) ,
repository : initializer . transactionRepository ,
2021-09-17 06:49:58 -07:00
accountRepository : initializer . accountRepository
)
2020-04-23 10:11:03 -07:00
}
2021-09-15 05:21:29 -07:00
internal init (
service : LightWalletService ,
storage : CompactBlockStorage ,
backend : ZcashRustBackendWelding . Type ,
config : Configuration ,
repository : TransactionRepository ,
accountRepository : AccountRepository
) {
2021-06-02 14:32:05 -07:00
self . service = service
self . downloader = CompactBlockDownloader ( service : service , storage : storage )
2019-10-18 11:45:19 -07:00
self . rustBackend = backend
2021-06-02 14:32:05 -07:00
self . storage = storage
2019-10-18 11:45:19 -07:00
self . config = config
2020-04-23 10:11:03 -07:00
self . transactionRepository = repository
2019-10-30 13:18:57 -07:00
self . latestBlockHeight = config . walletBirthday
2021-04-08 10:18:16 -07:00
self . accountRepository = accountRepository
2019-10-18 11:45:19 -07:00
}
deinit {
2022-09-01 05:58:41 -07:00
cancelableTask ? . cancel ( )
}
func setState ( _ newState : State ) {
let oldValue = state . getState ( )
state . setState ( newState )
transitionState ( from : oldValue , to : newState )
2019-10-18 11:45:19 -07:00
}
2021-09-17 06:49:58 -07:00
static func validateServerInfo (
_ info : LightWalletdInfo ,
saplingActivation : BlockHeight ,
localNetwork : ZcashNetwork ,
rustBackend : ZcashRustBackendWelding . Type
) throws {
// c h e c k n e t w o r k t y p e s
guard let remoteNetworkType = NetworkType . forChainName ( info . chainName ) else {
throw CompactBlockProcessorError . generalError (
message : " Chain name does not match. Expected either 'test' or 'main' but received ' \( info . chainName ) '. " +
" this is probably an API or programming error "
)
2020-02-26 08:54:48 -08:00
}
2021-09-17 06:49:58 -07:00
guard remoteNetworkType = = localNetwork . networkType else {
throw CompactBlockProcessorError . networkMismatch ( expected : localNetwork . networkType , found : remoteNetworkType )
2019-10-18 11:45:19 -07:00
}
2021-09-17 06:49:58 -07:00
guard saplingActivation = = info . saplingActivationHeight else {
throw CompactBlockProcessorError . saplingActivationMismatch ( expected : saplingActivation , found : BlockHeight ( info . saplingActivationHeight ) )
}
// c h e c k b r a n c h i d
let localBranch = try rustBackend . consensusBranchIdFor ( height : Int32 ( info . blockHeight ) , networkType : localNetwork . networkType )
guard let remoteBranchID = ConsensusBranchID . fromString ( info . consensusBranchID ) else {
throw CompactBlockProcessorError . generalError ( message : " Consensus BranchIDs don't match this is probably an API or programming error " )
}
guard remoteBranchID = = localBranch else {
throw CompactBlockProcessorError . wrongConsensusBranchId ( expectedLocally : localBranch , found : remoteBranchID )
2019-10-18 11:45:19 -07:00
}
}
2021-09-17 06:49:58 -07:00
static func nextBatchBlockRange ( latestHeight : BlockHeight , latestDownloadedHeight : BlockHeight , walletBirthday : BlockHeight ) -> CompactBlockRange {
let lowerBound = latestDownloadedHeight <= walletBirthday ? walletBirthday : latestDownloadedHeight + 1
let upperBound = latestHeight
return lowerBound . . . upperBound
}
2022-07-29 06:07:08 -07:00
// / S t a r t s t h e C o m p a c t B l o c k P r o c e s s o r i n s t a n c e a n d s t a r t s d o w n l o a d i n g a n d p r o c e s s i n g b l o c k s
// /
// / t r i g g e r s t h e b l o c k P r o c e s s o r S t a r t e d D o w n l o a d i n g n o t i f i c a t i o n
// /
// / - I m p o r t a n t : s u b s c r i b e t o t h e n o t i f i c a t i o n s b e f o r e c a l l i n g t h i s m e t h o d
2020-03-13 17:00:01 -07:00
public func start ( retry : Bool = false ) throws {
if retry {
self . retryAttempts = 0
2020-10-26 14:57:19 -07:00
self . processingError = nil
2021-05-03 14:50:00 -07:00
self . backoffTimer ? . invalidate ( )
self . backoffTimer = nil
2020-03-13 17:00:01 -07:00
}
2021-09-17 06:49:58 -07:00
2020-02-26 08:54:48 -08:00
guard shouldStart else {
2022-09-01 05:58:41 -07:00
switch self . state . getState ( ) {
2020-03-13 17:00:01 -07:00
case . error ( let e ) :
// m a x a t t e m p t s h a v e b e e n r e a c h e d
LoggerProxy . info ( " max retry attempts reached with error: \( e ) " )
notifyError ( CompactBlockProcessorError . maxAttemptsReached ( attempts : self . maxAttempts ) )
2022-09-01 05:58:41 -07:00
setState ( . stopped )
2020-03-13 17:00:01 -07:00
case . stopped :
// m a x a t t e m p t s h a v e b e e n r e a c h e d
2020-04-23 10:11:03 -07:00
LoggerProxy . info ( " max retry attempts reached " )
notifyError ( CompactBlockProcessorError . maxAttemptsReached ( attempts : self . maxAttempts ) )
2020-03-13 17:00:01 -07:00
case . synced :
// m a x a t t e m p t s h a v e b e e n r e a c h e d
LoggerProxy . warn ( " max retry attempts reached on synced state, this indicates malfunction " )
notifyError ( CompactBlockProcessorError . maxAttemptsReached ( attempts : self . maxAttempts ) )
default :
LoggerProxy . debug ( " Warning: compact block processor was started while busy!!!! " )
}
2020-02-26 08:54:48 -08:00
return
2019-11-04 15:18:07 -08:00
}
2021-09-17 06:49:58 -07:00
2022-09-01 05:58:41 -07:00
self . nextBatch ( )
2021-05-17 14:14:59 -07:00
}
2021-09-17 06:49:58 -07:00
2020-02-26 08:54:48 -08:00
/* *
2021-09-17 06:49:58 -07:00
Stops the CompactBlockProcessor
Note : retry count is reset
*/
2022-09-01 05:58:41 -07:00
public func stop ( ) {
2020-04-23 10:11:03 -07:00
self . backoffTimer ? . invalidate ( )
self . backoffTimer = nil
2021-09-17 06:49:58 -07:00
2022-09-01 05:58:41 -07:00
cancelableTask ? . cancel ( )
2021-09-17 06:49:58 -07:00
2019-11-14 06:38:54 -08:00
self . retryAttempts = 0
2022-09-01 05:58:41 -07:00
setState ( . stopped )
2019-10-30 13:18:57 -07:00
}
2021-09-17 06:49:58 -07:00
2021-05-24 07:56:05 -07:00
/* *
2021-09-17 06:49:58 -07:00
Rewinds to provided height .
If nil is provided , it will rescan to nearest height ( quick rescan )
*/
public func rewindTo ( _ height : BlockHeight ? ) throws -> BlockHeight {
2021-03-26 15:56:51 -07:00
self . stop ( )
2021-09-17 06:49:58 -07:00
2021-05-24 07:56:05 -07:00
let lastDownloaded = try downloader . lastDownloadedBlockHeight ( )
let height = Int32 ( height ? ? lastDownloaded )
2022-10-02 19:11:17 -07:00
let nearestHeight = rustBackend . getNearestRewindHeight (
dbData : config . dataDb ,
height : height ,
networkType : self . config . network . networkType
)
2021-09-17 06:49:58 -07:00
2021-04-19 10:07:50 -07:00
guard nearestHeight > 0 else {
2021-09-17 06:49:58 -07:00
let error = rustBackend . lastError ( ) ? ? RustWeldingError . genericError (
message : " unknown error getting nearest rewind height for height: \( height ) "
)
2021-04-19 10:07:50 -07:00
fail ( error )
throw error
}
2021-09-17 06:49:58 -07:00
2021-05-24 07:56:05 -07:00
// FIXME: t h i s s h o u l d b e d o n e o n t h e r u s t l a y e r
2021-09-17 06:49:58 -07:00
let rewindHeight = max ( Int32 ( nearestHeight - 1 ) , Int32 ( config . walletBirthday ) )
2021-07-28 09:59:10 -07:00
guard rustBackend . rewindToHeight ( dbData : config . dataDb , height : rewindHeight , networkType : self . config . network . networkType ) else {
2021-04-19 10:07:50 -07:00
let error = rustBackend . lastError ( ) ? ? RustWeldingError . genericError ( message : " unknown error rewinding to height \( height ) " )
fail ( error )
throw error
2021-03-26 15:56:51 -07:00
}
2021-09-17 06:49:58 -07:00
2021-03-26 15:56:51 -07:00
// c l e a r c a c h e
2021-04-19 10:07:50 -07:00
try downloader . rewind ( to : BlockHeight ( rewindHeight ) )
2021-04-12 09:10:14 -07:00
self . lastChainValidationFailure = nil
self . lowerBoundHeight = try ? downloader . lastDownloadedBlockHeight ( )
2021-05-24 07:56:05 -07:00
return BlockHeight ( rewindHeight )
2021-03-26 15:56:51 -07:00
}
2021-09-17 06:49:58 -07:00
/* *
changes the wallet birthday in configuration . Use this method when wallet birthday is not available and the processor can ' t be lazy initialized .
- Note : this does not rewind your chain state
- Parameter startHeight : the wallet birthday for this compact block processor
- Throws CompactBlockProcessorError . invalidConfiguration if block height is invalid or if processor is already started
*/
func setStartHeight ( _ startHeight : BlockHeight ) throws {
2022-09-01 05:58:41 -07:00
guard self . state . getState ( ) = = . stopped , startHeight >= config . network . constants . saplingActivationHeight else {
2021-09-17 06:49:58 -07:00
throw CompactBlockProcessorError . invalidConfiguration
}
var config = self . config
config . walletBirthday = startHeight
self . config = config
}
func validateServer ( completionBlock : @ escaping ( ( ) -> Void ) ) {
2022-10-03 16:05:11 -07:00
Task { @ MainActor in
do {
let info = try await self . service . getInfo ( )
try Self . validateServerInfo (
info ,
saplingActivation : self . config . saplingActivation ,
localNetwork : self . config . network ,
rustBackend : self . rustBackend
)
completionBlock ( )
} catch let error as LightWalletServiceError {
2021-09-17 06:49:58 -07:00
self . severeFailure ( error . mapToProcessorError ( ) )
2022-10-03 16:05:11 -07:00
} catch {
self . severeFailure ( error )
2019-10-18 11:45:19 -07:00
}
2022-10-03 16:05:11 -07:00
}
2019-10-30 13:18:57 -07:00
}
2022-09-01 05:58:41 -07:00
// / P r o c e s s e s n e w b l o c k s o n t h e g i v e n r a n g e b a s e d o n t h e c o n f i g u r a t i o n s e t f o r t h i s i n s t a n c e
2019-10-30 13:18:57 -07:00
func processNewBlocks ( range : CompactBlockRange ) {
2021-04-01 07:27:26 -07:00
self . foundBlocks = true
2020-03-11 19:17:32 -07:00
self . backoffTimer ? . invalidate ( )
self . backoffTimer = nil
2022-09-01 05:58:41 -07:00
cancelableTask = Task ( priority : . userInitiated ) {
do {
try await compactBlockStreamDownload (
blockBufferSize : config . downloadBufferSize ,
startHeight : range . lowerBound ,
targetHeight : range . upperBound
)
try await compactBlockValidation ( )
try await compactBlockBatchScanning ( range : range )
try await compactBlockEnhancement ( range : range )
try await fetchUnspentTxOutputs ( range : range )
} catch {
if error is CancellationError {
2021-07-14 16:43:02 -07:00
}
2022-09-01 05:58:41 -07:00
if ! ( Task . isCancelled ) {
fail ( error )
}
2021-07-14 16:43:02 -07:00
}
2021-06-03 07:51:12 -07:00
}
2019-10-18 11:45:19 -07:00
}
2019-11-04 15:18:07 -08:00
func calculateProgress ( start : BlockHeight , current : BlockHeight , latest : BlockHeight ) -> Float {
let totalBlocks = Float ( abs ( latest - start ) )
let completed = Float ( abs ( current - start ) )
2019-10-31 10:33:21 -07:00
let progress = completed / totalBlocks
return progress
}
2021-06-07 16:00:33 -07:00
func notifyProgress ( _ progress : CompactBlockProgress ) {
2021-09-17 06:49:58 -07:00
var userInfo : [ AnyHashable : Any ] = [ : ]
2021-06-07 16:00:33 -07:00
userInfo [ CompactBlockProcessorNotificationKey . progress ] = progress
2021-09-17 06:49:58 -07:00
LoggerProxy . debug ( " progress: \( progress ) " )
2021-05-11 15:27:22 -07:00
2021-09-17 06:49:58 -07:00
NotificationCenter . default . post (
name : Notification . Name . blockProcessorUpdated ,
object : self ,
userInfo : userInfo
)
2019-10-31 10:33:21 -07:00
}
2020-10-28 15:31:41 -07:00
func notifyTransactions ( _ txs : [ ConfirmedTransactionEntity ] , in range : BlockRange ) {
2021-09-17 06:49:58 -07:00
NotificationCenter . default . post (
name : . blockProcessorFoundTransactions ,
object : self ,
userInfo : [
CompactBlockProcessorNotificationKey . foundTransactions : txs ,
CompactBlockProcessorNotificationKey . foundTransactionsRange : ClosedRange ( uncheckedBounds : ( range . start . height , range . end . height ) )
]
)
2020-04-23 10:11:03 -07:00
}
2021-09-17 06:49:58 -07:00
func determineLowerBound (
errorHeight : Int ,
consecutiveErrors : Int ,
walletBirthday : BlockHeight
) -> BlockHeight {
let offset = min ( ZcashSDK . maxReorgSize , ZcashSDK . defaultRewindDistance * ( consecutiveErrors + 1 ) )
return max ( errorHeight - offset , walletBirthday - ZcashSDK . maxReorgSize )
}
func severeFailure ( _ error : Error ) {
2022-09-01 05:58:41 -07:00
cancelableTask ? . cancel ( )
2021-09-17 06:49:58 -07:00
LoggerProxy . error ( " show stoppper failure: \( error ) " )
self . backoffTimer ? . invalidate ( )
self . retryAttempts = config . retries
self . processingError = error
2022-09-01 05:58:41 -07:00
setState ( . error ( error ) )
2021-09-17 06:49:58 -07:00
self . notifyError ( error )
}
func fail ( _ error : Error ) {
// t o d o s p e c i f y : f a i l u r e
LoggerProxy . error ( " \( error ) " )
2022-09-01 05:58:41 -07:00
cancelableTask ? . cancel ( )
2021-09-17 06:49:58 -07:00
self . retryAttempts += 1
self . processingError = error
2022-09-01 05:58:41 -07:00
switch self . state . getState ( ) {
2021-09-17 06:49:58 -07:00
case . error :
notifyError ( error )
default :
break
}
2022-09-01 05:58:41 -07:00
setState ( . error ( error ) )
2021-09-17 06:49:58 -07:00
guard self . maxAttemptsReached else { return }
// d o n ' t s e t a n e w t i m e r i f t h e r e a r e n o m o r e a t t e m p t s .
self . setTimer ( )
}
func retryProcessing ( range : CompactBlockRange ) {
2022-09-01 05:58:41 -07:00
cancelableTask ? . cancel ( )
2021-09-17 06:49:58 -07:00
// u p d a t e r e t r i e s
self . retryAttempts += 1
self . processingError = nil
guard self . retryAttempts < config . retries else {
self . notifyError ( CompactBlockProcessorError . maxAttemptsReached ( attempts : self . retryAttempts ) )
self . stop ( )
return
}
do {
try downloader . rewind ( to : max ( range . lowerBound , self . config . walletBirthday ) )
// p r o c e s s n e x t b a t c h
// p r o c e s s N e w B l o c k s ( r a n g e : S e l f . n e x t B a t c h B l o c k R a n g e ( l a t e s t H e i g h t : l a t e s t B l o c k H e i g h t , l a t e s t D o w n l o a d e d H e i g h t : t r y d o w n l o a d e r . l a s t D o w n l o a d e d B l o c k H e i g h t ( ) , w a l l e t B i r t h d a y : c o n f i g . w a l l e t B i r t h d a y ) )
2022-09-01 05:58:41 -07:00
nextBatch ( )
2021-09-17 06:49:58 -07:00
} catch {
self . fail ( error )
}
}
func mapError ( _ error : Error ) -> CompactBlockProcessorError {
if let processorError = error as ? CompactBlockProcessorError {
return processorError
}
if let lwdError = error as ? LightWalletServiceError {
return lwdError . mapToProcessorError ( )
} else if let rpcError = error as ? GRPC . GRPCStatus {
switch rpcError {
case . ok :
LoggerProxy . warn ( " Error Raised when status is OK " )
return CompactBlockProcessorError . grpcError (
statusCode : rpcError . code . rawValue ,
message : rpcError . message ? ? " Error Raised when status is OK "
)
default :
return CompactBlockProcessorError . grpcError ( statusCode : rpcError . code . rawValue , message : rpcError . message ? ? " No message " )
}
}
return . unspecifiedError ( underlyingError : error )
}
private func validateConfiguration ( ) throws {
guard FileManager . default . isReadableFile ( atPath : config . cacheDb . absoluteString ) else {
throw CompactBlockProcessorError . missingDbPath ( path : config . cacheDb . absoluteString )
}
guard FileManager . default . isReadableFile ( atPath : config . dataDb . absoluteString ) else {
throw CompactBlockProcessorError . missingDbPath ( path : config . dataDb . absoluteString )
}
}
private func nextBatch ( ) {
2022-09-01 05:58:41 -07:00
setState ( . downloading )
Task { @ MainActor [ self ] in
2022-08-23 12:58:15 -07:00
do {
let nextState = try await NextStateHelper . nextStateAsync (
service : self . service ,
downloader : self . downloader ,
config : self . config ,
rustBackend : self . rustBackend
)
switch nextState {
case . finishProcessing ( let height ) :
self . latestBlockHeight = height
self . processingFinished ( height : height )
case . processNewBlocks ( let range ) :
self . latestBlockHeight = range . upperBound
self . lowerBoundHeight = range . lowerBound
self . processNewBlocks ( range : range )
case let . wait ( latestHeight , latestDownloadHeight ) :
// L i g h t w a l l e t d m i g h t b e s y n c i n g
self . lowerBoundHeight = latestDownloadHeight
self . latestBlockHeight = latestHeight
LoggerProxy . info (
" Lightwalletd might be syncing: latest downloaded block height is: \( latestDownloadHeight ) " +
" while latest blockheight is reported at: \( latestHeight ) "
)
self . processingFinished ( height : latestDownloadHeight )
}
} catch {
self . severeFailure ( error )
}
}
}
2021-09-17 06:49:58 -07:00
2022-09-01 05:58:41 -07:00
internal func validationFailed ( at height : BlockHeight ) {
2019-11-14 06:38:54 -08:00
// c a n c e l a l l T a s k s
2022-09-01 05:58:41 -07:00
cancelableTask ? . cancel ( )
2020-12-05 10:11:29 -08:00
2019-11-14 06:38:54 -08:00
// r e g i s t e r l a t e s t f a i l u r e
self . lastChainValidationFailure = height
2021-09-15 05:21:29 -07:00
self . consecutiveChainValidationErrors += 1
2019-11-14 06:38:54 -08:00
// r e w i n d
2021-09-15 05:21:29 -07:00
let rewindHeight = determineLowerBound (
2021-09-17 06:49:58 -07:00
errorHeight : height ,
consecutiveErrors : consecutiveChainValidationErrors ,
walletBirthday : self . config . walletBirthday
)
2021-07-28 09:59:10 -07:00
guard rustBackend . rewindToHeight ( dbData : config . dataDb , height : Int32 ( rewindHeight ) , networkType : self . config . network . networkType ) else {
2019-11-14 06:38:54 -08:00
fail ( rustBackend . lastError ( ) ? ? RustWeldingError . genericError ( message : " unknown error rewinding to height \( height ) " ) )
return
}
do {
try downloader . rewind ( to : rewindHeight )
2019-12-19 05:06:15 -08:00
// n o t i f y r e o r g
2021-09-15 05:21:29 -07:00
NotificationCenter . default . post (
name : Notification . Name . blockProcessorHandledReOrg ,
object : self ,
userInfo : [
CompactBlockProcessorNotificationKey . reorgHeight : height , CompactBlockProcessorNotificationKey . rewindHeight : rewindHeight
]
)
2020-04-23 10:11:03 -07:00
2019-11-14 06:38:54 -08:00
// p r o c e s s n e x t b a t c h
2022-09-01 05:58:41 -07:00
self . nextBatch ( )
2019-11-14 06:38:54 -08:00
} catch {
self . fail ( error )
}
}
2021-09-17 06:49:58 -07:00
2022-09-01 05:58:41 -07:00
internal func processBatchFinished ( range : CompactBlockRange ) {
2019-10-18 11:45:19 -07:00
guard processingError = = nil else {
2019-10-30 13:18:57 -07:00
retryProcessing ( range : range )
2019-10-18 11:45:19 -07:00
return
}
2019-11-14 06:38:54 -08:00
2019-10-18 11:45:19 -07:00
retryAttempts = 0
2019-11-14 06:38:54 -08:00
consecutiveChainValidationErrors = 0
2019-10-31 10:33:21 -07:00
2019-10-30 13:18:57 -07:00
guard ! range . isEmpty else {
2019-12-16 14:25:45 -08:00
processingFinished ( height : range . upperBound )
2019-10-18 11:45:19 -07:00
return
}
2022-09-01 05:58:41 -07:00
nextBatch ( )
2019-10-30 13:18:57 -07:00
}
2019-12-16 14:25:45 -08:00
private func processingFinished ( height : BlockHeight ) {
2021-09-17 06:49:58 -07:00
NotificationCenter . default . post (
name : Notification . Name . blockProcessorFinished ,
object : self ,
userInfo : [
CompactBlockProcessorNotificationKey . latestScannedBlockHeight : height ,
CompactBlockProcessorNotificationKey . foundBlocks : self . foundBlocks
]
)
2022-09-01 05:58:41 -07:00
setState ( . synced )
2021-09-17 06:49:58 -07:00
setTimer ( )
2020-03-11 19:17:32 -07:00
}
private func setTimer ( ) {
2020-02-26 08:54:48 -08:00
let interval = self . config . blockPollInterval
2020-03-11 19:17:32 -07:00
self . backoffTimer ? . invalidate ( )
2021-09-17 06:49:58 -07:00
let timer = Timer (
timeInterval : interval ,
repeats : true ,
block : { [ weak self ] _ in
guard let self = self else { return }
2020-04-23 10:11:03 -07:00
do {
if self . shouldStart {
2021-09-15 05:21:29 -07:00
LoggerProxy . debug (
" " "
Timer triggered : Starting compact Block processor ! .
Processor State : \ ( self . state )
latestHeight : \ ( self . latestBlockHeight )
attempts : \ ( self . retryAttempts )
lowerbound : \ ( String ( describing : self . lowerBoundHeight ) )
" " "
)
2020-04-23 10:11:03 -07:00
try self . start ( )
} else if self . maxAttemptsReached {
self . fail ( CompactBlockProcessorError . maxAttemptsReached ( attempts : self . config . retries ) )
}
2020-02-26 08:54:48 -08:00
} catch {
self . fail ( error )
}
2021-09-17 06:49:58 -07:00
}
)
2020-02-26 08:54:48 -08:00
RunLoop . main . add ( timer , forMode : . default )
self . backoffTimer = timer
2019-10-18 11:45:19 -07:00
}
2019-10-30 13:18:57 -07:00
private func transitionState ( from oldValue : State , to newValue : State ) {
guard oldValue != newValue else {
return
}
2021-09-17 06:49:58 -07:00
NotificationCenter . default . post (
name : . blockProcessorStatusChanged ,
object : self ,
userInfo : [
CompactBlockProcessorNotificationKey . previousStatus : oldValue ,
CompactBlockProcessorNotificationKey . newStatus : newValue
]
)
2019-10-30 13:18:57 -07:00
switch newValue {
case . downloading :
NotificationCenter . default . post ( name : Notification . Name . blockProcessorStartedDownloading , object : self )
case . synced :
2022-07-29 11:20:55 -07:00
// t r a n s i t i o n t o t h i s s t a t e i s h a n d l e d b y ` p r o c e s s i n g F i n i s h e d ( h e i g h t : B l o c k H e i g h t ) `
break
2019-10-30 13:18:57 -07:00
case . error ( let err ) :
2020-04-23 10:11:03 -07:00
notifyError ( err )
2019-10-30 13:18:57 -07:00
case . scanning :
NotificationCenter . default . post ( name : Notification . Name . blockProcessorStartedScanning , object : self )
case . stopped :
NotificationCenter . default . post ( name : Notification . Name . blockProcessorStopped , object : self )
case . validating :
NotificationCenter . default . post ( name : Notification . Name . blockProcessorStartedValidating , object : self )
2021-06-02 14:32:05 -07:00
case . enhancing :
NotificationCenter . default . post ( name : Notification . Name . blockProcessorStartedEnhancing , object : self )
case . fetching :
2021-06-07 16:00:33 -07:00
NotificationCenter . default . post ( name : Notification . Name . blockProcessorStartedFetching , object : self )
2019-10-30 13:18:57 -07:00
}
}
2021-09-17 06:49:58 -07:00
2020-03-13 17:00:01 -07:00
private func notifyError ( _ err : Error ) {
2021-09-17 06:49:58 -07:00
NotificationCenter . default . post (
name : Notification . Name . blockProcessorFailed ,
object : self ,
userInfo : [ CompactBlockProcessorNotificationKey . error : mapError ( err ) ]
)
2020-03-13 17:00:01 -07:00
}
// TODO: e n c a p s u l a t e s e r v i c e e r r o r s b e t t e r
2019-10-18 11:45:19 -07:00
}
2019-11-04 15:18:07 -08:00
public extension CompactBlockProcessor . Configuration {
2020-02-26 08:54:48 -08:00
/* *
2021-09-17 06:49:58 -07:00
Standard configuration for most compact block processors
*/
2021-07-28 09:59:10 -07:00
static func standard ( for network : ZcashNetwork , walletBirthday : BlockHeight ) -> CompactBlockProcessor . Configuration {
let pathProvider = DefaultResourceProvider ( network : network )
2021-09-17 06:49:58 -07:00
return CompactBlockProcessor . Configuration (
cacheDb : pathProvider . cacheDbURL ,
dataDb : pathProvider . dataDbURL ,
walletBirthday : walletBirthday ,
network : network
)
2019-10-30 13:18:57 -07:00
}
}
2020-08-10 15:19:59 -07:00
extension LightWalletServiceError {
func mapToProcessorError ( ) -> CompactBlockProcessorError {
switch self {
2021-09-17 06:49:58 -07:00
case let . failed ( statusCode , message ) :
2020-08-10 15:19:59 -07:00
return CompactBlockProcessorError . grpcError ( statusCode : statusCode , message : message )
case . invalidBlock :
return CompactBlockProcessorError . generalError ( message : " \( self ) " )
case . generalError ( let message ) :
return CompactBlockProcessorError . generalError ( message : message )
case . sentFailed ( let error ) :
return CompactBlockProcessorError . connectionError ( underlyingError : error )
case . genericError ( let error ) :
return CompactBlockProcessorError . unspecifiedError ( underlyingError : error )
case . timeOut :
return CompactBlockProcessorError . connectionTimeout
case . criticalError :
return CompactBlockProcessorError . criticalError
case . userCancelled :
return CompactBlockProcessorError . connectionTimeout
case . unknown :
return CompactBlockProcessorError . unspecifiedError ( underlyingError : self )
}
}
}
2019-10-30 13:18:57 -07:00
extension CompactBlockProcessor . State : Equatable {
2019-11-04 15:18:07 -08:00
public static func = = ( lhs : CompactBlockProcessor . State , rhs : CompactBlockProcessor . State ) -> Bool {
2020-12-06 22:37:02 -08:00
switch ( lhs , rhs ) {
2021-09-17 06:49:58 -07:00
case
( . downloading , . downloading ) ,
( . scanning , . scanning ) ,
( . validating , . validating ) ,
( . stopped , . stopped ) ,
( . error , . error ) ,
( . synced , . synced ) ,
( . enhancing , enhancing ) ,
( . fetching , . fetching ) :
return true
default :
return false
2019-10-30 13:18:57 -07:00
}
2019-10-18 11:45:19 -07:00
}
}
2021-03-08 10:47:36 -08:00
2021-04-08 10:18:16 -07:00
extension CompactBlockProcessor {
2022-10-02 19:11:17 -07:00
public func getUnifiedAddress ( accountIndex : Int ) -> UnifiedAddress ? {
try ? rustBackend . getCurrentAddress (
dbData : config . dataDb ,
account : Int32 ( accountIndex ) ,
networkType : config . network . networkType
)
2021-04-08 10:18:16 -07:00
}
2022-08-20 15:10:22 -07:00
public func getSaplingAddress ( accountIndex : Int ) -> SaplingAddress ? {
2022-10-02 19:11:17 -07:00
getUnifiedAddress ( accountIndex : accountIndex ) ? . saplingReceiver ( )
2021-04-08 10:18:16 -07:00
}
public func getTransparentAddress ( accountIndex : Int ) -> TransparentAddress ? {
2022-10-02 19:11:17 -07:00
getUnifiedAddress ( accountIndex : accountIndex ) ? . transparentReceiver ( )
2021-04-08 10:18:16 -07:00
}
public func getTransparentBalance ( accountIndex : Int ) throws -> WalletBalance {
2022-10-02 19:11:17 -07:00
guard accountIndex >= 0 else {
2021-04-08 10:18:16 -07:00
throw CompactBlockProcessorError . invalidAccount
}
2022-10-02 19:11:17 -07:00
return WalletBalance (
verified : Zatoshi (
try rustBackend . getVerifiedTransparentBalance (
dbData : config . dataDb ,
account : Int32 ( accountIndex ) ,
networkType : config . network . networkType )
) ,
total : Zatoshi (
try rustBackend . getTransparentBalance (
dbData : config . dataDb ,
account : Int32 ( accountIndex ) ,
networkType : config . network . networkType
)
)
)
2021-04-08 10:18:16 -07:00
}
}
extension CompactBlockProcessor {
2022-10-02 19:11:17 -07:00
func refreshUTXOs ( tAddress : TransparentAddress , startHeight : BlockHeight ) async throws -> RefreshedUTXOs {
2021-04-08 10:18:16 -07:00
let dataDb = self . config . dataDb
2022-09-01 05:58:41 -07:00
2022-10-02 19:11:17 -07:00
let stream : AsyncThrowingStream < UnspentTransactionOutputEntity , Error > = downloader . fetchUnspentTransactionOutputs ( tAddress : tAddress . stringEncoded , startHeight : startHeight )
2022-09-01 05:58:41 -07:00
var utxos : [ UnspentTransactionOutputEntity ] = [ ]
do {
for try await utxo in stream {
utxos . append ( utxo )
2021-04-08 10:18:16 -07:00
}
2022-09-01 05:58:41 -07:00
guard try rustBackend . clearUtxos (
dbData : dataDb ,
address : tAddress ,
sinceHeight : startHeight - 1 ,
networkType : self . config . network . networkType
) >= 0 else {
throw CompactBlockProcessorError . generalError ( message : " attempted to clear utxos but -1 was returned " )
}
return storeUTXOs ( utxos , in : dataDb )
} catch {
throw mapError ( error )
2021-04-08 10:18:16 -07:00
}
}
2021-09-17 06:49:58 -07:00
private func storeUTXOs ( _ utxos : [ UnspentTransactionOutputEntity ] , in dataDb : URL ) -> RefreshedUTXOs {
var refreshed : [ UnspentTransactionOutputEntity ] = [ ]
var skipped : [ UnspentTransactionOutputEntity ] = [ ]
2021-04-08 10:18:16 -07:00
for utxo in utxos {
do {
try self . rustBackend . putUnspentTransparentOutput (
dbData : dataDb ,
txid : utxo . txid . bytes ,
index : utxo . index ,
script : utxo . script . bytes ,
value : Int64 ( utxo . valueZat ) ,
2021-07-26 16:22:30 -07:00
height : utxo . height ,
2021-09-15 05:21:29 -07:00
networkType : self . config . network . networkType
) ? refreshed . append ( utxo ) : skipped . append ( utxo )
2021-04-08 10:18:16 -07:00
} catch {
LoggerProxy . info ( " failed to put utxo - error: \( error ) " )
skipped . append ( utxo )
}
}
return ( inserted : refreshed , skipped : skipped )
}
}
2021-05-17 14:14:59 -07:00
extension CompactBlockProcessorError : LocalizedError {
// / A l o c a l i z e d m e s s a g e d e s c r i b i n g w h a t e r r o r o c c u r r e d .
public var errorDescription : String ? {
switch self {
case . dataDbInitFailed ( let path ) :
return " Data Db file couldn't be initialized at path: \( path ) "
case . connectionError ( let underlyingError ) :
return " There's a problem with the Network Connection. Underlying error: \( underlyingError . localizedDescription ) "
case . connectionTimeout :
return " Network connection timeout "
case . criticalError :
return " Critical Error "
case . generalError ( let message ) :
return " Error Processing Blocks - \( message ) "
2021-09-17 06:49:58 -07:00
case let . grpcError ( statusCode , message ) :
2021-05-17 14:14:59 -07:00
return " Error on gRPC - Status Code: \( statusCode ) - Message: \( message ) "
case . invalidAccount :
return " Invalid Account "
case . invalidConfiguration :
return " CompactBlockProcessor was started with an Invalid Configuration "
case . maxAttemptsReached ( let attempts ) :
return " Compact Block failed \( attempts ) times and reached the maximum amount of retries it was set up to do "
case . missingDbPath ( let path ) :
return " CompactBlockProcessor was set up with path \( path ) but thath location couldn't be reached "
2021-09-17 06:49:58 -07:00
case let . networkMismatch ( expected , found ) :
2021-09-15 05:21:29 -07:00
// s w i f t l i n t : d i s a b l e : n e x t l i n e _ l e n g t h
2021-05-17 14:14:59 -07:00
return " A server was reached, but it's targeting the wrong network Type. App Expected \( expected ) but found \( found ) . Make sure you are pointing to the right server "
2021-09-17 06:49:58 -07:00
case let . saplingActivationMismatch ( expected , found ) :
2021-09-15 05:21:29 -07:00
// s w i f t l i n t : d i s a b l e : n e x t l i n e _ l e n g t h
2021-05-17 14:14:59 -07:00
return " A server was reached, it's showing a different sapling activation. App expected sapling activation height to be \( expected ) but instead it found \( found ) . Are you sure you are pointing to the right server? "
case . unspecifiedError ( let underlyingError ) :
return " Unspecified error caused by this underlying error: \( underlyingError ) "
2021-09-17 06:49:58 -07:00
case let . wrongConsensusBranchId ( expectedLocally , found ) :
2021-09-15 05:21:29 -07:00
// s w i f t l i n t : d i s a b l e : n e x t l i n e _ l e n g t h
2021-05-17 14:14:59 -07:00
return " The remote server you are connecting to is publishing a different branch ID \( found ) than the one your App is expecting to be ( \( expectedLocally ) ). This could be caused by your App being out of date or the server you are connecting you being either on a different network or out of date after a network upgrade. "
2022-09-01 05:58:41 -07:00
case . unknown : return " Unknown error occured. "
2021-05-17 14:14:59 -07:00
}
}
// / A l o c a l i z e d m e s s a g e d e s c r i b i n g t h e r e a s o n f o r t h e f a i l u r e .
public var failureReason : String ? {
self . localizedDescription
}
// / A l o c a l i z e d m e s s a g e d e s c r i b i n g h o w o n e m i g h t r e c o v e r f r o m t h e f a i l u r e .
public var recoverySuggestion : String ? {
self . localizedDescription
}
// / A l o c a l i z e d m e s s a g e p r o v i d i n g " h e l p " t e x t i f t h e u s e r r e q u e s t s h e l p .
public var helpAnchor : String ? {
self . localizedDescription
}
}
2021-06-02 14:32:05 -07:00
extension CompactBlockProcessor : EnhancementStreamDelegate {
2021-06-07 16:00:33 -07:00
func transactionEnhancementProgressUpdated ( _ progress : EnhancementProgress ) {
2021-09-17 06:49:58 -07:00
NotificationCenter . default . post (
name : . blockProcessorEnhancementProgress ,
object : self ,
userInfo : [ CompactBlockProcessorNotificationKey . enhancementProgress : progress ]
)
2021-06-02 14:32:05 -07:00
}
}
2021-06-19 16:40:45 -07:00
extension CompactBlockProcessor {
2021-09-15 05:21:29 -07:00
enum NextStateHelper {
2022-08-23 12:58:15 -07:00
static func nextStateAsync (
service : LightWalletService ,
downloader : CompactBlockDownloading ,
config : Configuration ,
rustBackend : ZcashRustBackendWelding . Type
2022-09-01 05:58:41 -07:00
) async throws -> NextState {
2022-08-23 12:58:15 -07:00
let task = Task ( priority : . userInitiated ) {
2022-10-03 16:05:11 -07:00
do {
let info = try await service . getInfo ( )
try CompactBlockProcessor . validateServerInfo (
info ,
saplingActivation : config . saplingActivation ,
localNetwork : config . network ,
rustBackend : rustBackend
2021-09-17 06:49:58 -07:00
)
2022-10-03 16:05:11 -07:00
// g e t l a t e s t b l o c k h e i g h t
let latestDownloadedBlockHeight : BlockHeight = max ( config . walletBirthday , try downloader . lastDownloadedBlockHeight ( ) )
let latestBlockheight = try service . latestBlockHeight ( )
if latestDownloadedBlockHeight < latestBlockheight {
return NextState . processNewBlocks (
range : CompactBlockProcessor . nextBatchBlockRange (
latestHeight : latestBlockheight ,
latestDownloadedHeight : latestDownloadedBlockHeight ,
walletBirthday : config . walletBirthday
)
)
} else if latestBlockheight = = latestDownloadedBlockHeight {
return . finishProcessing ( height : latestBlockheight )
}
return . wait ( latestHeight : latestBlockheight , latestDownloadHeight : latestBlockheight )
} catch {
throw error
}
2021-06-19 16:40:45 -07:00
}
2022-10-03 16:05:11 -07:00
return try await task . value
2021-06-19 16:40:45 -07:00
}
}
}