react-native-zcash 0.5.0 → 0.6.0

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.
Files changed (156) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +6 -3
  3. package/android/build.gradle +5 -4
  4. package/android/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/2240000.json +8 -0
  5. package/android/src/main/assets/co.electriccoin.zcash/checkpoint/mainnet/2250000.json +8 -0
  6. package/android/src/main/java/app/edge/rnzcash/RNZcashModule.kt +94 -121
  7. package/ios/RNZcash.m +1 -12
  8. package/ios/RNZcash.swift +119 -152
  9. package/ios/ZCashLightClientKit/Block/Actions/Action.swift +98 -0
  10. package/ios/ZCashLightClientKit/Block/Actions/ClearAlreadyScannedBlocksAction.swift +35 -0
  11. package/ios/ZCashLightClientKit/Block/Actions/ClearCacheAction.swift +30 -0
  12. package/ios/ZCashLightClientKit/Block/Actions/DownloadAction.swift +67 -0
  13. package/ios/ZCashLightClientKit/Block/Actions/EnhanceAction.swift +97 -0
  14. package/ios/ZCashLightClientKit/Block/Actions/FetchUTXOsAction.swift +33 -0
  15. package/ios/ZCashLightClientKit/Block/Actions/MigrateLegacyCacheDBAction.swift +70 -0
  16. package/ios/ZCashLightClientKit/Block/Actions/ProcessSuggestedScanRangesAction.swift +59 -0
  17. package/ios/ZCashLightClientKit/Block/Actions/RewindAction.swift +48 -0
  18. package/ios/ZCashLightClientKit/Block/Actions/SaplingParamsAction.swift +33 -0
  19. package/ios/ZCashLightClientKit/Block/Actions/ScanAction.swift +95 -0
  20. package/ios/ZCashLightClientKit/Block/Actions/UpdateChainTipAction.swift +55 -0
  21. package/ios/ZCashLightClientKit/Block/Actions/UpdateSubtreeRootsAction.swift +58 -0
  22. package/ios/ZCashLightClientKit/Block/Actions/ValidateServerAction.swift +60 -0
  23. package/ios/ZCashLightClientKit/Block/CompactBlockProcessor.swift +421 -937
  24. package/ios/ZCashLightClientKit/Block/Download/BlockDownloader.swift +31 -17
  25. package/ios/ZCashLightClientKit/Block/Download/BlockDownloaderService.swift +2 -2
  26. package/ios/ZCashLightClientKit/Block/Enhance/BlockEnhancer.swift +46 -15
  27. package/ios/ZCashLightClientKit/Block/FetchUnspentTxOutputs/UTXOFetcher.swift +4 -15
  28. package/ios/ZCashLightClientKit/Block/FilesystemStorage/FSCompactBlockRepository.swift +4 -4
  29. package/ios/ZCashLightClientKit/Block/Scan/BlockScanner.swift +10 -35
  30. package/ios/ZCashLightClientKit/Block/Utils/CompactBlockProgress.swift +24 -0
  31. package/ios/ZCashLightClientKit/Block/Utils/SyncControlData.swift +25 -0
  32. package/ios/ZCashLightClientKit/ClosureSynchronizer.swift +1 -2
  33. package/ios/ZCashLightClientKit/CombineSynchronizer.swift +2 -5
  34. package/ios/ZCashLightClientKit/Constants/ZcashSDK.swift +7 -25
  35. package/ios/ZCashLightClientKit/DAO/TransactionDao.swift +40 -42
  36. package/ios/ZCashLightClientKit/DAO/UnspentTransactionOutputDao.swift +13 -4
  37. package/ios/ZCashLightClientKit/Entity/AccountEntity.swift +9 -0
  38. package/ios/ZCashLightClientKit/Entity/TransactionEntity.swift +7 -10
  39. package/ios/ZCashLightClientKit/Error/Sourcery/generateErrorCode.sh +1 -1
  40. package/ios/ZCashLightClientKit/Error/ZcashError.swift +121 -12
  41. package/ios/ZCashLightClientKit/Error/ZcashErrorCode.swift +43 -5
  42. package/ios/ZCashLightClientKit/Error/ZcashErrorCodeDefinition.swift +72 -6
  43. package/ios/ZCashLightClientKit/Extensions/Bool+ToData.swift +15 -0
  44. package/ios/ZCashLightClientKit/Extensions/Data+ToOtherTypes.swift +18 -0
  45. package/ios/ZCashLightClientKit/Extensions/Int+ToData.swift +15 -0
  46. package/ios/ZCashLightClientKit/Initializer.swift +47 -26
  47. package/ios/ZCashLightClientKit/Metrics/SDKMetrics.swift +0 -12
  48. package/ios/ZCashLightClientKit/Model/Checkpoint.swift +12 -0
  49. package/ios/ZCashLightClientKit/Model/ScanProgress.swift +29 -0
  50. package/ios/ZCashLightClientKit/Model/ScanRange.swift +31 -0
  51. package/ios/ZCashLightClientKit/Modules/Service/GRPC/LightWalletGRPCService.swift +15 -0
  52. package/ios/ZCashLightClientKit/Modules/Service/GRPC/ProtoBuf/compact_formats.pb.swift +150 -46
  53. package/ios/ZCashLightClientKit/Modules/Service/GRPC/ProtoBuf/proto/compact_formats.proto +30 -16
  54. package/ios/ZCashLightClientKit/Modules/Service/GRPC/ProtoBuf/proto/service.proto +32 -6
  55. package/ios/ZCashLightClientKit/Modules/Service/GRPC/ProtoBuf/service.grpc.swift +259 -22
  56. package/ios/ZCashLightClientKit/Modules/Service/GRPC/ProtoBuf/service.pb.swift +193 -7
  57. package/ios/ZCashLightClientKit/Modules/Service/LightWalletService.swift +8 -0
  58. package/ios/ZCashLightClientKit/Providers/LatestBlocksDataProvider.swift +18 -28
  59. package/ios/ZCashLightClientKit/Repository/CompactBlockRepository.swift +1 -1
  60. package/ios/ZCashLightClientKit/Repository/TransactionRepository.swift +2 -6
  61. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2092500.json +8 -0
  62. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2095000.json +8 -0
  63. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2097500.json +8 -0
  64. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2102500.json +8 -0
  65. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2105000.json +8 -0
  66. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2107500.json +8 -0
  67. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2112500.json +8 -0
  68. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2115000.json +8 -0
  69. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2117500.json +8 -0
  70. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2122500.json +8 -0
  71. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2125000.json +8 -0
  72. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2127500.json +8 -0
  73. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2132500.json +8 -0
  74. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2135000.json +8 -0
  75. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2137500.json +8 -0
  76. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2142500.json +8 -0
  77. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2145000.json +8 -0
  78. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2147500.json +8 -0
  79. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2152500.json +8 -0
  80. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2155000.json +8 -0
  81. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2157500.json +8 -0
  82. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2162500.json +8 -0
  83. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2165000.json +8 -0
  84. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2167500.json +8 -0
  85. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2172500.json +8 -0
  86. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2175000.json +8 -0
  87. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2177500.json +8 -0
  88. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2182500.json +8 -0
  89. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2185000.json +8 -0
  90. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2187500.json +8 -0
  91. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2192500.json +8 -0
  92. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2195000.json +8 -0
  93. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2197500.json +8 -0
  94. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2202500.json +8 -0
  95. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2205000.json +8 -0
  96. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2207500.json +8 -0
  97. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2212500.json +8 -0
  98. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2215000.json +8 -0
  99. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2217500.json +8 -0
  100. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2222500.json +8 -0
  101. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2225000.json +8 -0
  102. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2227500.json +8 -0
  103. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2230000.json +8 -0
  104. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2232500.json +8 -0
  105. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2235000.json +8 -0
  106. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2237500.json +8 -0
  107. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2240000.json +8 -0
  108. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2242500.json +8 -0
  109. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2245000.json +8 -0
  110. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2247500.json +8 -0
  111. package/ios/ZCashLightClientKit/Resources/checkpoints/mainnet/2250000.json +8 -0
  112. package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2350000.json +8 -0
  113. package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2360000.json +8 -0
  114. package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2370000.json +8 -0
  115. package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2380000.json +8 -0
  116. package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2390000.json +8 -0
  117. package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2400000.json +8 -0
  118. package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2410000.json +8 -0
  119. package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2420000.json +8 -0
  120. package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2430000.json +8 -0
  121. package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2440000.json +8 -0
  122. package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2450000.json +8 -0
  123. package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2460000.json +8 -0
  124. package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2470000.json +8 -0
  125. package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2480000.json +8 -0
  126. package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2490000.json +8 -0
  127. package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2500000.json +8 -0
  128. package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2510000.json +8 -0
  129. package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2520000.json +8 -0
  130. package/ios/ZCashLightClientKit/Resources/checkpoints/testnet/2530000.json +8 -0
  131. package/ios/ZCashLightClientKit/Rust/ZcashRustBackend.swift +293 -158
  132. package/ios/ZCashLightClientKit/Rust/ZcashRustBackendWelding.swift +58 -64
  133. package/ios/ZCashLightClientKit/Rust/zcashlc.h +618 -512
  134. package/ios/ZCashLightClientKit/Synchronizer/ClosureSDKSynchronizer.swift +2 -8
  135. package/ios/ZCashLightClientKit/Synchronizer/CombineSDKSynchronizer.swift +3 -15
  136. package/ios/ZCashLightClientKit/Synchronizer/Dependencies.swift +11 -30
  137. package/ios/ZCashLightClientKit/Synchronizer/SDKSynchronizer.swift +41 -50
  138. package/ios/ZCashLightClientKit/Synchronizer.swift +51 -65
  139. package/ios/ZCashLightClientKit/Transaction/TransactionEncoder.swift +2 -2
  140. package/ios/ZCashLightClientKit/Transaction/WalletTransactionEncoder.swift +7 -7
  141. package/ios/ZCashLightClientKit/Utils/OSLogger.swift +3 -3
  142. package/ios/ZCashLightClientKit/Utils/ZcashFileManager.swift +16 -0
  143. package/ios/libzcashlc.xcframework/Info.plist +9 -5
  144. package/ios/libzcashlc.xcframework/ios-arm64/libzcashlc.a +0 -0
  145. package/ios/libzcashlc.xcframework/ios-arm64_x86_64-simulator/libzcashlc.a +0 -0
  146. package/lib/rnzcash.rn.js +8 -30
  147. package/lib/rnzcash.rn.js.map +1 -1
  148. package/lib/src/react-native.d.ts +2 -4
  149. package/lib/src/types.d.ts +14 -14
  150. package/package.json +1 -1
  151. package/src/react-native.ts +11 -20
  152. package/src/types.ts +20 -23
  153. package/ios/ZCashLightClientKit/Block/Utils/InternalSyncProgress.swift +0 -200
  154. package/ios/ZCashLightClientKit/Block/Validate/BlockValidator.swift +0 -51
  155. package/ios/ZCashLightClientKit/DAO/BlockDao.swift +0 -112
  156. package/ios/ZCashLightClientKit/Entity/BlockProgress.swift +0 -24
package/ios/RNZcash.swift CHANGED
@@ -8,17 +8,21 @@ var SynchronizerMap = [String: WalletSynchronizer]()
8
8
  struct ConfirmedTx {
9
9
  var minedHeight: Int
10
10
  var toAddress: String?
11
+ var raw: String?
11
12
  var rawTransactionId: String
12
13
  var blockTimeInSeconds: Int
13
14
  var value: String
15
+ var fee: String?
14
16
  var memos: Array<String>?
15
17
  var dictionary: [String: Any?] {
16
18
  return [
17
19
  "minedHeight": minedHeight,
18
20
  "toAddress": toAddress,
21
+ "raw": raw,
19
22
  "rawTransactionId": rawTransactionId,
20
23
  "blockTimeInSeconds": blockTimeInSeconds,
21
24
  "value": value,
25
+ "fee": fee,
22
26
  "memos": memos ?? [],
23
27
  ]
24
28
  }
@@ -42,16 +46,10 @@ struct TotalBalances {
42
46
  }
43
47
 
44
48
  struct ProcessorState {
45
- var alias: String
46
- var lastDownloadedHeight: Int
47
- var lastScannedHeight: Int
48
49
  var scanProgress: Int
49
50
  var networkBlockHeight: Int
50
51
  var dictionary: [String: Any] {
51
52
  return [
52
- "alias": alias,
53
- "lastDownloadedHeight": lastDownloadedHeight,
54
- "lastScannedHeight": lastScannedHeight,
55
53
  "scanProgress": scanProgress,
56
54
  "networkBlockHeight": networkBlockHeight,
57
55
  ]
@@ -83,7 +81,7 @@ class RNZcash: RCTEventEmitter {
83
81
  // Synchronizer
84
82
  @objc func initialize(
85
83
  _ seed: String, _ birthdayHeight: Int, _ alias: String, _ networkName: String,
86
- _ defaultHost: String, _ defaultPort: Int, resolver resolve: @escaping RCTPromiseResolveBlock,
84
+ _ defaultHost: String, _ defaultPort: Int, _ newWallet: Bool, resolver resolve: @escaping RCTPromiseResolveBlock,
87
85
  rejecter reject: @escaping RCTPromiseRejectBlock
88
86
  ) {
89
87
  Task {
@@ -92,6 +90,7 @@ class RNZcash: RCTEventEmitter {
92
90
  let initializer = Initializer(
93
91
  cacheDbURL: try! cacheDbURLHelper(alias, network),
94
92
  fsBlockDbRoot: try! fsBlockDbRootURLHelper(alias, network),
93
+ generalStorageURL: try! generalStorageURLHelper(alias, network),
95
94
  dataDbURL: try! dataDbURLHelper(alias, network),
96
95
  endpoint: endpoint,
97
96
  network: network,
@@ -105,12 +104,12 @@ class RNZcash: RCTEventEmitter {
105
104
  let wallet = try WalletSynchronizer(
106
105
  alias: alias, initializer: initializer, emitter: sendToJs)
107
106
  let seedBytes = try Mnemonic.deterministicSeedBytes(from: seed)
108
- let viewingKeys = try deriveUnifiedViewingKey(seed, network)
107
+ let initMode = newWallet ? WalletInitMode.newWallet : WalletInitMode.existingWallet
109
108
 
110
109
  _ = try await wallet.synchronizer.prepare(
111
110
  with: seedBytes,
112
- viewingKeys: [viewingKeys],
113
- walletBirthday: birthdayHeight
111
+ walletBirthday: birthdayHeight,
112
+ for: initMode
114
113
  )
115
114
  try await wallet.synchronizer.start()
116
115
  wallet.subscribe()
@@ -221,7 +220,7 @@ class RNZcash: RCTEventEmitter {
221
220
  memo: sdkMemo
222
221
  )
223
222
 
224
- let tx: NSMutableDictionary = ["txId": broadcastTx.rawID.hexEncodedString()]
223
+ let tx: NSMutableDictionary = ["txId": broadcastTx.rawID.toHexStringTxId()]
225
224
  if broadcastTx.raw != nil {
226
225
  tx["raw"] = broadcastTx.raw?.hexEncodedString()
227
226
  }
@@ -235,97 +234,6 @@ class RNZcash: RCTEventEmitter {
235
234
  }
236
235
  }
237
236
 
238
- @objc func getTransactions(
239
- _ alias: String, _ first: Int, _ last: Int, resolver resolve: @escaping RCTPromiseResolveBlock,
240
- rejecter reject: @escaping RCTPromiseRejectBlock
241
- ) {
242
- Task {
243
- if let wallet = SynchronizerMap[alias] {
244
- if !wallet.fullySynced {
245
- reject("GetTransactionsError", "Wallet is not synced", genericError)
246
- return
247
- }
248
-
249
- var out: [NSDictionary] = []
250
- do {
251
- let txs = try await wallet.synchronizer.allTransactions()
252
- let filterTxs = txs.filter {
253
- $0.minedHeight != nil && $0.minedHeight! >= first && $0.minedHeight! <= last
254
- && $0.blockTime != nil
255
- }
256
- for tx in filterTxs {
257
- var confTx = ConfirmedTx(
258
- minedHeight: tx.minedHeight!,
259
- rawTransactionId: (tx.rawID.toHexStringTxId()),
260
- blockTimeInSeconds: Int(tx.blockTime!),
261
- value: String(describing: abs(tx.value.amount))
262
- )
263
- if tx.isSentTransaction {
264
- let recipients = await wallet.synchronizer.getRecipients(for: tx)
265
- if recipients.count > 0 {
266
- let addresses = recipients.compactMap {
267
- if case let .address(address) = $0 {
268
- return address
269
- } else {
270
- return nil
271
- }
272
- }
273
- if addresses.count > 0 {
274
- confTx.toAddress = addresses.first!.stringEncoded
275
- }
276
- }
277
- }
278
- if tx.memoCount > 0 {
279
- let memos = (try? await wallet.synchronizer.getMemos(for: tx)) ?? []
280
- let textMemos = memos.compactMap {
281
- return $0.toString()
282
- }
283
- confTx.memos = textMemos
284
- }
285
- out.append(confTx.nsDictionary)
286
- }
287
- resolve(out)
288
- } catch {
289
- reject("GetTransactionsError", "Failed to get transactions", genericError)
290
- }
291
- } else {
292
- reject("GetTransactionsError", "Wallet does not exist", genericError)
293
- }
294
- }
295
- }
296
-
297
- @objc func getBalance(
298
- _ alias: String, resolver resolve: @escaping RCTPromiseResolveBlock,
299
- rejecter reject: @escaping RCTPromiseRejectBlock
300
- ) {
301
- Task {
302
- if let wallet = SynchronizerMap[alias] {
303
- do {
304
- let totalShieldedBalance = try await wallet.synchronizer.getShieldedBalance()
305
- let availableShieldedBalance = try await wallet.synchronizer.getShieldedVerifiedBalance()
306
-
307
- let transparentWalletBalance = try await wallet.synchronizer.getTransparentBalance(
308
- accountIndex: 0)
309
- let totalTransparentBalance = transparentWalletBalance.total
310
- let availableTransparentBalance = transparentWalletBalance.verified
311
-
312
- let totalAvailable = availableShieldedBalance + availableTransparentBalance
313
- let totalTotal = totalShieldedBalance + totalTransparentBalance
314
-
315
- let balance = TotalBalances(
316
- availableZatoshi: String(totalAvailable.amount), totalZatoshi: String(totalTotal.amount)
317
- )
318
- resolve(balance.nsDictionary)
319
- return
320
- } catch {
321
- reject("GetShieldedBalanceError", "Error", error)
322
- }
323
- } else {
324
- reject("GetShieldedBalanceError", "Wallet does not exist", genericError)
325
- }
326
- }
327
- }
328
-
329
237
  @objc func rescan(
330
238
  _ alias: String, resolver resolve: @escaping RCTPromiseResolveBlock,
331
239
  rejecter reject: @escaping RCTPromiseRejectBlock
@@ -343,6 +251,7 @@ class RNZcash: RCTEventEmitter {
343
251
  wallet.initializeProcessorState()
344
252
  wallet.cancellables.forEach { $0.cancel() }
345
253
  wallet.subscribe()
254
+ try await wallet.synchronizer.start()
346
255
  resolve(nil)
347
256
  case .failure:
348
257
  reject("RescanError", "Failed to rescan wallet", genericError)
@@ -444,7 +353,7 @@ class RNZcash: RCTEventEmitter {
444
353
  }
445
354
 
446
355
  override func supportedEvents() -> [String] {
447
- return ["StatusEvent", "UpdateEvent"]
356
+ return ["BalanceEvent", "StatusEvent", "TransactionEvent", "UpdateEvent"]
448
357
  }
449
358
  }
450
359
 
@@ -457,6 +366,7 @@ class WalletSynchronizer: NSObject {
457
366
  var restart: Bool
458
367
  var processorState: ProcessorState
459
368
  var cancellables: [AnyCancellable] = []
369
+ var balances: TotalBalances
460
370
 
461
371
  init(alias: String, initializer: Initializer, emitter: @escaping (String, Any) -> Void) throws {
462
372
  self.alias = alias
@@ -466,12 +376,10 @@ class WalletSynchronizer: NSObject {
466
376
  self.fullySynced = false
467
377
  self.restart = false
468
378
  self.processorState = ProcessorState(
469
- alias: self.alias,
470
- lastDownloadedHeight: 0,
471
- lastScannedHeight: 0,
472
379
  scanProgress: 0,
473
380
  networkBlockHeight: 0
474
381
  )
382
+ self.balances = TotalBalances(availableZatoshi: "0", totalZatoshi: "0")
475
383
  }
476
384
 
477
385
  public func subscribe() {
@@ -479,10 +387,25 @@ class WalletSynchronizer: NSObject {
479
387
  .throttle(for: .seconds(0.3), scheduler: DispatchQueue.main, latest: true)
480
388
  .sink(receiveValue: { [weak self] state in self?.updateSyncStatus(event: state) })
481
389
  .store(in: &cancellables)
390
+ self.synchronizer.stateStream
391
+ .sink(receiveValue: { [weak self] state in self?.updateProcessorState(event: state) })
392
+ .store(in: &cancellables)
393
+ self.synchronizer.eventStream
394
+ .sink { SynchronizerEvent in
395
+ switch SynchronizerEvent {
396
+ case .minedTransaction(let transaction):
397
+ self.emitTxs(transactions: [transaction])
398
+ case .foundTransactions(let transactions, _):
399
+ self.emitTxs(transactions: transactions)
400
+ default:
401
+ return
402
+ }
403
+ }
404
+ .store(in: &cancellables)
482
405
  }
483
406
 
484
407
  func updateSyncStatus(event: SynchronizerState) {
485
-
408
+
486
409
  if !self.fullySynced {
487
410
  switch event.internalSyncStatus {
488
411
  case .syncing:
@@ -502,68 +425,104 @@ class WalletSynchronizer: NSObject {
502
425
  let data: NSDictionary = ["alias": self.alias, "name": self.status]
503
426
  emit("StatusEvent", data)
504
427
  }
505
-
506
- updateProcessorState(event: event)
507
428
  }
508
429
 
509
430
  func updateProcessorState(event: SynchronizerState) {
510
- let prevLastDownloadedHeight = self.processorState.lastDownloadedHeight
511
- let prevScanProgress = self.processorState.scanProgress
512
- let prevLastScannedHeight = self.synchronizer.latestState.latestScannedHeight
513
- let prevNetworkBlockHeight = self.processorState.lastScannedHeight
514
-
515
- if event.internalSyncStatus != .synced {
516
- switch event.internalSyncStatus {
517
- case .syncing(let status):
518
- // The SDK emits all zero values just before emitting a SYNCED status so we need to ignore these
519
- if status.targetHeight == 0 {
520
- return
521
- }
522
- self.processorState.lastDownloadedHeight = status.progressHeight
523
- self.processorState.scanProgress = Int(floor(status.progress * 100))
524
- self.processorState.lastScannedHeight = status.progressHeight
525
- self.processorState.networkBlockHeight = status.targetHeight
526
- default:
527
- return
528
- }
529
- } else {
530
- self.processorState.lastDownloadedHeight = self.synchronizer.latestState.latestScannedHeight
531
- self.processorState.scanProgress = 100
532
- self.processorState.lastScannedHeight = self.synchronizer.latestState.latestScannedHeight
533
- self.processorState.networkBlockHeight = event.latestBlockHeight
431
+ var scanProgress = 0
432
+
433
+ switch event.internalSyncStatus {
434
+ case .syncing(let progress):
435
+ scanProgress = Int(floor(progress * 100))
436
+ case .synced:
437
+ scanProgress = 100
438
+ case .unprepared, .disconnected, .stopped:
439
+ scanProgress = 0
440
+ default:
441
+ return
534
442
  }
535
443
 
536
- if self.processorState.lastDownloadedHeight != prevLastDownloadedHeight
537
- || self.processorState.scanProgress != prevScanProgress
538
- || self.processorState.lastScannedHeight != prevLastScannedHeight
539
- || self.processorState.networkBlockHeight != prevNetworkBlockHeight
540
- {
541
- emit("UpdateEvent", self.processorState.nsDictionary)
542
- }
444
+ if (scanProgress == self.processorState.scanProgress && event.latestBlockHeight == self.processorState.networkBlockHeight) { return }
445
+
446
+ self.processorState = ProcessorState(scanProgress: scanProgress, networkBlockHeight: event.latestBlockHeight)
447
+ let data: NSDictionary = ["alias": self.alias, "scanProgress": self.processorState.scanProgress, "networkBlockHeight": self.processorState.networkBlockHeight]
448
+ emit("UpdateEvent", data)
449
+ updateBalanceState(event: event)
543
450
  }
544
451
 
545
452
  func initializeProcessorState() {
546
453
  self.processorState = ProcessorState(
547
- alias: self.alias,
548
- lastDownloadedHeight: 0,
549
- lastScannedHeight: 0,
550
454
  scanProgress: 0,
551
455
  networkBlockHeight: 0
552
456
  )
457
+ self.balances = TotalBalances(availableZatoshi: "0", totalZatoshi: "0")
553
458
  }
554
- }
555
459
 
556
- func z_hexEncodedString(data: Data) -> String {
557
- let hexDigits = Array("0123456789abcdef".utf16)
558
- var chars: [unichar] = []
460
+ func updateBalanceState(event: SynchronizerState) {
461
+ let transparentBalance = event.transparentBalance
462
+ let shieldedBalance = event.shieldedBalance
463
+
464
+ let availableTransparentBalance = transparentBalance.verified
465
+ let totalTransparentBalance = transparentBalance.total
466
+
467
+ let availableShieldedBalance = shieldedBalance.verified
468
+ let totalShieldedBalance = shieldedBalance.total
469
+
470
+ let availableZatoshi = String((availableShieldedBalance + availableTransparentBalance).amount)
471
+ let totalZatoshi = String((totalShieldedBalance + totalTransparentBalance).amount)
472
+
473
+ if (availableZatoshi == self.balances.availableZatoshi && totalZatoshi == self.balances.totalZatoshi) { return }
559
474
 
560
- chars.reserveCapacity(2 * data.count)
561
- for byte in data {
562
- chars.append(hexDigits[Int(byte / 16)])
563
- chars.append(hexDigits[Int(byte % 16)])
475
+ self.balances = TotalBalances(availableZatoshi: availableZatoshi, totalZatoshi: totalZatoshi)
476
+ let data: NSDictionary = ["alias": self.alias, "availableZatoshi": self.balances.availableZatoshi, "totalZatoshi": self.balances.totalZatoshi]
477
+ emit("BalanceEvent", data)
564
478
  }
565
479
 
566
- return String(utf16CodeUnits: chars, count: chars.count)
480
+ func emitTxs(transactions: [ZcashTransaction.Overview]) {
481
+ Task {
482
+ var out: [NSDictionary] = []
483
+ for tx in transactions {
484
+ if (tx.isExpiredUmined ?? false) { continue }
485
+ var confTx = ConfirmedTx(
486
+ minedHeight: tx.minedHeight!,
487
+ rawTransactionId: (tx.rawID.toHexStringTxId()),
488
+ blockTimeInSeconds: Int(tx.blockTime!),
489
+ value: String(describing: abs(tx.value.amount))
490
+ )
491
+ if tx.raw != nil {
492
+ confTx.raw = tx.raw!.hexEncodedString()
493
+ }
494
+ if tx.fee != nil {
495
+ confTx.fee = String(describing: abs(tx.value.amount))
496
+ }
497
+ if tx.isSentTransaction {
498
+ let recipients = await self.synchronizer.getRecipients(for: tx)
499
+ if recipients.count > 0 {
500
+ let addresses = recipients.compactMap {
501
+ if case let .address(address) = $0 {
502
+ return address
503
+ } else {
504
+ return nil
505
+ }
506
+ }
507
+ if addresses.count > 0 {
508
+ confTx.toAddress = addresses.first!.stringEncoded
509
+ }
510
+ }
511
+ }
512
+ if tx.memoCount > 0 {
513
+ let memos = (try? await self.synchronizer.getMemos(for: tx)) ?? []
514
+ let textMemos = memos.compactMap {
515
+ return $0.toString()
516
+ }
517
+ confTx.memos = textMemos
518
+ }
519
+ out.append(confTx.nsDictionary)
520
+ }
521
+
522
+ let data: NSDictionary = ["alias": self.alias, "transactions": NSArray(array: out)]
523
+ emit("TransactionEvent", data)
524
+ }
525
+ }
567
526
  }
568
527
 
569
528
  // Local file helper funcs
@@ -603,3 +562,11 @@ func fsBlockDbRootURLHelper(_ alias: String, _ network: ZcashNetwork) throws ->
603
562
  isDirectory: true
604
563
  )
605
564
  }
565
+
566
+ func generalStorageURLHelper(_ alias: String, _ network: ZcashNetwork) throws -> URL {
567
+ try documentsDirectoryHelper()
568
+ .appendingPathComponent(
569
+ network.constants.defaultDbNamePrefix + alias + "general_storage",
570
+ isDirectory: true
571
+ )
572
+ }
@@ -0,0 +1,98 @@
1
+ //
2
+ // Action.swift
3
+ //
4
+ //
5
+ // Created by Michal Fousek on 05.05.2023.
6
+ //
7
+
8
+ import Foundation
9
+
10
+ protocol ActionContext {
11
+ var state: CBPState { get async }
12
+ var prevState: CBPState? { get async }
13
+ var syncControlData: SyncControlData { get async }
14
+ var requestedRewindHeight: BlockHeight? { get async }
15
+ var processedHeight: BlockHeight { get async }
16
+ var lastChainTipUpdateTime: TimeInterval { get async }
17
+ var lastScannedHeight: BlockHeight? { get async }
18
+ var lastEnhancedHeight: BlockHeight? { get async }
19
+
20
+ func update(state: CBPState) async
21
+ func update(syncControlData: SyncControlData) async
22
+ func update(processedHeight: BlockHeight) async
23
+ func update(lastChainTipUpdateTime: TimeInterval) async
24
+ func update(lastScannedHeight: BlockHeight) async
25
+ func update(lastDownloadedHeight: BlockHeight) async
26
+ func update(lastEnhancedHeight: BlockHeight?) async
27
+ func update(requestedRewindHeight: BlockHeight) async
28
+ }
29
+
30
+ actor ActionContextImpl: ActionContext {
31
+ var state: CBPState
32
+ var prevState: CBPState?
33
+ var syncControlData: SyncControlData
34
+ var requestedRewindHeight: BlockHeight?
35
+ /// Amount of blocks that have been processed so far
36
+ var processedHeight: BlockHeight = 0
37
+ /// Update chain tip must be called repeatadly, this value stores the previous update and help to decide when to call it again
38
+ var lastChainTipUpdateTime: TimeInterval = 0.0
39
+ var lastScannedHeight: BlockHeight?
40
+ var lastDownloadedHeight: BlockHeight?
41
+ var lastEnhancedHeight: BlockHeight?
42
+
43
+ init(state: CBPState) {
44
+ self.state = state
45
+ syncControlData = SyncControlData.empty
46
+ }
47
+
48
+ func update(state: CBPState) async {
49
+ prevState = self.state
50
+ self.state = state
51
+ }
52
+ func update(syncControlData: SyncControlData) async { self.syncControlData = syncControlData }
53
+ func update(processedHeight: BlockHeight) async { self.processedHeight = processedHeight }
54
+ func update(lastChainTipUpdateTime: TimeInterval) async { self.lastChainTipUpdateTime = lastChainTipUpdateTime }
55
+ func update(lastScannedHeight: BlockHeight) async { self.lastScannedHeight = lastScannedHeight }
56
+ func update(lastDownloadedHeight: BlockHeight) async { self.lastDownloadedHeight = lastDownloadedHeight }
57
+ func update(lastEnhancedHeight: BlockHeight?) async { self.lastEnhancedHeight = lastEnhancedHeight }
58
+ func update(requestedRewindHeight: BlockHeight) async { self.requestedRewindHeight = requestedRewindHeight }
59
+ }
60
+
61
+ enum CBPState: CaseIterable {
62
+ case idle
63
+ case migrateLegacyCacheDB
64
+ case validateServer
65
+ case updateSubtreeRoots
66
+ case updateChainTip
67
+ case processSuggestedScanRanges
68
+ case rewind
69
+ case download
70
+ case scan
71
+ case clearAlreadyScannedBlocks
72
+ case enhance
73
+ case fetchUTXO
74
+ case handleSaplingParams
75
+ case clearCache
76
+ case finished
77
+ case failed
78
+ case stopped
79
+ }
80
+
81
+ protocol Action {
82
+ /// If this is true and action fails with error then blocks cache is cleared.
83
+ var removeBlocksCacheWhenFailed: Bool { get }
84
+
85
+ // When any action is created it can get `DIContainer` and resolve any depedencies it requires.
86
+ // Every action uses `context` to get some informartion like download range.
87
+ //
88
+ // `didUpdate` is closure that action use to tell CBP that some part of the work is done. For example if download action would like to
89
+ // update progress on every block downloaded it can use this closure. Also if action doesn't need to update progress on partial work it doesn't
90
+ // need to use this closure at all.
91
+ //
92
+ // Each action updates context accordingly. It should at least set new state. Reason for this is that action can return different states for
93
+ // different conditions. And action is the thing that knows these conditions.
94
+ func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext
95
+
96
+ // Should be called on each existing action when processor wants to stop. Some actions may do it's own background work.
97
+ func stop() async
98
+ }
@@ -0,0 +1,35 @@
1
+ //
2
+ // ClearCacheForLastScannedBatch.swift
3
+ //
4
+ //
5
+ // Created by Michal Fousek on 08.05.2023.
6
+ //
7
+
8
+ import Foundation
9
+
10
+ final class ClearAlreadyScannedBlocksAction {
11
+ let storage: CompactBlockRepository
12
+ let transactionRepository: TransactionRepository
13
+
14
+ init(container: DIContainer) {
15
+ storage = container.resolve(CompactBlockRepository.self)
16
+ transactionRepository = container.resolve(TransactionRepository.self)
17
+ }
18
+ }
19
+
20
+ extension ClearAlreadyScannedBlocksAction: Action {
21
+ var removeBlocksCacheWhenFailed: Bool { false }
22
+
23
+ func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
24
+ guard let lastScannedHeight = await context.lastScannedHeight else {
25
+ throw ZcashError.compactBlockProcessorLastScannedHeight
26
+ }
27
+
28
+ try await storage.clear(upTo: lastScannedHeight)
29
+
30
+ await context.update(state: .enhance)
31
+ return context
32
+ }
33
+
34
+ func stop() async { }
35
+ }
@@ -0,0 +1,30 @@
1
+ //
2
+ // ClearCacheAction.swift
3
+ //
4
+ //
5
+ // Created by Michal Fousek on 05.05.2023.
6
+ //
7
+
8
+ import Foundation
9
+
10
+ final class ClearCacheAction {
11
+ let storage: CompactBlockRepository
12
+
13
+ init(container: DIContainer) {
14
+ storage = container.resolve(CompactBlockRepository.self)
15
+ }
16
+ }
17
+
18
+ extension ClearCacheAction: Action {
19
+ var removeBlocksCacheWhenFailed: Bool { false }
20
+
21
+ func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
22
+ try await storage.clear()
23
+
24
+ await context.update(state: .processSuggestedScanRanges)
25
+
26
+ return context
27
+ }
28
+
29
+ func stop() async { }
30
+ }
@@ -0,0 +1,67 @@
1
+ //
2
+ // DownloadAction.swift
3
+ //
4
+ //
5
+ // Created by Michal Fousek on 05.05.2023.
6
+ //
7
+
8
+ import Foundation
9
+
10
+ final class DownloadAction {
11
+ let configProvider: CompactBlockProcessor.ConfigProvider
12
+ let downloader: BlockDownloader
13
+ let transactionRepository: TransactionRepository
14
+ let logger: Logger
15
+
16
+ init(container: DIContainer, configProvider: CompactBlockProcessor.ConfigProvider) {
17
+ self.configProvider = configProvider
18
+ downloader = container.resolve(BlockDownloader.self)
19
+ transactionRepository = container.resolve(TransactionRepository.self)
20
+ logger = container.resolve(Logger.self)
21
+ }
22
+
23
+ private func update(context: ActionContext) async -> ActionContext {
24
+ await context.update(state: .scan)
25
+ return context
26
+ }
27
+ }
28
+
29
+ extension DownloadAction: Action {
30
+ var removeBlocksCacheWhenFailed: Bool { true }
31
+
32
+ func run(with context: ActionContext, didUpdate: @escaping (CompactBlockProcessor.Event) async -> Void) async throws -> ActionContext {
33
+ guard let lastScannedHeight = await context.lastScannedHeight else {
34
+ return await update(context: context)
35
+ }
36
+
37
+ let config = await configProvider.config
38
+ let latestBlockHeight = await context.syncControlData.latestBlockHeight
39
+ // This action is executed for each batch (batch size is 100 blocks by default) until all the blocks in whole `downloadRange` are downloaded.
40
+ // So the right range for this batch must be computed.
41
+ let batchRangeStart = lastScannedHeight
42
+ let batchRangeEnd = min(latestBlockHeight, batchRangeStart + config.batchSize)
43
+
44
+ guard batchRangeStart <= batchRangeEnd else {
45
+ return await update(context: context)
46
+ }
47
+
48
+ let batchRange = batchRangeStart...batchRangeEnd
49
+ let potentialDownloadLimit = batchRange.upperBound + (2 * config.batchSize)
50
+ let downloadLimit = await context.syncControlData.latestBlockHeight >= potentialDownloadLimit ? potentialDownloadLimit : batchRangeEnd
51
+
52
+ logger.debug("Starting download with range: \(batchRangeStart)...\(batchRangeEnd)")
53
+
54
+ await downloader.update(latestDownloadedBlockHeight: batchRangeStart, force: true)
55
+ try await downloader.setSyncRange(lastScannedHeight...latestBlockHeight, batchSize: config.batchSize)
56
+ await downloader.setDownloadLimit(downloadLimit)
57
+ await downloader.startDownload(maxBlockBufferSize: config.downloadBufferSize)
58
+
59
+ try await downloader.waitUntilRequestedBlocksAreDownloaded(in: batchRange)
60
+
61
+ return await update(context: context)
62
+ }
63
+
64
+ func stop() async {
65
+ await downloader.stopDownload()
66
+ }
67
+ }