react-native-zcash 0.6.10 → 0.6.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/android/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/2460000.json +8 -0
- package/ios/ZCashLightClientKit/Block/CompactBlockProcessor.swift +937 -425
- package/ios/ZCashLightClientKit/Block/Download/BlockDownloader.swift +17 -31
- package/ios/ZCashLightClientKit/Block/Download/BlockDownloaderService.swift +2 -2
- package/ios/ZCashLightClientKit/Block/Enhance/BlockEnhancer.swift +15 -46
- package/ios/ZCashLightClientKit/Block/FetchUnspentTxOutputs/UTXOFetcher.swift +15 -4
- package/ios/ZCashLightClientKit/Block/FilesystemStorage/FSCompactBlockRepository.swift +4 -4
- package/ios/ZCashLightClientKit/Block/Scan/BlockScanner.swift +35 -10
- package/ios/ZCashLightClientKit/Block/Utils/InternalSyncProgress.swift +200 -0
- package/ios/ZCashLightClientKit/Block/Validate/BlockValidator.swift +51 -0
- package/ios/ZCashLightClientKit/ClosureSynchronizer.swift +2 -1
- package/ios/ZCashLightClientKit/CombineSynchronizer.swift +5 -2
- package/ios/ZCashLightClientKit/Constants/ZcashSDK.swift +26 -13
- package/ios/ZCashLightClientKit/DAO/BlockDao.swift +112 -0
- package/ios/ZCashLightClientKit/DAO/TransactionDao.swift +42 -40
- package/ios/ZCashLightClientKit/DAO/UnspentTransactionOutputDao.swift +4 -13
- package/ios/ZCashLightClientKit/Entity/AccountEntity.swift +0 -9
- package/ios/ZCashLightClientKit/Entity/BlockProgress.swift +24 -0
- package/ios/ZCashLightClientKit/Entity/TransactionEntity.swift +10 -7
- package/ios/ZCashLightClientKit/Error/Sourcery/generateErrorCode.sh +1 -1
- package/ios/ZCashLightClientKit/Error/ZcashError.swift +12 -121
- package/ios/ZCashLightClientKit/Error/ZcashErrorCode.swift +5 -43
- package/ios/ZCashLightClientKit/Error/ZcashErrorCodeDefinition.swift +6 -72
- package/ios/ZCashLightClientKit/Initializer.swift +26 -47
- package/ios/ZCashLightClientKit/Metrics/SDKMetrics.swift +12 -0
- package/ios/ZCashLightClientKit/Model/Checkpoint.swift +0 -12
- package/ios/ZCashLightClientKit/Modules/Service/GRPC/LightWalletGRPCService.swift +0 -15
- package/ios/ZCashLightClientKit/Modules/Service/GRPC/ProtoBuf/compact_formats.pb.swift +46 -150
- package/ios/ZCashLightClientKit/Modules/Service/GRPC/ProtoBuf/proto/compact_formats.proto +16 -30
- package/ios/ZCashLightClientKit/Modules/Service/GRPC/ProtoBuf/proto/service.proto +6 -32
- package/ios/ZCashLightClientKit/Modules/Service/GRPC/ProtoBuf/service.grpc.swift +22 -259
- package/ios/ZCashLightClientKit/Modules/Service/GRPC/ProtoBuf/service.pb.swift +7 -193
- package/ios/ZCashLightClientKit/Modules/Service/LightWalletService.swift +0 -8
- package/ios/ZCashLightClientKit/Providers/LatestBlocksDataProvider.swift +28 -18
- package/ios/ZCashLightClientKit/Repository/CompactBlockRepository.swift +1 -1
- package/ios/ZCashLightClientKit/Repository/TransactionRepository.swift +6 -2
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2460000.json +8 -0
- package/ios/ZCashLightClientKit/Rust/ZcashRustBackend.swift +158 -293
- package/ios/ZCashLightClientKit/Rust/ZcashRustBackendWelding.swift +64 -58
- package/ios/ZCashLightClientKit/Rust/zcashlc.h +513 -619
- package/ios/ZCashLightClientKit/Synchronizer/ClosureSDKSynchronizer.swift +8 -2
- package/ios/ZCashLightClientKit/Synchronizer/CombineSDKSynchronizer.swift +15 -3
- package/ios/ZCashLightClientKit/Synchronizer/Dependencies.swift +30 -11
- package/ios/ZCashLightClientKit/Synchronizer/SDKSynchronizer.swift +50 -41
- package/ios/ZCashLightClientKit/Synchronizer.swift +65 -51
- package/ios/ZCashLightClientKit/Transaction/TransactionEncoder.swift +2 -2
- package/ios/ZCashLightClientKit/Transaction/WalletTransactionEncoder.swift +7 -7
- package/ios/ZCashLightClientKit/Utils/OSLogger.swift +3 -3
- package/ios/libzcashlc.xcframework/Info.plist +0 -4
- package/ios/libzcashlc.xcframework/ios-arm64/libzcashlc.a +0 -0
- package/ios/libzcashlc.xcframework/ios-arm64_x86_64-simulator/libzcashlc.a +0 -0
- package/package.json +1 -1
- package/ios/ZCashLightClientKit/Block/Actions/Action.swift +0 -98
- package/ios/ZCashLightClientKit/Block/Actions/ClearAlreadyScannedBlocksAction.swift +0 -35
- package/ios/ZCashLightClientKit/Block/Actions/ClearCacheAction.swift +0 -30
- package/ios/ZCashLightClientKit/Block/Actions/DownloadAction.swift +0 -67
- package/ios/ZCashLightClientKit/Block/Actions/EnhanceAction.swift +0 -97
- package/ios/ZCashLightClientKit/Block/Actions/FetchUTXOsAction.swift +0 -33
- package/ios/ZCashLightClientKit/Block/Actions/MigrateLegacyCacheDBAction.swift +0 -70
- package/ios/ZCashLightClientKit/Block/Actions/ProcessSuggestedScanRangesAction.swift +0 -60
- package/ios/ZCashLightClientKit/Block/Actions/RewindAction.swift +0 -48
- package/ios/ZCashLightClientKit/Block/Actions/SaplingParamsAction.swift +0 -33
- package/ios/ZCashLightClientKit/Block/Actions/ScanAction.swift +0 -95
- package/ios/ZCashLightClientKit/Block/Actions/UpdateChainTipAction.swift +0 -55
- package/ios/ZCashLightClientKit/Block/Actions/UpdateSubtreeRootsAction.swift +0 -58
- package/ios/ZCashLightClientKit/Block/Actions/ValidateServerAction.swift +0 -60
- package/ios/ZCashLightClientKit/Block/Utils/CompactBlockProgress.swift +0 -24
- package/ios/ZCashLightClientKit/Block/Utils/SyncControlData.swift +0 -25
- package/ios/ZCashLightClientKit/Extensions/Bool+ToData.swift +0 -15
- package/ios/ZCashLightClientKit/Extensions/Data+ToOtherTypes.swift +0 -18
- package/ios/ZCashLightClientKit/Extensions/Int+ToData.swift +0 -15
- package/ios/ZCashLightClientKit/Model/ScanProgress.swift +0 -29
- package/ios/ZCashLightClientKit/Model/ScanRange.swift +0 -31
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2092500.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2095000.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2097500.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2102500.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2105000.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2107500.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2112500.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2115000.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2117500.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2122500.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2125000.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2127500.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2132500.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2135000.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2137500.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2142500.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2145000.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2147500.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2152500.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2155000.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2157500.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2162500.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2165000.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2167500.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2172500.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2175000.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2177500.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2182500.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2185000.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2187500.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2192500.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2195000.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2197500.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2202500.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2205000.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2207500.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2212500.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2215000.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2217500.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2222500.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2225000.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2227500.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2232500.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2235000.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2237500.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2242500.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2245000.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2247500.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2252500.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2255000.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2257500.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2262500.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2265000.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2267500.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2350000.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2360000.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2370000.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2380000.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2390000.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2400000.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2410000.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2420000.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2430000.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2440000.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2450000.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2460000.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2470000.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2480000.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2490000.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2500000.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2510000.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2520000.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2530000.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2540000.json +0 -8
- package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2550000.json +0 -8
- package/ios/ZCashLightClientKit/Utils/ZcashFileManager.swift +0 -16
|
@@ -5,46 +5,144 @@
|
|
|
5
5
|
// Created by Francisco Gindre on 18/09/2019.
|
|
6
6
|
// Copyright © 2019 Electric Coin Company. All rights reserved.
|
|
7
7
|
//
|
|
8
|
+
// swiftlint:disable file_length type_body_length
|
|
8
9
|
|
|
9
10
|
import Foundation
|
|
10
11
|
import Combine
|
|
11
12
|
|
|
12
13
|
public typealias RefreshedUTXOs = (inserted: [UnspentTransactionOutputEntity], skipped: [UnspentTransactionOutputEntity])
|
|
13
14
|
|
|
15
|
+
public enum CompactBlockProgress {
|
|
16
|
+
case syncing(_ progress: BlockProgress)
|
|
17
|
+
case enhance(_ progress: EnhancementProgress)
|
|
18
|
+
case fetch(_ progress: Float)
|
|
19
|
+
|
|
20
|
+
public var progress: Float {
|
|
21
|
+
switch self {
|
|
22
|
+
case .syncing(let blockProgress):
|
|
23
|
+
return blockProgress.progress
|
|
24
|
+
case .enhance(let enhancementProgress):
|
|
25
|
+
return enhancementProgress.progress
|
|
26
|
+
case .fetch(let fetchingProgress):
|
|
27
|
+
return fetchingProgress
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public var progressHeight: BlockHeight? {
|
|
32
|
+
switch self {
|
|
33
|
+
case .syncing(let blockProgress):
|
|
34
|
+
return blockProgress.progressHeight
|
|
35
|
+
case .enhance(let enhancementProgress):
|
|
36
|
+
return enhancementProgress.lastFoundTransaction?.minedHeight
|
|
37
|
+
default:
|
|
38
|
+
return 0
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public var blockDate: Date? {
|
|
43
|
+
if case .enhance(let enhancementProgress) = self, let time = enhancementProgress.lastFoundTransaction?.blockTime {
|
|
44
|
+
return Date(timeIntervalSince1970: time)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return nil
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public var targetHeight: BlockHeight? {
|
|
51
|
+
switch self {
|
|
52
|
+
case .syncing(let blockProgress):
|
|
53
|
+
return blockProgress.targetHeight
|
|
54
|
+
default:
|
|
55
|
+
return nil
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
public struct EnhancementProgress: Equatable {
|
|
61
|
+
/// total transactions that were detected in the `range`
|
|
62
|
+
public let totalTransactions: Int
|
|
63
|
+
/// enhanced transactions so far
|
|
64
|
+
public let enhancedTransactions: Int
|
|
65
|
+
/// last found transaction
|
|
66
|
+
public let lastFoundTransaction: ZcashTransaction.Overview?
|
|
67
|
+
/// block range that's being enhanced
|
|
68
|
+
public let range: CompactBlockRange
|
|
69
|
+
/// whether this transaction can be considered `newly mined` and not part of the
|
|
70
|
+
/// wallet catching up to stale and uneventful blocks.
|
|
71
|
+
public let newlyMined: Bool
|
|
72
|
+
|
|
73
|
+
public init(
|
|
74
|
+
totalTransactions: Int,
|
|
75
|
+
enhancedTransactions: Int,
|
|
76
|
+
lastFoundTransaction: ZcashTransaction.Overview?,
|
|
77
|
+
range: CompactBlockRange,
|
|
78
|
+
newlyMined: Bool
|
|
79
|
+
) {
|
|
80
|
+
self.totalTransactions = totalTransactions
|
|
81
|
+
self.enhancedTransactions = enhancedTransactions
|
|
82
|
+
self.lastFoundTransaction = lastFoundTransaction
|
|
83
|
+
self.range = range
|
|
84
|
+
self.newlyMined = newlyMined
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public var progress: Float {
|
|
88
|
+
totalTransactions > 0 ? Float(enhancedTransactions) / Float(totalTransactions) : 0
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
public static var zero: EnhancementProgress {
|
|
92
|
+
EnhancementProgress(totalTransactions: 0, enhancedTransactions: 0, lastFoundTransaction: nil, range: 0...0, newlyMined: false)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
public static func == (lhs: EnhancementProgress, rhs: EnhancementProgress) -> Bool {
|
|
96
|
+
return
|
|
97
|
+
lhs.totalTransactions == rhs.totalTransactions &&
|
|
98
|
+
lhs.enhancedTransactions == rhs.enhancedTransactions &&
|
|
99
|
+
lhs.lastFoundTransaction?.id == rhs.lastFoundTransaction?.id &&
|
|
100
|
+
lhs.range == rhs.range
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
14
104
|
/// The compact block processor is in charge of orchestrating the download and caching of compact blocks from a LightWalletEndpoint
|
|
15
105
|
/// when started the processor downloads does a download - validate - scan cycle until it reaches latest height on the blockchain.
|
|
16
106
|
actor CompactBlockProcessor {
|
|
17
|
-
|
|
18
|
-
// and that is one provided by `SDKSynchronizer`. But while running tests more "subscribers" is required here. Therefore it's required to handle
|
|
19
|
-
// more closures here.
|
|
20
|
-
private var eventClosures: [String: EventClosure] = [:]
|
|
107
|
+
typealias EventClosure = (Event) async -> Void
|
|
21
108
|
|
|
22
|
-
|
|
109
|
+
enum Event {
|
|
110
|
+
/// Event sent when the CompactBlockProcessor presented an error.
|
|
111
|
+
case failed (Error)
|
|
23
112
|
|
|
24
|
-
|
|
25
|
-
|
|
113
|
+
/// Event sent when the CompactBlockProcessor has finished syncing the blockchain to latest height
|
|
114
|
+
case finished (_ lastScannedHeight: BlockHeight, _ foundBlocks: Bool)
|
|
26
115
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
private var afterSyncHooksManager = AfterSyncHooksManager()
|
|
116
|
+
/// Event sent when the CompactBlockProcessor found a newly mined transaction
|
|
117
|
+
case minedTransaction(ZcashTransaction.Overview)
|
|
30
118
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
119
|
+
/// Event sent when the CompactBlockProcessor enhanced a bunch of transactions in some range.
|
|
120
|
+
case foundTransactions ([ZcashTransaction.Overview], CompactBlockRange)
|
|
121
|
+
|
|
122
|
+
/// Event sent when the CompactBlockProcessor handled a ReOrg.
|
|
123
|
+
/// `reorgHeight` is the height on which the reorg was detected.
|
|
124
|
+
/// `rewindHeight` is the height that the processor backed to in order to solve the Reorg.
|
|
125
|
+
case handledReorg (_ reorgHeight: BlockHeight, _ rewindHeight: BlockHeight)
|
|
126
|
+
|
|
127
|
+
/// Event sent when progress of the sync process changes.
|
|
128
|
+
case progressUpdated (CompactBlockProgress)
|
|
129
|
+
|
|
130
|
+
/// Event sent when the CompactBlockProcessor fetched utxos from lightwalletd attempted to store them.
|
|
131
|
+
case storedUTXOs ((inserted: [UnspentTransactionOutputEntity], skipped: [UnspentTransactionOutputEntity]))
|
|
132
|
+
|
|
133
|
+
/// Event sent when the CompactBlockProcessor starts enhancing of the transactions.
|
|
134
|
+
case startedEnhancing
|
|
135
|
+
|
|
136
|
+
/// Event sent when the CompactBlockProcessor starts fetching of the UTXOs.
|
|
137
|
+
case startedFetching
|
|
138
|
+
|
|
139
|
+
/// Event sent when the CompactBlockProcessor starts syncing.
|
|
140
|
+
case startedSyncing
|
|
141
|
+
|
|
142
|
+
/// Event sent when the CompactBlockProcessor stops syncing.
|
|
143
|
+
case stopped
|
|
144
|
+
}
|
|
41
145
|
|
|
42
|
-
private var retryAttempts: Int = 0
|
|
43
|
-
private var backoffTimer: Timer?
|
|
44
|
-
private var consecutiveChainValidationErrors: Int = 0
|
|
45
|
-
|
|
46
|
-
private var compactBlockProgress: CompactBlockProgress = .zero
|
|
47
|
-
|
|
48
146
|
/// Compact Block Processor configuration
|
|
49
147
|
///
|
|
50
148
|
/// - parameter fsBlockCacheRoot: absolute root path where the filesystem block cache will be stored.
|
|
@@ -58,8 +156,8 @@ actor CompactBlockProcessor {
|
|
|
58
156
|
let dataDb: URL
|
|
59
157
|
let spendParamsURL: URL
|
|
60
158
|
let outputParamsURL: URL
|
|
61
|
-
let
|
|
62
|
-
let
|
|
159
|
+
let downloadBatchSize: Int
|
|
160
|
+
let scanningBatchSize: Int
|
|
63
161
|
let retries: Int
|
|
64
162
|
let maxBackoffInterval: TimeInterval
|
|
65
163
|
let maxReorgSize = ZcashSDK.maxReorgSize
|
|
@@ -73,7 +171,7 @@ actor CompactBlockProcessor {
|
|
|
73
171
|
var blockPollInterval: TimeInterval {
|
|
74
172
|
TimeInterval.random(in: ZcashSDK.defaultPollInterval / 2 ... ZcashSDK.defaultPollInterval * 1.5)
|
|
75
173
|
}
|
|
76
|
-
|
|
174
|
+
|
|
77
175
|
init(
|
|
78
176
|
alias: ZcashSynchronizerAlias,
|
|
79
177
|
cacheDbURL: URL? = nil,
|
|
@@ -82,11 +180,11 @@ actor CompactBlockProcessor {
|
|
|
82
180
|
spendParamsURL: URL,
|
|
83
181
|
outputParamsURL: URL,
|
|
84
182
|
saplingParamsSourceURL: SaplingParamsSourceURL,
|
|
85
|
-
|
|
86
|
-
batchSize: Int = ZcashSDK.DefaultBatchSize,
|
|
183
|
+
downloadBatchSize: Int = ZcashSDK.DefaultDownloadBatch,
|
|
87
184
|
retries: Int = ZcashSDK.defaultRetries,
|
|
88
185
|
maxBackoffInterval: TimeInterval = ZcashSDK.defaultMaxBackOffInterval,
|
|
89
186
|
rewindDistance: Int = ZcashSDK.defaultRewindDistance,
|
|
187
|
+
scanningBatchSize: Int = ZcashSDK.DefaultScanningBatch,
|
|
90
188
|
walletBirthdayProvider: @escaping () -> BlockHeight,
|
|
91
189
|
saplingActivation: BlockHeight,
|
|
92
190
|
network: ZcashNetwork
|
|
@@ -98,16 +196,17 @@ actor CompactBlockProcessor {
|
|
|
98
196
|
self.outputParamsURL = outputParamsURL
|
|
99
197
|
self.saplingParamsSourceURL = saplingParamsSourceURL
|
|
100
198
|
self.network = network
|
|
101
|
-
self.
|
|
102
|
-
self.batchSize = batchSize
|
|
199
|
+
self.downloadBatchSize = downloadBatchSize
|
|
103
200
|
self.retries = retries
|
|
104
201
|
self.maxBackoffInterval = maxBackoffInterval
|
|
105
202
|
self.rewindDistance = rewindDistance
|
|
203
|
+
self.scanningBatchSize = scanningBatchSize
|
|
106
204
|
self.walletBirthdayProvider = walletBirthdayProvider
|
|
107
205
|
self.saplingActivation = saplingActivation
|
|
108
206
|
self.cacheDbURL = cacheDbURL
|
|
207
|
+
assert(downloadBatchSize >= scanningBatchSize)
|
|
109
208
|
}
|
|
110
|
-
|
|
209
|
+
|
|
111
210
|
init(
|
|
112
211
|
alias: ZcashSynchronizerAlias,
|
|
113
212
|
fsBlockCacheRoot: URL,
|
|
@@ -115,11 +214,11 @@ actor CompactBlockProcessor {
|
|
|
115
214
|
spendParamsURL: URL,
|
|
116
215
|
outputParamsURL: URL,
|
|
117
216
|
saplingParamsSourceURL: SaplingParamsSourceURL,
|
|
118
|
-
|
|
119
|
-
batchSize: Int = ZcashSDK.DefaultBatchSize,
|
|
217
|
+
downloadBatchSize: Int = ZcashSDK.DefaultDownloadBatch,
|
|
120
218
|
retries: Int = ZcashSDK.defaultRetries,
|
|
121
219
|
maxBackoffInterval: TimeInterval = ZcashSDK.defaultMaxBackOffInterval,
|
|
122
220
|
rewindDistance: Int = ZcashSDK.defaultRewindDistance,
|
|
221
|
+
scanningBatchSize: Int = ZcashSDK.DefaultScanningBatch,
|
|
123
222
|
walletBirthdayProvider: @escaping () -> BlockHeight,
|
|
124
223
|
network: ZcashNetwork
|
|
125
224
|
) {
|
|
@@ -133,21 +232,124 @@ actor CompactBlockProcessor {
|
|
|
133
232
|
self.saplingActivation = network.constants.saplingActivationHeight
|
|
134
233
|
self.network = network
|
|
135
234
|
self.cacheDbURL = nil
|
|
136
|
-
self.
|
|
137
|
-
self.batchSize = batchSize
|
|
235
|
+
self.downloadBatchSize = downloadBatchSize
|
|
138
236
|
self.retries = retries
|
|
139
237
|
self.maxBackoffInterval = maxBackoffInterval
|
|
140
238
|
self.rewindDistance = rewindDistance
|
|
239
|
+
self.scanningBatchSize = scanningBatchSize
|
|
240
|
+
|
|
241
|
+
assert(downloadBatchSize >= scanningBatchSize)
|
|
141
242
|
}
|
|
142
243
|
}
|
|
143
244
|
|
|
245
|
+
/**
|
|
246
|
+
Represents the possible states of a CompactBlockProcessor
|
|
247
|
+
*/
|
|
248
|
+
enum State {
|
|
249
|
+
/**
|
|
250
|
+
connected and downloading blocks
|
|
251
|
+
*/
|
|
252
|
+
case syncing
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
was doing something but was paused
|
|
256
|
+
*/
|
|
257
|
+
case stopped
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
Processor is Enhancing transactions
|
|
261
|
+
*/
|
|
262
|
+
case enhancing
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
fetching utxos
|
|
266
|
+
*/
|
|
267
|
+
case fetching
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
was processing but erred
|
|
271
|
+
*/
|
|
272
|
+
case error(_ error: Error)
|
|
273
|
+
|
|
274
|
+
/// Download sapling param files if needed.
|
|
275
|
+
case handlingSaplingFiles
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
Processor is up to date with the blockchain and you can now make transactions.
|
|
279
|
+
*/
|
|
280
|
+
case synced
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
private var afterSyncHooksManager = AfterSyncHooksManager()
|
|
284
|
+
|
|
285
|
+
let metrics: SDKMetrics
|
|
286
|
+
let logger: Logger
|
|
287
|
+
|
|
288
|
+
/// Don't update this variable directly. Use `updateState()` method.
|
|
289
|
+
var state: State = .stopped
|
|
290
|
+
|
|
291
|
+
private(set) var config: Configuration
|
|
292
|
+
|
|
293
|
+
var maxAttemptsReached: Bool {
|
|
294
|
+
self.retryAttempts >= self.config.retries
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
var shouldStart: Bool {
|
|
298
|
+
switch self.state {
|
|
299
|
+
case .stopped, .synced, .error:
|
|
300
|
+
return !maxAttemptsReached
|
|
301
|
+
default:
|
|
302
|
+
return false
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// It would be better to use Combine here but Combine doesn't work great with async. When this runs regularly only one closure is stored here
|
|
307
|
+
// and that is one provided by `SDKSynchronizer`. But while running tests more "subscribers" is required here. Therefore it's required to handle
|
|
308
|
+
// more closures here.
|
|
309
|
+
var eventClosures: [String: EventClosure] = [:]
|
|
310
|
+
|
|
311
|
+
let blockDownloaderService: BlockDownloaderService
|
|
312
|
+
let blockDownloader: BlockDownloader
|
|
313
|
+
let blockValidator: BlockValidator
|
|
314
|
+
let blockScanner: BlockScanner
|
|
315
|
+
let blockEnhancer: BlockEnhancer
|
|
316
|
+
let utxoFetcher: UTXOFetcher
|
|
317
|
+
let saplingParametersHandler: SaplingParametersHandler
|
|
318
|
+
private let latestBlocksDataProvider: LatestBlocksDataProvider
|
|
319
|
+
|
|
320
|
+
let service: LightWalletService
|
|
321
|
+
let storage: CompactBlockRepository
|
|
322
|
+
let transactionRepository: TransactionRepository
|
|
323
|
+
let accountRepository: AccountRepository
|
|
324
|
+
let rustBackend: ZcashRustBackendWelding
|
|
325
|
+
private var retryAttempts: Int = 0
|
|
326
|
+
private var backoffTimer: Timer?
|
|
327
|
+
private var lastChainValidationFailure: BlockHeight?
|
|
328
|
+
private var consecutiveChainValidationErrors: Int = 0
|
|
329
|
+
var processingError: Error?
|
|
330
|
+
private var foundBlocks = false
|
|
331
|
+
private var maxAttempts: Int {
|
|
332
|
+
config.retries
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
var batchSize: BlockHeight {
|
|
336
|
+
BlockHeight(self.config.downloadBatchSize)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
private var cancelableTask: Task<Void, Error>?
|
|
340
|
+
|
|
341
|
+
private let internalSyncProgress: InternalSyncProgress
|
|
342
|
+
|
|
144
343
|
/// Initializes a CompactBlockProcessor instance
|
|
145
344
|
/// - Parameters:
|
|
146
345
|
/// - service: concrete implementation of `LightWalletService` protocol
|
|
147
346
|
/// - storage: concrete implementation of `CompactBlockRepository` protocol
|
|
148
347
|
/// - backend: a class that complies to `ZcashRustBackendWelding`
|
|
149
348
|
/// - config: `Configuration` struct for this processor
|
|
150
|
-
init(
|
|
349
|
+
init(
|
|
350
|
+
container: DIContainer,
|
|
351
|
+
config: Configuration
|
|
352
|
+
) {
|
|
151
353
|
self.init(
|
|
152
354
|
container: container,
|
|
153
355
|
config: config,
|
|
@@ -174,8 +376,8 @@ actor CompactBlockProcessor {
|
|
|
174
376
|
accountRepository: initializer.accountRepository
|
|
175
377
|
)
|
|
176
378
|
}
|
|
177
|
-
|
|
178
|
-
init(
|
|
379
|
+
|
|
380
|
+
internal init(
|
|
179
381
|
container: DIContainer,
|
|
180
382
|
config: Configuration,
|
|
181
383
|
accountRepository: AccountRepository
|
|
@@ -186,145 +388,172 @@ actor CompactBlockProcessor {
|
|
|
186
388
|
accountRepository: accountRepository
|
|
187
389
|
)
|
|
188
390
|
|
|
189
|
-
let configProvider = ConfigProvider(config: config)
|
|
190
|
-
context = ActionContextImpl(state: .idle)
|
|
191
|
-
actions = Self.makeActions(container: container, configProvider: configProvider)
|
|
192
|
-
|
|
193
391
|
self.metrics = container.resolve(SDKMetrics.self)
|
|
194
392
|
self.logger = container.resolve(Logger.self)
|
|
195
393
|
self.latestBlocksDataProvider = container.resolve(LatestBlocksDataProvider.self)
|
|
394
|
+
self.internalSyncProgress = container.resolve(InternalSyncProgress.self)
|
|
196
395
|
self.blockDownloaderService = container.resolve(BlockDownloaderService.self)
|
|
396
|
+
self.blockDownloader = container.resolve(BlockDownloader.self)
|
|
397
|
+
self.blockValidator = container.resolve(BlockValidator.self)
|
|
398
|
+
self.blockScanner = container.resolve(BlockScanner.self)
|
|
399
|
+
self.blockEnhancer = container.resolve(BlockEnhancer.self)
|
|
400
|
+
self.utxoFetcher = container.resolve(UTXOFetcher.self)
|
|
401
|
+
self.saplingParametersHandler = container.resolve(SaplingParametersHandler.self)
|
|
197
402
|
self.service = container.resolve(LightWalletService.self)
|
|
198
403
|
self.rustBackend = container.resolve(ZcashRustBackendWelding.self)
|
|
199
404
|
self.storage = container.resolve(CompactBlockRepository.self)
|
|
200
405
|
self.config = config
|
|
201
406
|
self.transactionRepository = container.resolve(TransactionRepository.self)
|
|
202
407
|
self.accountRepository = accountRepository
|
|
203
|
-
self.fileManager = container.resolve(ZcashFileManager.self)
|
|
204
|
-
self.configProvider = configProvider
|
|
205
408
|
}
|
|
206
|
-
|
|
409
|
+
|
|
207
410
|
deinit {
|
|
208
|
-
|
|
209
|
-
syncTask = nil
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// swiftlint:disable:next cyclomatic_complexity
|
|
213
|
-
private static func makeActions(container: DIContainer, configProvider: ConfigProvider) -> [CBPState: Action] {
|
|
214
|
-
let actionsDefinition = CBPState.allCases.compactMap { state -> (CBPState, Action)? in
|
|
215
|
-
let action: Action
|
|
216
|
-
switch state {
|
|
217
|
-
case .migrateLegacyCacheDB:
|
|
218
|
-
action = MigrateLegacyCacheDBAction(container: container, configProvider: configProvider)
|
|
219
|
-
case .validateServer:
|
|
220
|
-
action = ValidateServerAction(container: container, configProvider: configProvider)
|
|
221
|
-
case .updateSubtreeRoots:
|
|
222
|
-
action = UpdateSubtreeRootsAction(container: container, configProvider: configProvider)
|
|
223
|
-
case .updateChainTip:
|
|
224
|
-
action = UpdateChainTipAction(container: container)
|
|
225
|
-
case .processSuggestedScanRanges:
|
|
226
|
-
action = ProcessSuggestedScanRangesAction(container: container)
|
|
227
|
-
case .rewind:
|
|
228
|
-
action = RewindAction(container: container)
|
|
229
|
-
case .download:
|
|
230
|
-
action = DownloadAction(container: container, configProvider: configProvider)
|
|
231
|
-
case .scan:
|
|
232
|
-
action = ScanAction(container: container, configProvider: configProvider)
|
|
233
|
-
case .clearAlreadyScannedBlocks:
|
|
234
|
-
action = ClearAlreadyScannedBlocksAction(container: container)
|
|
235
|
-
case .enhance:
|
|
236
|
-
action = EnhanceAction(container: container, configProvider: configProvider)
|
|
237
|
-
case .fetchUTXO:
|
|
238
|
-
action = FetchUTXOsAction(container: container)
|
|
239
|
-
case .handleSaplingParams:
|
|
240
|
-
action = SaplingParamsAction(container: container)
|
|
241
|
-
case .clearCache:
|
|
242
|
-
action = ClearCacheAction(container: container)
|
|
243
|
-
case .finished, .failed, .stopped, .idle:
|
|
244
|
-
return nil
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
return (state, action)
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
return Dictionary(uniqueKeysWithValues: actionsDefinition)
|
|
411
|
+
cancelableTask?.cancel()
|
|
251
412
|
}
|
|
252
413
|
|
|
253
|
-
// This is currently used only in tests. And it should be used only in tests.
|
|
254
414
|
func update(config: Configuration) async {
|
|
255
415
|
self.config = config
|
|
256
|
-
await
|
|
416
|
+
await stop()
|
|
257
417
|
}
|
|
258
|
-
}
|
|
259
418
|
|
|
260
|
-
|
|
419
|
+
func updateState(_ newState: State) async -> Void {
|
|
420
|
+
let oldState = state
|
|
421
|
+
state = newState
|
|
422
|
+
await transitionState(from: oldState, to: newState)
|
|
423
|
+
}
|
|
261
424
|
|
|
262
|
-
|
|
425
|
+
func updateEventClosure(identifier: String, closure: @escaping (Event) async -> Void) async {
|
|
426
|
+
eventClosures[identifier] = closure
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
func send(event: Event) async {
|
|
430
|
+
for item in eventClosures {
|
|
431
|
+
await item.value(event)
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
static func validateServerInfo(
|
|
436
|
+
_ info: LightWalletdInfo,
|
|
437
|
+
saplingActivation: BlockHeight,
|
|
438
|
+
localNetwork: ZcashNetwork,
|
|
439
|
+
rustBackend: ZcashRustBackendWelding
|
|
440
|
+
) async throws {
|
|
441
|
+
// check network types
|
|
442
|
+
guard let remoteNetworkType = NetworkType.forChainName(info.chainName) else {
|
|
443
|
+
throw ZcashError.compactBlockProcessorChainName(info.chainName)
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
guard remoteNetworkType == localNetwork.networkType else {
|
|
447
|
+
throw ZcashError.compactBlockProcessorNetworkMismatch(localNetwork.networkType, remoteNetworkType)
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
guard saplingActivation == info.saplingActivationHeight else {
|
|
451
|
+
throw ZcashError.compactBlockProcessorSaplingActivationMismatch(saplingActivation, BlockHeight(info.saplingActivationHeight))
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// check branch id
|
|
455
|
+
let localBranch = try rustBackend.consensusBranchIdFor(height: Int32(info.blockHeight))
|
|
456
|
+
|
|
457
|
+
guard let remoteBranchID = ConsensusBranchID.fromString(info.consensusBranchID) else {
|
|
458
|
+
throw ZcashError.compactBlockProcessorConsensusBranchID
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
guard remoteBranchID == localBranch else {
|
|
462
|
+
throw ZcashError.compactBlockProcessorWrongConsensusBranchId(localBranch, remoteBranchID)
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/// Starts the CompactBlockProcessor instance and starts downloading and processing blocks
|
|
467
|
+
///
|
|
468
|
+
/// triggers the blockProcessorStartedDownloading notification
|
|
469
|
+
///
|
|
470
|
+
/// - Important: subscribe to the notifications before calling this method
|
|
263
471
|
func start(retry: Bool = false) async {
|
|
264
472
|
if retry {
|
|
265
473
|
self.retryAttempts = 0
|
|
474
|
+
self.processingError = nil
|
|
266
475
|
self.backoffTimer?.invalidate()
|
|
267
476
|
self.backoffTimer = nil
|
|
268
477
|
}
|
|
269
478
|
|
|
270
|
-
guard
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
479
|
+
guard shouldStart else {
|
|
480
|
+
switch self.state {
|
|
481
|
+
case .error(let error):
|
|
482
|
+
// max attempts have been reached
|
|
483
|
+
logger.info("max retry attempts reached with error: \(error)")
|
|
484
|
+
await notifyError(ZcashError.compactBlockProcessorMaxAttemptsReached(self.maxAttempts))
|
|
485
|
+
await updateState(.stopped)
|
|
486
|
+
case .stopped:
|
|
487
|
+
// max attempts have been reached
|
|
488
|
+
logger.info("max retry attempts reached")
|
|
489
|
+
await notifyError(ZcashError.compactBlockProcessorMaxAttemptsReached(self.maxAttempts))
|
|
490
|
+
case .synced:
|
|
491
|
+
// max attempts have been reached
|
|
492
|
+
logger.warn("max retry attempts reached on synced state, this indicates malfunction")
|
|
493
|
+
await notifyError(ZcashError.compactBlockProcessorMaxAttemptsReached(self.maxAttempts))
|
|
494
|
+
case .syncing, .enhancing, .fetching, .handlingSaplingFiles:
|
|
275
495
|
logger.debug("Warning: compact block processor was started while busy!!!!")
|
|
276
496
|
afterSyncHooksManager.insert(hook: .anotherSync)
|
|
277
497
|
}
|
|
278
498
|
return
|
|
279
499
|
}
|
|
280
500
|
|
|
281
|
-
|
|
282
|
-
|
|
501
|
+
do {
|
|
502
|
+
if let legacyCacheDbURL = self.config.cacheDbURL {
|
|
503
|
+
try await self.migrateCacheDb(legacyCacheDbURL)
|
|
504
|
+
}
|
|
505
|
+
} catch {
|
|
506
|
+
await self.fail(error)
|
|
283
507
|
}
|
|
508
|
+
|
|
509
|
+
await self.nextBatch()
|
|
284
510
|
}
|
|
285
511
|
|
|
512
|
+
/**
|
|
513
|
+
Stops the CompactBlockProcessor
|
|
514
|
+
|
|
515
|
+
Note: retry count is reset
|
|
516
|
+
*/
|
|
286
517
|
func stop() async {
|
|
287
|
-
syncTask?.cancel()
|
|
288
518
|
self.backoffTimer?.invalidate()
|
|
289
519
|
self.backoffTimer = nil
|
|
290
|
-
await stopAllActions()
|
|
291
|
-
retryAttempts = 0
|
|
292
|
-
}
|
|
293
520
|
|
|
294
|
-
|
|
295
|
-
|
|
521
|
+
cancelableTask?.cancel()
|
|
522
|
+
await blockDownloader.stopDownload()
|
|
523
|
+
|
|
524
|
+
self.retryAttempts = 0
|
|
296
525
|
}
|
|
297
|
-
}
|
|
298
526
|
|
|
299
|
-
// MARK:
|
|
527
|
+
// MARK: Rewind
|
|
300
528
|
|
|
301
|
-
extension CompactBlockProcessor {
|
|
302
529
|
/// Rewinds to provided height.
|
|
303
530
|
/// - Parameter height: height to rewind to. If nil is provided, it will rescan to nearest height (quick rescan)
|
|
304
531
|
///
|
|
305
532
|
/// - Note: If this is called while sync is in progress then the sync process is stopped first and then rewind is executed.
|
|
306
|
-
func rewind(context: AfterSyncHooksManager.RewindContext) async
|
|
533
|
+
func rewind(context: AfterSyncHooksManager.RewindContext) async {
|
|
307
534
|
logger.debug("Starting rewind")
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
try await doRewind(context: context)
|
|
311
|
-
} else {
|
|
535
|
+
switch self.state {
|
|
536
|
+
case .syncing, .enhancing, .fetching, .handlingSaplingFiles:
|
|
312
537
|
logger.debug("Stopping sync because of rewind")
|
|
313
538
|
afterSyncHooksManager.insert(hook: .rewind(context))
|
|
314
539
|
await stop()
|
|
540
|
+
|
|
541
|
+
case .stopped, .error, .synced:
|
|
542
|
+
logger.debug("Sync doesn't run. Executing rewind.")
|
|
543
|
+
await doRewind(context: context)
|
|
315
544
|
}
|
|
316
545
|
}
|
|
317
546
|
|
|
318
|
-
private func doRewind(context: AfterSyncHooksManager.RewindContext) async
|
|
547
|
+
private func doRewind(context: AfterSyncHooksManager.RewindContext) async {
|
|
319
548
|
logger.debug("Executing rewind.")
|
|
320
|
-
let lastDownloaded = await
|
|
549
|
+
let lastDownloaded = await internalSyncProgress.latestDownloadedBlockHeight
|
|
321
550
|
let height = Int32(context.height ?? lastDownloaded)
|
|
322
551
|
|
|
323
552
|
let nearestHeight: Int32
|
|
324
553
|
do {
|
|
325
554
|
nearestHeight = try await rustBackend.getNearestRewindHeight(height: height)
|
|
326
555
|
} catch {
|
|
327
|
-
await
|
|
556
|
+
await fail(error)
|
|
328
557
|
return await context.completion(.failure(error))
|
|
329
558
|
}
|
|
330
559
|
|
|
@@ -332,10 +561,9 @@ extension CompactBlockProcessor {
|
|
|
332
561
|
let rewindHeight = max(Int32(nearestHeight - 1), Int32(config.walletBirthday))
|
|
333
562
|
|
|
334
563
|
do {
|
|
335
|
-
try await rewindDownloadBlockAction(to: BlockHeight(rewindHeight))
|
|
336
564
|
try await rustBackend.rewindToHeight(height: rewindHeight)
|
|
337
565
|
} catch {
|
|
338
|
-
await
|
|
566
|
+
await fail(error)
|
|
339
567
|
return await context.completion(.failure(error))
|
|
340
568
|
}
|
|
341
569
|
|
|
@@ -346,46 +574,38 @@ extension CompactBlockProcessor {
|
|
|
346
574
|
} catch {
|
|
347
575
|
return await context.completion(.failure(error))
|
|
348
576
|
}
|
|
349
|
-
|
|
350
|
-
await resetContext(restoreLastEnhancedHeight: false)
|
|
351
|
-
|
|
352
|
-
await context.completion(.success(rewindBlockHeight))
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
577
|
|
|
356
|
-
|
|
578
|
+
await internalSyncProgress.rewind(to: rewindBlockHeight)
|
|
357
579
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
if let downloadAction = actions[.download] as? DownloadAction {
|
|
361
|
-
await downloadAction.downloader.rewind(latestDownloadedBlockHeight: rewindHeight)
|
|
362
|
-
} else {
|
|
363
|
-
throw ZcashError.compactBlockProcessorDownloadBlockActionRewind
|
|
364
|
-
}
|
|
580
|
+
self.lastChainValidationFailure = nil
|
|
581
|
+
await context.completion(.success(rewindBlockHeight))
|
|
365
582
|
}
|
|
366
|
-
}
|
|
367
583
|
|
|
368
|
-
// MARK:
|
|
584
|
+
// MARK: Wipe
|
|
369
585
|
|
|
370
|
-
|
|
371
|
-
func wipe(context: AfterSyncHooksManager.WipeContext) async throws {
|
|
586
|
+
func wipe(context: AfterSyncHooksManager.WipeContext) async {
|
|
372
587
|
logger.debug("Starting wipe")
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
try await doWipe(context: context)
|
|
376
|
-
} else {
|
|
588
|
+
switch self.state {
|
|
589
|
+
case .syncing, .enhancing, .fetching, .handlingSaplingFiles:
|
|
377
590
|
logger.debug("Stopping sync because of wipe")
|
|
378
591
|
afterSyncHooksManager.insert(hook: .wipe(context))
|
|
379
592
|
await stop()
|
|
593
|
+
|
|
594
|
+
case .stopped, .error, .synced:
|
|
595
|
+
logger.debug("Sync doesn't run. Executing wipe.")
|
|
596
|
+
await doWipe(context: context)
|
|
380
597
|
}
|
|
381
598
|
}
|
|
382
599
|
|
|
383
|
-
private func doWipe(context: AfterSyncHooksManager.WipeContext) async
|
|
600
|
+
private func doWipe(context: AfterSyncHooksManager.WipeContext) async {
|
|
384
601
|
logger.debug("Executing wipe.")
|
|
385
602
|
context.prewipe()
|
|
386
603
|
|
|
604
|
+
await updateState(.stopped)
|
|
605
|
+
|
|
387
606
|
do {
|
|
388
607
|
try await self.storage.clear()
|
|
608
|
+
await internalSyncProgress.rewind(to: 0)
|
|
389
609
|
|
|
390
610
|
wipeLegacyCacheDbIfNeeded()
|
|
391
611
|
|
|
@@ -394,308 +614,437 @@ extension CompactBlockProcessor {
|
|
|
394
614
|
try fileManager.removeItem(at: config.dataDb)
|
|
395
615
|
}
|
|
396
616
|
|
|
397
|
-
try await rewindDownloadBlockAction(to: nil)
|
|
398
|
-
|
|
399
617
|
await context.completion(nil)
|
|
400
618
|
} catch {
|
|
401
619
|
await context.completion(error)
|
|
402
620
|
}
|
|
403
621
|
}
|
|
404
622
|
|
|
405
|
-
|
|
406
|
-
guard let cacheDbURL = config.cacheDbURL else { return }
|
|
407
|
-
guard fileManager.isDeletableFile(atPath: cacheDbURL.pathExtension) else { return }
|
|
408
|
-
try? fileManager.removeItem(at: cacheDbURL)
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
// MARK: - Events
|
|
413
|
-
|
|
414
|
-
extension CompactBlockProcessor {
|
|
415
|
-
typealias EventClosure = (Event) async -> Void
|
|
416
|
-
|
|
417
|
-
enum Event {
|
|
418
|
-
/// Event sent when the CompactBlockProcessor presented an error.
|
|
419
|
-
case failed(Error)
|
|
420
|
-
|
|
421
|
-
/// Event sent when the CompactBlockProcessor has finished syncing the blockchain to latest height
|
|
422
|
-
case finished(_ lastScannedHeight: BlockHeight)
|
|
423
|
-
|
|
424
|
-
/// Event sent when the CompactBlockProcessor found a newly mined transaction
|
|
425
|
-
case minedTransaction(ZcashTransaction.Overview)
|
|
623
|
+
// MARK: Sync
|
|
426
624
|
|
|
427
|
-
|
|
428
|
-
|
|
625
|
+
func validateServer() async {
|
|
626
|
+
do {
|
|
627
|
+
let info = try await self.service.getInfo()
|
|
628
|
+
try await Self.validateServerInfo(
|
|
629
|
+
info,
|
|
630
|
+
saplingActivation: self.config.saplingActivation,
|
|
631
|
+
localNetwork: self.config.network,
|
|
632
|
+
rustBackend: self.rustBackend
|
|
633
|
+
)
|
|
634
|
+
} catch {
|
|
635
|
+
await self.severeFailure(error)
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/// Processes new blocks on the given range based on the configuration set for this instance
|
|
640
|
+
func processNewBlocks(ranges: SyncRanges) async {
|
|
641
|
+
self.foundBlocks = true
|
|
642
|
+
|
|
643
|
+
cancelableTask = Task(priority: .userInitiated) {
|
|
644
|
+
do {
|
|
645
|
+
let totalProgressRange = computeTotalProgressRange(from: ranges)
|
|
646
|
+
|
|
647
|
+
logger.debug("""
|
|
648
|
+
Syncing with ranges:
|
|
649
|
+
downloaded but not scanned: \
|
|
650
|
+
\(ranges.downloadedButUnscannedRange?.lowerBound ?? -1)...\(ranges.downloadedButUnscannedRange?.upperBound ?? -1)
|
|
651
|
+
download and scan: \(ranges.downloadAndScanRange?.lowerBound ?? -1)...\(ranges.downloadAndScanRange?.upperBound ?? -1)
|
|
652
|
+
enhance range: \(ranges.enhanceRange?.lowerBound ?? -1)...\(ranges.enhanceRange?.upperBound ?? -1)
|
|
653
|
+
fetchUTXO range: \(ranges.fetchUTXORange?.lowerBound ?? -1)...\(ranges.fetchUTXORange?.upperBound ?? -1)
|
|
654
|
+
total progress range: \(totalProgressRange.lowerBound)...\(totalProgressRange.upperBound)
|
|
655
|
+
""")
|
|
656
|
+
|
|
657
|
+
var anyActionExecuted = false
|
|
658
|
+
|
|
659
|
+
// clear any present cached state if needed.
|
|
660
|
+
// this checks if there was a sync in progress that was
|
|
661
|
+
// interrupted abruptly and cache was not able to be cleared
|
|
662
|
+
// properly and internal state set to the appropriate value
|
|
663
|
+
if let newLatestDownloadedHeight = ranges.shouldClearBlockCacheAndUpdateInternalState() {
|
|
664
|
+
try await storage.clear()
|
|
665
|
+
await internalSyncProgress.set(newLatestDownloadedHeight, .latestDownloadedBlockHeight)
|
|
666
|
+
} else {
|
|
667
|
+
try await storage.create()
|
|
668
|
+
}
|
|
429
669
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
670
|
+
if let range = ranges.downloadedButUnscannedRange {
|
|
671
|
+
logger.debug("Starting scan with downloaded but not scanned blocks with range: \(range.lowerBound)...\(range.upperBound)")
|
|
672
|
+
try await blockScanner.scanBlocks(at: range, totalProgressRange: totalProgressRange) { [weak self] lastScannedHeight in
|
|
673
|
+
let progress = BlockProgress(
|
|
674
|
+
startHeight: totalProgressRange.lowerBound,
|
|
675
|
+
targetHeight: totalProgressRange.upperBound,
|
|
676
|
+
progressHeight: lastScannedHeight
|
|
677
|
+
)
|
|
678
|
+
await self?.notifyProgress(.syncing(progress))
|
|
679
|
+
}
|
|
680
|
+
}
|
|
434
681
|
|
|
435
|
-
|
|
436
|
-
|
|
682
|
+
if let range = ranges.downloadAndScanRange {
|
|
683
|
+
logger.debug("Starting sync with range: \(range.lowerBound)...\(range.upperBound)")
|
|
684
|
+
try await blockDownloader.setSyncRange(range, batchSize: batchSize)
|
|
685
|
+
try await downloadAndScanBlocks(at: range, totalProgressRange: totalProgressRange)
|
|
686
|
+
// Side effect of calling stop is to delete last used download stream. To be sure that it doesn't keep any data in memory.
|
|
687
|
+
await blockDownloader.stopDownload()
|
|
688
|
+
}
|
|
437
689
|
|
|
438
|
-
|
|
439
|
-
|
|
690
|
+
if let range = ranges.enhanceRange {
|
|
691
|
+
anyActionExecuted = true
|
|
692
|
+
logger.debug("Enhancing with range: \(range.lowerBound)...\(range.upperBound)")
|
|
693
|
+
await updateState(.enhancing)
|
|
694
|
+
if let transactions = try await blockEnhancer.enhance(
|
|
695
|
+
at: range,
|
|
696
|
+
didEnhance: { [weak self] progress in
|
|
697
|
+
await self?.notifyProgress(.enhance(progress))
|
|
698
|
+
if
|
|
699
|
+
let foundTx = progress.lastFoundTransaction,
|
|
700
|
+
progress.newlyMined {
|
|
701
|
+
await self?.notifyMinedTransaction(foundTx)
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
) {
|
|
705
|
+
await notifyTransactions(transactions, in: range)
|
|
706
|
+
}
|
|
707
|
+
}
|
|
440
708
|
|
|
441
|
-
|
|
442
|
-
|
|
709
|
+
if let range = ranges.fetchUTXORange {
|
|
710
|
+
anyActionExecuted = true
|
|
711
|
+
logger.debug("Fetching UTXO with range: \(range.lowerBound)...\(range.upperBound)")
|
|
712
|
+
await updateState(.fetching)
|
|
713
|
+
let result = try await utxoFetcher.fetch(at: range) { [weak self] progress in
|
|
714
|
+
await self?.notifyProgress(.fetch(progress))
|
|
715
|
+
}
|
|
716
|
+
await send(event: .storedUTXOs(result))
|
|
717
|
+
}
|
|
443
718
|
|
|
444
|
-
|
|
445
|
-
|
|
719
|
+
logger.debug("Fetching sapling parameters")
|
|
720
|
+
await updateState(.handlingSaplingFiles)
|
|
721
|
+
try await saplingParametersHandler.handleIfNeeded()
|
|
446
722
|
|
|
447
|
-
|
|
448
|
-
|
|
723
|
+
logger.debug("Clearing cache")
|
|
724
|
+
try await clearCompactBlockCache()
|
|
449
725
|
|
|
450
|
-
|
|
451
|
-
|
|
726
|
+
if !Task.isCancelled {
|
|
727
|
+
let newBlocksMined = await ranges.latestBlockHeight < latestBlocksDataProvider.latestBlockHeight
|
|
728
|
+
await processBatchFinished(height: (anyActionExecuted && !newBlocksMined) ? ranges.latestBlockHeight : nil)
|
|
729
|
+
}
|
|
730
|
+
} catch {
|
|
731
|
+
// Side effect of calling stop is to delete last used download stream. To be sure that it doesn't keep any data in memory.
|
|
732
|
+
await blockDownloader.stopDownload()
|
|
733
|
+
logger.error("Sync failed with error: \(error)")
|
|
452
734
|
|
|
453
|
-
|
|
454
|
-
|
|
735
|
+
if Task.isCancelled {
|
|
736
|
+
logger.info("Processing cancelled.")
|
|
737
|
+
await updateState(.stopped)
|
|
738
|
+
await handleAfterSyncHooks()
|
|
739
|
+
} else {
|
|
740
|
+
if case let ZcashError.rustValidateCombinedChainInvalidChain(height) = error {
|
|
741
|
+
await validationFailed(at: BlockHeight(height))
|
|
742
|
+
} else {
|
|
743
|
+
logger.error("processing failed with error: \(error)")
|
|
744
|
+
await fail(error)
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
}
|
|
455
749
|
}
|
|
456
750
|
|
|
457
|
-
func
|
|
458
|
-
|
|
459
|
-
|
|
751
|
+
private func handleAfterSyncHooks() async {
|
|
752
|
+
let afterSyncHooksManager = self.afterSyncHooksManager
|
|
753
|
+
self.afterSyncHooksManager = AfterSyncHooksManager()
|
|
460
754
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
755
|
+
if let wipeContext = afterSyncHooksManager.shouldExecuteWipeHook() {
|
|
756
|
+
await doWipe(context: wipeContext)
|
|
757
|
+
} else if let rewindContext = afterSyncHooksManager.shouldExecuteRewindHook() {
|
|
758
|
+
await doRewind(context: rewindContext)
|
|
759
|
+
} else if afterSyncHooksManager.shouldExecuteAnotherSyncHook() {
|
|
760
|
+
logger.debug("Starting new sync.")
|
|
761
|
+
await nextBatch()
|
|
464
762
|
}
|
|
465
763
|
}
|
|
466
|
-
}
|
|
467
764
|
|
|
468
|
-
|
|
765
|
+
private func downloadAndScanBlocks(at range: CompactBlockRange, totalProgressRange: CompactBlockRange) async throws {
|
|
766
|
+
// Divide `range` by `batchSize` and compute how many time do we need to run to download and scan all the blocks.
|
|
767
|
+
// +1 must be done here becase `range` is closed range. So even if upperBound and lowerBound are same there is one block to sync.
|
|
768
|
+
let blocksCountToSync = (range.upperBound - range.lowerBound) + 1
|
|
769
|
+
var loopsCount = blocksCountToSync / batchSize
|
|
770
|
+
if blocksCountToSync % batchSize != 0 {
|
|
771
|
+
loopsCount += 1
|
|
772
|
+
}
|
|
469
773
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
// of sync process without any side effects.
|
|
474
|
-
//
|
|
475
|
-
// Check `docs/cbp_state_machine.puml` file and `docs/images/cbp_state_machine.png` file to see all the state tree. Also when you update state
|
|
476
|
-
// tree in the code update this documentation. Image is generated by plantuml tool.
|
|
477
|
-
//
|
|
478
|
-
// swiftlint:disable:next cyclomatic_complexity
|
|
479
|
-
private func run() async {
|
|
480
|
-
logger.debug("Starting run")
|
|
481
|
-
await resetContext()
|
|
482
|
-
|
|
483
|
-
while true {
|
|
484
|
-
// Sync is starting when the state is `idle`.
|
|
485
|
-
if await context.state == .idle {
|
|
486
|
-
// Side effect of calling stop is to delete last used download stream. To be sure that it doesn't keep any data in memory.
|
|
487
|
-
await stopAllActions()
|
|
488
|
-
// Update state to the first state in state machine that can be handled by action.
|
|
489
|
-
await context.update(state: .migrateLegacyCacheDB)
|
|
490
|
-
await syncStarted()
|
|
774
|
+
var lastScannedHeight: BlockHeight = .zero
|
|
775
|
+
for i in 0..<loopsCount {
|
|
776
|
+
let processingRange = computeSingleLoopDownloadRange(fullRange: range, loopCounter: i, batchSize: batchSize)
|
|
491
777
|
|
|
492
|
-
|
|
493
|
-
await setTimer()
|
|
494
|
-
}
|
|
495
|
-
}
|
|
778
|
+
logger.debug("Sync loop #\(i + 1) range: \(processingRange.lowerBound)...\(processingRange.upperBound)")
|
|
496
779
|
|
|
497
|
-
|
|
498
|
-
|
|
780
|
+
// This is important. We must be sure that no new download is executed when this Task is canceled. Without this line `stop()` doesn't
|
|
781
|
+
// work.
|
|
782
|
+
try Task.checkCancellation()
|
|
499
783
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
break
|
|
509
|
-
}
|
|
784
|
+
do {
|
|
785
|
+
await blockDownloader.setDownloadLimit(processingRange.upperBound + (2 * batchSize))
|
|
786
|
+
await blockDownloader.startDownload(maxBlockBufferSize: config.downloadBufferSize)
|
|
787
|
+
|
|
788
|
+
try await blockDownloader.waitUntilRequestedBlocksAreDownloaded(in: processingRange)
|
|
789
|
+
} catch {
|
|
790
|
+
await ifTaskIsNotCanceledClearCompactBlockCache(lastScannedHeight: lastScannedHeight)
|
|
791
|
+
throw error
|
|
510
792
|
}
|
|
511
793
|
|
|
512
794
|
do {
|
|
513
|
-
try
|
|
514
|
-
|
|
515
|
-
// Execute action.
|
|
516
|
-
context = try await action.run(with: context) { [weak self] event in
|
|
517
|
-
await self?.send(event: event)
|
|
518
|
-
if let progressChanged = await self?.compactBlockProgress.hasProgressUpdated(event), progressChanged {
|
|
519
|
-
if let progress = await self?.compactBlockProgress.progress {
|
|
520
|
-
await self?.send(event: .progressUpdated(progress))
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
await didFinishAction()
|
|
795
|
+
try await blockValidator.validate()
|
|
526
796
|
} catch {
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
797
|
+
await ifTaskIsNotCanceledClearCompactBlockCache(lastScannedHeight: lastScannedHeight)
|
|
798
|
+
logger.error("Block validation failed with error: \(error)")
|
|
799
|
+
throw error
|
|
800
|
+
}
|
|
530
801
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
} else {
|
|
546
|
-
if await handleSyncFailure(action: action, error: error) {
|
|
547
|
-
// Start sync all over again
|
|
548
|
-
await resetContext()
|
|
549
|
-
} else {
|
|
550
|
-
// end the sync loop
|
|
551
|
-
break
|
|
552
|
-
}
|
|
802
|
+
// Without this `stop()` would work. But this line improves support for Task cancelation.
|
|
803
|
+
try Task.checkCancellation()
|
|
804
|
+
|
|
805
|
+
do {
|
|
806
|
+
lastScannedHeight = try await blockScanner.scanBlocks(
|
|
807
|
+
at: processingRange,
|
|
808
|
+
totalProgressRange: totalProgressRange
|
|
809
|
+
) { [weak self] lastScannedHeight in
|
|
810
|
+
let progress = BlockProgress(
|
|
811
|
+
startHeight: totalProgressRange.lowerBound,
|
|
812
|
+
targetHeight: totalProgressRange.upperBound,
|
|
813
|
+
progressHeight: lastScannedHeight
|
|
814
|
+
)
|
|
815
|
+
await self?.notifyProgress(.syncing(progress))
|
|
553
816
|
}
|
|
817
|
+
} catch {
|
|
818
|
+
logger.error("Scanning failed with error: \(error)")
|
|
819
|
+
await ifTaskIsNotCanceledClearCompactBlockCache(lastScannedHeight: lastScannedHeight)
|
|
820
|
+
throw error
|
|
554
821
|
}
|
|
555
|
-
}
|
|
556
822
|
|
|
557
|
-
|
|
558
|
-
|
|
823
|
+
try await clearCompactBlockCache(upTo: lastScannedHeight)
|
|
824
|
+
|
|
825
|
+
let progress = BlockProgress(
|
|
826
|
+
startHeight: totalProgressRange.lowerBound,
|
|
827
|
+
targetHeight: totalProgressRange.upperBound,
|
|
828
|
+
progressHeight: processingRange.upperBound
|
|
829
|
+
)
|
|
830
|
+
await notifyProgress(.syncing(progress))
|
|
831
|
+
}
|
|
559
832
|
}
|
|
560
833
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
834
|
+
/*
|
|
835
|
+
Here range for one batch is computed. For example if we want to sync blocks 0...1000 with batchSize 100 we want to generage blocks like
|
|
836
|
+
this:
|
|
837
|
+
0...99
|
|
838
|
+
100...199
|
|
839
|
+
200...299
|
|
840
|
+
300...399
|
|
841
|
+
...
|
|
842
|
+
900...999
|
|
843
|
+
1000...1000
|
|
844
|
+
*/
|
|
845
|
+
func computeSingleLoopDownloadRange(fullRange: CompactBlockRange, loopCounter: Int, batchSize: BlockHeight) -> CompactBlockRange {
|
|
846
|
+
let lowerBound = fullRange.lowerBound + (loopCounter * batchSize)
|
|
847
|
+
let upperBound = min(fullRange.lowerBound + ((loopCounter + 1) * batchSize) - 1, fullRange.upperBound)
|
|
848
|
+
return lowerBound...upperBound
|
|
566
849
|
}
|
|
567
850
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
851
|
+
/// It may happen that sync process start with syncing blocks that were downloaded but not synced in previous run of the sync process. This
|
|
852
|
+
/// methods analyses what must be done and computes range that should be used to compute reported progress.
|
|
853
|
+
private func computeTotalProgressRange(from syncRanges: SyncRanges) -> CompactBlockRange {
|
|
854
|
+
guard syncRanges.downloadedButUnscannedRange != nil || syncRanges.downloadAndScanRange != nil else {
|
|
855
|
+
// In this case we are sure that no downloading or scanning happens so this returned range won't be even used. And it's easier to return
|
|
856
|
+
// this "fake" range than to handle nil.
|
|
857
|
+
return 0...0
|
|
571
858
|
}
|
|
572
859
|
|
|
573
|
-
|
|
574
|
-
|
|
860
|
+
// Thanks to guard above we can be sure that one of these two ranges is not nil.
|
|
861
|
+
let lowerBound = syncRanges.downloadedButUnscannedRange?.lowerBound ?? syncRanges.downloadAndScanRange?.lowerBound ?? 0
|
|
862
|
+
let upperBound = syncRanges.downloadAndScanRange?.upperBound ?? syncRanges.downloadedButUnscannedRange?.upperBound ?? 0
|
|
575
863
|
|
|
576
|
-
return
|
|
864
|
+
return lowerBound...upperBound
|
|
577
865
|
}
|
|
578
866
|
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
switch await context.state {
|
|
583
|
-
case .idle:
|
|
584
|
-
break
|
|
585
|
-
case .migrateLegacyCacheDB:
|
|
586
|
-
break
|
|
587
|
-
case .validateServer:
|
|
588
|
-
break
|
|
589
|
-
case .updateSubtreeRoots:
|
|
590
|
-
break
|
|
591
|
-
case .updateChainTip:
|
|
592
|
-
break
|
|
593
|
-
case .processSuggestedScanRanges:
|
|
594
|
-
break
|
|
595
|
-
case .rewind:
|
|
596
|
-
break
|
|
597
|
-
case .download:
|
|
598
|
-
break
|
|
599
|
-
case .scan:
|
|
600
|
-
break
|
|
601
|
-
case .clearAlreadyScannedBlocks:
|
|
602
|
-
break
|
|
603
|
-
case .enhance:
|
|
604
|
-
await send(event: .startedEnhancing)
|
|
605
|
-
case .fetchUTXO:
|
|
606
|
-
await send(event: .startedFetching)
|
|
607
|
-
case .handleSaplingParams:
|
|
608
|
-
break
|
|
609
|
-
case .clearCache:
|
|
610
|
-
break
|
|
611
|
-
case .finished:
|
|
612
|
-
break
|
|
613
|
-
case .failed:
|
|
614
|
-
break
|
|
615
|
-
case .stopped:
|
|
616
|
-
break
|
|
617
|
-
}
|
|
867
|
+
func notifyMinedTransaction(_ tx: ZcashTransaction.Overview) async {
|
|
868
|
+
logger.debug("notify mined transaction: \(tx)")
|
|
869
|
+
await send(event: .minedTransaction(tx))
|
|
618
870
|
}
|
|
619
871
|
|
|
620
|
-
func
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
872
|
+
func notifyProgress(_ progress: CompactBlockProgress) async {
|
|
873
|
+
logger.debug("progress: \(progress)")
|
|
874
|
+
await send(event: .progressUpdated(progress))
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
func notifyTransactions(_ txs: [ZcashTransaction.Overview], in range: CompactBlockRange) async {
|
|
878
|
+
await send(event: .foundTransactions(txs, range))
|
|
626
879
|
}
|
|
627
880
|
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
881
|
+
func determineLowerBound(
|
|
882
|
+
errorHeight: Int,
|
|
883
|
+
consecutiveErrors: Int,
|
|
884
|
+
walletBirthday: BlockHeight
|
|
885
|
+
) -> BlockHeight {
|
|
886
|
+
let offset = min(ZcashSDK.maxReorgSize, ZcashSDK.defaultRewindDistance * (consecutiveErrors + 1))
|
|
887
|
+
return max(errorHeight - offset, walletBirthday - ZcashSDK.maxReorgSize)
|
|
632
888
|
}
|
|
633
889
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
890
|
+
func severeFailure(_ error: Error) async {
|
|
891
|
+
cancelableTask?.cancel()
|
|
892
|
+
await blockDownloader.stopDownload()
|
|
893
|
+
logger.error("show stopper failure: \(error)")
|
|
894
|
+
self.backoffTimer?.invalidate()
|
|
895
|
+
self.retryAttempts = config.retries
|
|
896
|
+
self.processingError = error
|
|
897
|
+
await updateState(.error(error))
|
|
898
|
+
await self.notifyError(error)
|
|
899
|
+
}
|
|
641
900
|
|
|
642
|
-
|
|
643
|
-
|
|
901
|
+
func fail(_ error: Error) async {
|
|
902
|
+
// TODO: [#713] specify: failure. https://github.com/zcash/ZcashLightClientKit/issues/713
|
|
903
|
+
logger.error("\(error)")
|
|
904
|
+
cancelableTask?.cancel()
|
|
905
|
+
await blockDownloader.stopDownload()
|
|
906
|
+
self.retryAttempts += 1
|
|
907
|
+
self.processingError = error
|
|
908
|
+
switch self.state {
|
|
909
|
+
case .error:
|
|
910
|
+
await notifyError(error)
|
|
911
|
+
default:
|
|
912
|
+
break
|
|
913
|
+
}
|
|
914
|
+
await updateState(.error(error))
|
|
915
|
+
guard self.maxAttemptsReached else { return }
|
|
916
|
+
// don't set a new timer if there are no more attempts.
|
|
917
|
+
await self.setTimer()
|
|
918
|
+
}
|
|
644
919
|
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
await send(event: .finished(lastScannedHeight))
|
|
650
|
-
await context.update(state: .finished)
|
|
920
|
+
private func validateConfiguration() throws {
|
|
921
|
+
guard FileManager.default.isReadableFile(atPath: config.fsBlockCacheRoot.absoluteString) else {
|
|
922
|
+
throw ZcashError.compactBlockProcessorMissingDbPath(config.fsBlockCacheRoot.absoluteString)
|
|
923
|
+
}
|
|
651
924
|
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
925
|
+
guard FileManager.default.isReadableFile(atPath: config.dataDb.absoluteString) else {
|
|
926
|
+
throw ZcashError.compactBlockProcessorMissingDbPath(config.dataDb.absoluteString)
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
private func nextBatch() async {
|
|
931
|
+
await updateState(.syncing)
|
|
932
|
+
if backoffTimer == nil { await setTimer() }
|
|
933
|
+
do {
|
|
934
|
+
let nextState = try await NextStateHelper.nextState(
|
|
935
|
+
service: self.service,
|
|
936
|
+
downloaderService: blockDownloaderService,
|
|
937
|
+
latestBlocksDataProvider: latestBlocksDataProvider,
|
|
938
|
+
config: self.config,
|
|
939
|
+
rustBackend: self.rustBackend,
|
|
940
|
+
internalSyncProgress: internalSyncProgress
|
|
941
|
+
)
|
|
942
|
+
switch nextState {
|
|
943
|
+
case .finishProcessing(let height):
|
|
944
|
+
await self.processingFinished(height: height)
|
|
945
|
+
case .processNewBlocks(let ranges):
|
|
946
|
+
await self.processNewBlocks(ranges: ranges)
|
|
947
|
+
case let .wait(latestHeight, latestDownloadHeight):
|
|
948
|
+
// Lightwalletd might be syncing
|
|
949
|
+
logger.info(
|
|
950
|
+
"Lightwalletd might be syncing: latest downloaded block height is: \(latestDownloadHeight) " +
|
|
951
|
+
"while latest blockheight is reported at: \(latestHeight)"
|
|
952
|
+
)
|
|
953
|
+
await self.processingFinished(height: latestDownloadHeight)
|
|
954
|
+
}
|
|
955
|
+
} catch {
|
|
956
|
+
await self.severeFailure(error)
|
|
658
957
|
}
|
|
659
958
|
}
|
|
660
959
|
|
|
661
|
-
|
|
662
|
-
|
|
960
|
+
internal func validationFailed(at height: BlockHeight) async {
|
|
961
|
+
// cancel all Tasks
|
|
962
|
+
cancelableTask?.cancel()
|
|
963
|
+
await blockDownloader.stopDownload()
|
|
663
964
|
|
|
664
|
-
|
|
965
|
+
// register latest failure
|
|
966
|
+
self.lastChainValidationFailure = height
|
|
967
|
+
|
|
968
|
+
// rewind
|
|
969
|
+
let rewindHeight = determineLowerBound(
|
|
970
|
+
errorHeight: height,
|
|
971
|
+
consecutiveErrors: consecutiveChainValidationErrors,
|
|
972
|
+
walletBirthday: self.config.walletBirthday
|
|
973
|
+
)
|
|
665
974
|
|
|
666
|
-
self.
|
|
667
|
-
await send(event: .failed(error))
|
|
975
|
+
self.consecutiveChainValidationErrors += 1
|
|
668
976
|
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
977
|
+
do {
|
|
978
|
+
try await rustBackend.rewindToHeight(height: Int32(rewindHeight))
|
|
979
|
+
} catch {
|
|
980
|
+
await fail(error)
|
|
981
|
+
return
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
do {
|
|
985
|
+
try await blockDownloaderService.rewind(to: rewindHeight)
|
|
986
|
+
await internalSyncProgress.rewind(to: rewindHeight)
|
|
987
|
+
|
|
988
|
+
await send(event: .handledReorg(height, rewindHeight))
|
|
989
|
+
|
|
990
|
+
// process next batch
|
|
991
|
+
await self.nextBatch()
|
|
992
|
+
} catch {
|
|
993
|
+
await self.fail(error)
|
|
672
994
|
}
|
|
673
995
|
}
|
|
674
996
|
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
997
|
+
internal func processBatchFinished(height: BlockHeight?) async {
|
|
998
|
+
retryAttempts = 0
|
|
999
|
+
consecutiveChainValidationErrors = 0
|
|
678
1000
|
|
|
679
|
-
if let
|
|
680
|
-
|
|
681
|
-
return false
|
|
682
|
-
} else if let rewindContext = afterSyncHooksManager.shouldExecuteRewindHook() {
|
|
683
|
-
try await doRewind(context: rewindContext)
|
|
684
|
-
return false
|
|
685
|
-
} else if afterSyncHooksManager.shouldExecuteAnotherSyncHook() {
|
|
686
|
-
logger.debug("Starting new sync.")
|
|
687
|
-
return true
|
|
1001
|
+
if let height {
|
|
1002
|
+
await processingFinished(height: height)
|
|
688
1003
|
} else {
|
|
689
|
-
|
|
1004
|
+
await nextBatch()
|
|
690
1005
|
}
|
|
691
1006
|
}
|
|
692
|
-
|
|
1007
|
+
|
|
1008
|
+
private func processingFinished(height: BlockHeight) async {
|
|
1009
|
+
await send(event: .finished(height, foundBlocks))
|
|
1010
|
+
await updateState(.synced)
|
|
1011
|
+
await setTimer()
|
|
1012
|
+
}
|
|
693
1013
|
|
|
694
|
-
|
|
1014
|
+
private func ifTaskIsNotCanceledClearCompactBlockCache(lastScannedHeight: BlockHeight) async {
|
|
1015
|
+
guard !Task.isCancelled else { return }
|
|
1016
|
+
do {
|
|
1017
|
+
// Blocks download work in parallel with scanning. So imagine this scenario:
|
|
1018
|
+
//
|
|
1019
|
+
// Scanning is done until height 10300. Blocks are downloaded until height 10400.
|
|
1020
|
+
// And now validation fails and this method is called. And `.latestDownloadedBlockHeight` in `internalSyncProgress` is set to 10400. And
|
|
1021
|
+
// all the downloaded blocks are removed here.
|
|
1022
|
+
//
|
|
1023
|
+
// If this line doesn't happen then when sync starts next time it thinks that all the blocks are downloaded until 10400. But all were
|
|
1024
|
+
// removed. So blocks between 10300 and 10400 wouldn't ever be scanned.
|
|
1025
|
+
//
|
|
1026
|
+
// Scanning is done until 10300 so the SDK can be sure that blocks with height below 10300 are not required. So it makes sense to set
|
|
1027
|
+
// `.latestDownloadedBlockHeight` to `lastScannedHeight`. And sync will work fine in next run.
|
|
1028
|
+
await internalSyncProgress.set(lastScannedHeight, .latestDownloadedBlockHeight)
|
|
1029
|
+
try await clearCompactBlockCache()
|
|
1030
|
+
} catch {
|
|
1031
|
+
logger.error("`clearCompactBlockCache` failed after error: \(error)")
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
695
1034
|
|
|
696
|
-
|
|
1035
|
+
private func clearCompactBlockCache(upTo height: BlockHeight) async throws {
|
|
1036
|
+
try await storage.clear(upTo: height)
|
|
1037
|
+
logger.info("Cache removed upTo \(height)")
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
private func clearCompactBlockCache() async throws {
|
|
1041
|
+
await blockDownloader.stopDownload()
|
|
1042
|
+
try await storage.clear()
|
|
1043
|
+
logger.info("Cache removed")
|
|
1044
|
+
}
|
|
1045
|
+
|
|
697
1046
|
private func setTimer() async {
|
|
698
|
-
let interval = config.blockPollInterval
|
|
1047
|
+
let interval = self.config.blockPollInterval
|
|
699
1048
|
self.backoffTimer?.invalidate()
|
|
700
1049
|
let timer = Timer(
|
|
701
1050
|
timeInterval: interval,
|
|
@@ -703,65 +1052,77 @@ extension CompactBlockProcessor {
|
|
|
703
1052
|
block: { [weak self] _ in
|
|
704
1053
|
Task { [weak self] in
|
|
705
1054
|
guard let self else { return }
|
|
706
|
-
|
|
707
|
-
|
|
1055
|
+
switch await self.state {
|
|
1056
|
+
case .syncing, .enhancing, .fetching, .handlingSaplingFiles:
|
|
1057
|
+
await self.latestBlocksDataProvider.updateBlockData()
|
|
1058
|
+
case .stopped, .error, .synced:
|
|
1059
|
+
if await self.shouldStart {
|
|
708
1060
|
self.logger.debug(
|
|
709
1061
|
"""
|
|
710
1062
|
Timer triggered: Starting compact Block processor!.
|
|
711
|
-
Processor State: \(await self.
|
|
1063
|
+
Processor State: \(await self.state)
|
|
1064
|
+
latestHeight: \(try await self.transactionRepository.lastScannedHeight())
|
|
712
1065
|
attempts: \(await self.retryAttempts)
|
|
713
1066
|
"""
|
|
714
1067
|
)
|
|
715
1068
|
await self.start()
|
|
716
|
-
} else if await self.
|
|
717
|
-
await self.
|
|
1069
|
+
} else if await self.maxAttemptsReached {
|
|
1070
|
+
await self.fail(ZcashError.compactBlockProcessorMaxAttemptsReached(self.config.retries))
|
|
718
1071
|
}
|
|
719
|
-
} else {
|
|
720
|
-
await self.latestBlocksDataProvider.updateBlockData()
|
|
721
1072
|
}
|
|
722
1073
|
}
|
|
723
1074
|
}
|
|
724
1075
|
)
|
|
725
1076
|
RunLoop.main.add(timer, forMode: .default)
|
|
1077
|
+
|
|
726
1078
|
self.backoffTimer = timer
|
|
727
1079
|
}
|
|
1080
|
+
|
|
1081
|
+
private func transitionState(from oldValue: State, to newValue: State) async {
|
|
1082
|
+
guard oldValue != newValue else {
|
|
1083
|
+
return
|
|
1084
|
+
}
|
|
728
1085
|
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
private func stopAllActions() async {
|
|
747
|
-
for action in actions.values {
|
|
748
|
-
await action.stop()
|
|
1086
|
+
switch newValue {
|
|
1087
|
+
case .error(let err):
|
|
1088
|
+
await notifyError(err)
|
|
1089
|
+
case .stopped:
|
|
1090
|
+
await send(event: .stopped)
|
|
1091
|
+
case .enhancing:
|
|
1092
|
+
await send(event: .startedEnhancing)
|
|
1093
|
+
case .fetching:
|
|
1094
|
+
await send(event: .startedFetching)
|
|
1095
|
+
case .handlingSaplingFiles:
|
|
1096
|
+
// We don't report this to outside world as separate phase for now.
|
|
1097
|
+
break
|
|
1098
|
+
case .synced:
|
|
1099
|
+
// transition to this state is handled by `processingFinished(height: BlockHeight)`
|
|
1100
|
+
break
|
|
1101
|
+
case .syncing:
|
|
1102
|
+
await send(event: .startedSyncing)
|
|
749
1103
|
}
|
|
750
1104
|
}
|
|
751
1105
|
|
|
752
|
-
private func
|
|
753
|
-
|
|
754
|
-
do {
|
|
755
|
-
try await clearCompactBlockCache()
|
|
756
|
-
} catch {
|
|
757
|
-
logger.error("`clearCompactBlockCache` failed after error: \(error)")
|
|
758
|
-
}
|
|
1106
|
+
private func notifyError(_ err: Error) async {
|
|
1107
|
+
await send(event: .failed(err))
|
|
759
1108
|
}
|
|
1109
|
+
// TODO: [#713] encapsulate service errors better, https://github.com/zcash/ZcashLightClientKit/issues/713
|
|
1110
|
+
}
|
|
760
1111
|
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
1112
|
+
extension CompactBlockProcessor.State: Equatable {
|
|
1113
|
+
public static func == (lhs: CompactBlockProcessor.State, rhs: CompactBlockProcessor.State) -> Bool {
|
|
1114
|
+
switch (lhs, rhs) {
|
|
1115
|
+
case
|
|
1116
|
+
(.syncing, .syncing),
|
|
1117
|
+
(.stopped, .stopped),
|
|
1118
|
+
(.error, .error),
|
|
1119
|
+
(.synced, .synced),
|
|
1120
|
+
(.enhancing, .enhancing),
|
|
1121
|
+
(.fetching, .fetching):
|
|
1122
|
+
return true
|
|
1123
|
+
default:
|
|
1124
|
+
return false
|
|
1125
|
+
}
|
|
765
1126
|
}
|
|
766
1127
|
}
|
|
767
1128
|
|
|
@@ -837,17 +1198,168 @@ extension CompactBlockProcessor {
|
|
|
837
1198
|
}
|
|
838
1199
|
}
|
|
839
1200
|
|
|
840
|
-
|
|
1201
|
+
extension CompactBlockProcessor {
|
|
1202
|
+
enum NextState: Equatable {
|
|
1203
|
+
case finishProcessing(height: BlockHeight)
|
|
1204
|
+
case processNewBlocks(ranges: SyncRanges)
|
|
1205
|
+
case wait(latestHeight: BlockHeight, latestDownloadHeight: BlockHeight)
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
@discardableResult
|
|
1209
|
+
func figureNextBatch(
|
|
1210
|
+
downloaderService: BlockDownloaderService
|
|
1211
|
+
) async throws -> NextState {
|
|
1212
|
+
try Task.checkCancellation()
|
|
1213
|
+
|
|
1214
|
+
do {
|
|
1215
|
+
return try await CompactBlockProcessor.NextStateHelper.nextState(
|
|
1216
|
+
service: service,
|
|
1217
|
+
downloaderService: downloaderService,
|
|
1218
|
+
latestBlocksDataProvider: latestBlocksDataProvider,
|
|
1219
|
+
config: config,
|
|
1220
|
+
rustBackend: rustBackend,
|
|
1221
|
+
internalSyncProgress: internalSyncProgress
|
|
1222
|
+
)
|
|
1223
|
+
} catch {
|
|
1224
|
+
throw error
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
extension CompactBlockProcessor {
|
|
1230
|
+
enum NextStateHelper {
|
|
1231
|
+
// swiftlint:disable:next function_parameter_count
|
|
1232
|
+
static func nextState(
|
|
1233
|
+
service: LightWalletService,
|
|
1234
|
+
downloaderService: BlockDownloaderService,
|
|
1235
|
+
latestBlocksDataProvider: LatestBlocksDataProvider,
|
|
1236
|
+
config: Configuration,
|
|
1237
|
+
rustBackend: ZcashRustBackendWelding,
|
|
1238
|
+
internalSyncProgress: InternalSyncProgress
|
|
1239
|
+
) async throws -> CompactBlockProcessor.NextState {
|
|
1240
|
+
// It should be ok to not create new Task here because this method is already async. But for some reason something not good happens
|
|
1241
|
+
// when Task is not created here. For example tests start failing. Reason is unknown at this time.
|
|
1242
|
+
let task = Task(priority: .userInitiated) {
|
|
1243
|
+
let info = try await service.getInfo()
|
|
1244
|
+
|
|
1245
|
+
try await CompactBlockProcessor.validateServerInfo(
|
|
1246
|
+
info,
|
|
1247
|
+
saplingActivation: config.saplingActivation,
|
|
1248
|
+
localNetwork: config.network,
|
|
1249
|
+
rustBackend: rustBackend
|
|
1250
|
+
)
|
|
1251
|
+
|
|
1252
|
+
let latestDownloadHeight = try await downloaderService.lastDownloadedBlockHeight()
|
|
1253
|
+
|
|
1254
|
+
await internalSyncProgress.migrateIfNeeded(latestDownloadedBlockHeightFromCacheDB: latestDownloadHeight)
|
|
1255
|
+
|
|
1256
|
+
await latestBlocksDataProvider.updateScannedData()
|
|
1257
|
+
await latestBlocksDataProvider.updateBlockData()
|
|
1258
|
+
|
|
1259
|
+
return await internalSyncProgress.computeNextState(
|
|
1260
|
+
latestBlockHeight: latestBlocksDataProvider.latestBlockHeight,
|
|
1261
|
+
latestScannedHeight: latestBlocksDataProvider.latestScannedHeight,
|
|
1262
|
+
walletBirthday: config.walletBirthday
|
|
1263
|
+
)
|
|
1264
|
+
}
|
|
841
1265
|
|
|
1266
|
+
return try await task.value
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
/// This extension contains asociated types and functions needed to clean up the
|
|
1272
|
+
/// `cacheDb` in favor of `FsBlockDb`. Once this cleanup functionality is deprecated,
|
|
1273
|
+
/// delete the whole extension and reference to it in other parts of the code including tests.
|
|
842
1274
|
extension CompactBlockProcessor {
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
1275
|
+
public enum CacheDbMigrationError: Error {
|
|
1276
|
+
case fsCacheMigrationFailedSameURL
|
|
1277
|
+
case failedToDeleteLegacyDb(Error)
|
|
1278
|
+
case failedToInitFsBlockDb(Error)
|
|
1279
|
+
case failedToSetDownloadHeight(Error)
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
/// Deletes the SQLite cacheDb and attempts to initialize the fsBlockDbRoot
|
|
1283
|
+
/// - parameter legacyCacheDbURL: the URL where the cache Db used to be stored.
|
|
1284
|
+
/// - Throws: `InitializerError.fsCacheInitFailedSameURL` when the given URL
|
|
1285
|
+
/// is the same URL than the one provided as `self.fsBlockDbRoot` assuming that's a
|
|
1286
|
+
/// programming error being the `legacyCacheDbURL` a sqlite database file and not a
|
|
1287
|
+
/// directory. Also throws errors from initializing the fsBlockDbRoot.
|
|
1288
|
+
///
|
|
1289
|
+
/// - Note: Errors from deleting the `legacyCacheDbURL` won't be throwns.
|
|
1290
|
+
func migrateCacheDb(_ legacyCacheDbURL: URL) async throws {
|
|
1291
|
+
guard legacyCacheDbURL != config.fsBlockCacheRoot else {
|
|
1292
|
+
throw ZcashError.compactBlockProcessorCacheDbMigrationFsCacheMigrationFailedSameURL
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
// Instance with alias `default` is same as instance before the Alias was introduced. So it makes sense that only this instance handles
|
|
1296
|
+
// legacy cache DB. Any instance with different than `default` alias was created after the Alias was introduced and at this point legacy
|
|
1297
|
+
// cache DB is't anymore. So there is nothing to migrate for instances with not default Alias.
|
|
1298
|
+
guard config.alias == .default else {
|
|
1299
|
+
return
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
// if the URL provided is not readable, it means that the client has a reference
|
|
1303
|
+
// to the cacheDb file but it has been deleted in a prior sync cycle. there's
|
|
1304
|
+
// nothing to do here.
|
|
1305
|
+
guard FileManager.default.isReadableFile(atPath: legacyCacheDbURL.path) else {
|
|
1306
|
+
return
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
do {
|
|
1310
|
+
// if there's a readable file at the provided URL, delete it.
|
|
1311
|
+
try FileManager.default.removeItem(at: legacyCacheDbURL)
|
|
1312
|
+
} catch {
|
|
1313
|
+
throw ZcashError.compactBlockProcessorCacheDbMigrationFailedToDeleteLegacyDb(error)
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
// create the storage
|
|
1317
|
+
try await self.storage.create()
|
|
1318
|
+
|
|
1319
|
+
// The database has been deleted, so we have adjust the internal state of the
|
|
1320
|
+
// `CompactBlockProcessor` so that it doesn't rely on download heights set
|
|
1321
|
+
// by a previous processing cycle.
|
|
1322
|
+
let lastScannedHeight = try await transactionRepository.lastScannedHeight()
|
|
1323
|
+
|
|
1324
|
+
await internalSyncProgress.set(lastScannedHeight, .latestDownloadedBlockHeight)
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
func wipeLegacyCacheDbIfNeeded() {
|
|
1328
|
+
guard let cacheDbURL = config.cacheDbURL else {
|
|
1329
|
+
return
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
guard FileManager.default.isDeletableFile(atPath: cacheDbURL.pathExtension) else {
|
|
1333
|
+
return
|
|
847
1334
|
}
|
|
848
1335
|
|
|
849
|
-
|
|
850
|
-
|
|
1336
|
+
try? FileManager.default.removeItem(at: cacheDbURL)
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
extension SyncRanges {
|
|
1341
|
+
/// Tells whether the state represented by these sync ranges evidence some sort of
|
|
1342
|
+
/// outdated state on the cache or the internal state of the compact block processor.
|
|
1343
|
+
///
|
|
1344
|
+
/// - Note: this can mean that the processor has synced over the height that the internal
|
|
1345
|
+
/// state knows of because the sync process was interrupted before it could reflect
|
|
1346
|
+
/// it in the internal state storage. This could happen because of many factors, the
|
|
1347
|
+
/// most feasible being OS shutting down a background process or the user abruptly
|
|
1348
|
+
/// exiting the app.
|
|
1349
|
+
/// - Returns: an ``Optional<BlockHeight>`` where Some represents what's the
|
|
1350
|
+
/// new state the internal state should reflect and indicating that the cache should be cleared
|
|
1351
|
+
/// as well. c`None` means that no action is required.
|
|
1352
|
+
func shouldClearBlockCacheAndUpdateInternalState() -> BlockHeight? {
|
|
1353
|
+
guard self.downloadedButUnscannedRange != nil else {
|
|
1354
|
+
return nil
|
|
851
1355
|
}
|
|
1356
|
+
|
|
1357
|
+
guard
|
|
1358
|
+
let latestScannedHeight = self.latestScannedHeight,
|
|
1359
|
+
let latestDownloadedHeight = self.latestDownloadedBlockHeight,
|
|
1360
|
+
latestScannedHeight > latestDownloadedHeight
|
|
1361
|
+
else { return nil }
|
|
1362
|
+
|
|
1363
|
+
return latestScannedHeight
|
|
852
1364
|
}
|
|
853
1365
|
}
|