2019-11-14 06:38:54 -08:00
|
|
|
//
|
|
|
|
// SDKSynchronizer.swift
|
|
|
|
// ZcashLightClientKit
|
|
|
|
//
|
|
|
|
// Created by Francisco Gindre on 11/6/19.
|
|
|
|
// Copyright © 2019 Electric Coin Company. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
import Foundation
|
2022-11-11 09:46:13 -08:00
|
|
|
import Combine
|
2019-11-14 06:38:54 -08:00
|
|
|
|
2022-08-20 15:10:22 -07:00
|
|
|
/// Synchronizer implementation for UIKit and iOS 13+
|
2021-09-15 05:21:29 -07:00
|
|
|
// swiftlint:disable type_body_length
|
2019-11-14 06:38:54 -08:00
|
|
|
public class SDKSynchronizer: Synchronizer {
|
2024-02-19 12:25:10 -08:00
|
|
|
|
2023-03-22 05:47:32 -07:00
|
|
|
public var alias: ZcashSynchronizerAlias { initializer.alias }
|
2022-11-21 07:22:43 -08:00
|
|
|
|
2023-03-22 05:47:32 -07:00
|
|
|
private lazy var streamsUpdateQueue = { DispatchQueue(label: "streamsUpdateQueue_\(initializer.alias.description)") }()
|
2023-03-15 04:17:43 -07:00
|
|
|
private let stateSubject = CurrentValueSubject<SynchronizerState, Never>(.zero)
|
|
|
|
public var stateStream: AnyPublisher<SynchronizerState, Never> { stateSubject.eraseToAnyPublisher() }
|
|
|
|
public private(set) var latestState: SynchronizerState = .zero
|
|
|
|
|
|
|
|
private let eventSubject = PassthroughSubject<SynchronizerEvent, Never>()
|
|
|
|
public var eventStream: AnyPublisher<SynchronizerEvent, Never> { eventSubject.eraseToAnyPublisher() }
|
|
|
|
|
2023-12-08 02:45:19 -08:00
|
|
|
let metrics: SDKMetrics
|
2023-03-22 05:47:32 -07:00
|
|
|
public let logger: Logger
|
2023-08-09 01:03:36 -07:00
|
|
|
|
2023-03-16 02:11:18 -07:00
|
|
|
// Don't read this variable directly. Use `status` instead. And don't update this variable directly use `updateStatus()` methods instead.
|
2023-04-28 10:13:21 -07:00
|
|
|
private var underlyingStatus: GenericActor<InternalSyncStatus>
|
|
|
|
var status: InternalSyncStatus {
|
2023-03-16 02:11:18 -07:00
|
|
|
get async { await underlyingStatus.value }
|
2019-12-16 14:25:45 -08:00
|
|
|
}
|
2023-02-07 05:22:28 -08:00
|
|
|
|
2023-01-12 04:05:11 -08:00
|
|
|
let blockProcessor: CompactBlockProcessor
|
2023-03-22 05:47:32 -07:00
|
|
|
lazy var blockProcessorEventProcessingQueue = { DispatchQueue(label: "blockProcessorEventProcessingQueue_\(initializer.alias.description)") }()
|
2023-02-07 05:22:28 -08:00
|
|
|
|
2023-04-24 14:15:20 -07:00
|
|
|
public let initializer: Initializer
|
|
|
|
public var connectionState: ConnectionState
|
|
|
|
public let network: ZcashNetwork
|
2023-05-05 10:30:47 -07:00
|
|
|
private let transactionEncoder: TransactionEncoder
|
2023-04-24 14:15:20 -07:00
|
|
|
private let transactionRepository: TransactionRepository
|
|
|
|
|
|
|
|
private let syncSessionIDGenerator: SyncSessionIDGenerator
|
|
|
|
private let syncSession: SyncSession
|
|
|
|
private let syncSessionTicker: SessionTicker
|
2024-02-07 02:48:59 -08:00
|
|
|
var latestBlocksDataProvider: LatestBlocksDataProvider
|
2023-02-07 05:22:28 -08:00
|
|
|
|
2022-06-22 12:45:37 -07:00
|
|
|
/// Creates an SDKSynchronizer instance
|
|
|
|
/// - Parameter initializer: a wallet Initializer object
|
2023-03-10 03:58:28 -08:00
|
|
|
public convenience init(initializer: Initializer) {
|
|
|
|
self.init(
|
2021-09-17 06:49:58 -07:00
|
|
|
status: .unprepared,
|
|
|
|
initializer: initializer,
|
2023-05-05 10:30:47 -07:00
|
|
|
transactionEncoder: WalletTransactionEncoder(initializer: initializer),
|
2021-09-17 06:49:58 -07:00
|
|
|
transactionRepository: initializer.transactionRepository,
|
2023-03-10 03:58:28 -08:00
|
|
|
blockProcessor: CompactBlockProcessor(
|
|
|
|
initializer: initializer,
|
|
|
|
walletBirthdayProvider: { initializer.walletBirthday }
|
2023-03-22 05:47:32 -07:00
|
|
|
),
|
2023-05-01 07:28:59 -07:00
|
|
|
syncSessionTicker: .live
|
2021-09-17 06:49:58 -07:00
|
|
|
)
|
2021-09-15 05:21:29 -07:00
|
|
|
}
|
2022-11-21 07:22:43 -08:00
|
|
|
|
2021-09-15 05:21:29 -07:00
|
|
|
init(
|
2023-04-28 10:13:21 -07:00
|
|
|
status: InternalSyncStatus,
|
2021-09-15 05:21:29 -07:00
|
|
|
initializer: Initializer,
|
2023-05-05 10:30:47 -07:00
|
|
|
transactionEncoder: TransactionEncoder,
|
2021-09-15 05:21:29 -07:00
|
|
|
transactionRepository: TransactionRepository,
|
2023-03-22 05:47:32 -07:00
|
|
|
blockProcessor: CompactBlockProcessor,
|
2023-05-01 07:28:59 -07:00
|
|
|
syncSessionTicker: SessionTicker
|
2023-03-10 03:58:28 -08:00
|
|
|
) {
|
2021-06-15 14:53:21 -07:00
|
|
|
self.connectionState = .idle
|
2023-03-16 02:11:18 -07:00
|
|
|
self.underlyingStatus = GenericActor(status)
|
2020-06-03 16:18:57 -07:00
|
|
|
self.initializer = initializer
|
2023-05-05 10:30:47 -07:00
|
|
|
self.transactionEncoder = transactionEncoder
|
2020-06-03 16:18:57 -07:00
|
|
|
self.transactionRepository = transactionRepository
|
2021-03-08 10:47:36 -08:00
|
|
|
self.blockProcessor = blockProcessor
|
2021-07-26 16:22:30 -07:00
|
|
|
self.network = initializer.network
|
2023-05-01 07:28:59 -07:00
|
|
|
self.metrics = initializer.container.resolve(SDKMetrics.self)
|
2023-03-22 05:47:32 -07:00
|
|
|
self.logger = initializer.logger
|
2023-05-01 07:28:59 -07:00
|
|
|
self.syncSessionIDGenerator = initializer.container.resolve(SyncSessionIDGenerator.self)
|
2023-04-07 05:02:05 -07:00
|
|
|
self.syncSession = SyncSession(.nullID)
|
|
|
|
self.syncSessionTicker = syncSessionTicker
|
2023-05-01 07:28:59 -07:00
|
|
|
self.latestBlocksDataProvider = initializer.container.resolve(LatestBlocksDataProvider.self)
|
2023-04-07 05:02:05 -07:00
|
|
|
|
2023-03-22 05:47:32 -07:00
|
|
|
initializer.lightWalletService.connectionStateChange = { [weak self] oldState, newState in
|
|
|
|
self?.connectivityStateChanged(oldState: oldState, newState: newState)
|
|
|
|
}
|
2023-02-07 05:22:28 -08:00
|
|
|
|
|
|
|
Task(priority: .high) { [weak self] in await self?.subscribeToProcessorEvents(blockProcessor) }
|
2020-06-03 16:18:57 -07:00
|
|
|
}
|
2022-11-21 07:22:43 -08:00
|
|
|
|
2019-11-14 06:38:54 -08:00
|
|
|
deinit {
|
2023-03-22 05:47:32 -07:00
|
|
|
UsedAliasesChecker.stopUsing(alias: initializer.alias, id: initializer.id)
|
2022-10-27 03:51:38 -07:00
|
|
|
Task { [blockProcessor] in
|
|
|
|
await blockProcessor.stop()
|
|
|
|
}
|
2019-11-14 06:38:54 -08:00
|
|
|
}
|
2022-11-21 07:22:43 -08:00
|
|
|
|
2023-05-24 06:24:09 -07:00
|
|
|
func updateStatus(_ newValue: InternalSyncStatus, updateExternalStatus: Bool = true) async {
|
2023-03-16 02:11:18 -07:00
|
|
|
let oldValue = await underlyingStatus.update(newValue)
|
2023-12-08 02:45:19 -08:00
|
|
|
logger.info("Synchronizer's status updated from \(oldValue) to \(newValue)")
|
2023-05-24 06:24:09 -07:00
|
|
|
await notify(oldStatus: oldValue, newStatus: newValue, updateExternalStatus: updateExternalStatus)
|
2023-03-16 02:11:18 -07:00
|
|
|
}
|
|
|
|
|
2023-03-22 05:47:32 -07:00
|
|
|
func throwIfUnprepared() throws {
|
2023-04-28 10:13:21 -07:00
|
|
|
if !latestState.internalSyncStatus.isPrepared {
|
2023-04-24 14:15:20 -07:00
|
|
|
throw ZcashError.synchronizerNotPrepared
|
2023-03-22 05:47:32 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-24 14:15:20 -07:00
|
|
|
func checkIfCanContinueInitialisation() -> ZcashError? {
|
2023-03-22 05:47:32 -07:00
|
|
|
if let initialisationError = initializer.urlsParsingError {
|
|
|
|
return initialisationError
|
|
|
|
}
|
|
|
|
|
|
|
|
if !UsedAliasesChecker.tryToUse(alias: initializer.alias, id: initializer.id) {
|
2023-04-24 14:15:20 -07:00
|
|
|
return .initializerAliasAlreadyInUse(initializer.alias)
|
2023-03-22 05:47:32 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-03-10 03:58:28 -08:00
|
|
|
public func prepare(
|
|
|
|
with seed: [UInt8]?,
|
2023-09-08 06:40:52 -07:00
|
|
|
walletBirthday: BlockHeight,
|
|
|
|
for walletMode: WalletInitMode
|
2023-03-16 02:11:18 -07:00
|
|
|
) async throws -> Initializer.InitializationResult {
|
|
|
|
guard await status == .unprepared else { return .success }
|
2023-03-10 03:58:28 -08:00
|
|
|
|
2023-03-22 05:47:32 -07:00
|
|
|
if let error = checkIfCanContinueInitialisation() {
|
|
|
|
throw error
|
|
|
|
}
|
|
|
|
|
2023-09-08 06:40:52 -07:00
|
|
|
if case .seedRequired = try await self.initializer.initialize(with: seed, walletBirthday: walletBirthday, for: walletMode) {
|
2022-08-24 08:38:42 -07:00
|
|
|
return .seedRequired
|
|
|
|
}
|
2023-07-30 23:51:04 -07:00
|
|
|
|
2023-04-14 00:08:37 -07:00
|
|
|
await latestBlocksDataProvider.updateWalletBirthday(initializer.walletBirthday)
|
|
|
|
await latestBlocksDataProvider.updateScannedData()
|
|
|
|
|
2023-05-24 06:24:09 -07:00
|
|
|
await updateStatus(.disconnected, updateExternalStatus: false)
|
2023-03-15 04:17:43 -07:00
|
|
|
|
2022-08-24 08:38:42 -07:00
|
|
|
return .success
|
2021-05-05 12:08:57 -07:00
|
|
|
}
|
2021-09-17 06:49:58 -07:00
|
|
|
|
2022-06-22 12:45:37 -07:00
|
|
|
/// Starts the synchronizer
|
2023-04-24 14:15:20 -07:00
|
|
|
/// - Throws: ZcashError when failures occur
|
2023-03-16 02:11:18 -07:00
|
|
|
public func start(retry: Bool = false) async throws {
|
|
|
|
switch await status {
|
2021-05-05 12:08:57 -07:00
|
|
|
case .unprepared:
|
2023-04-24 14:15:20 -07:00
|
|
|
throw ZcashError.synchronizerNotPrepared
|
2021-09-17 06:49:58 -07:00
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
case .syncing:
|
2023-03-22 05:47:32 -07:00
|
|
|
logger.warn("warning: Synchronizer started when already running. Next sync process will be started when the current one stops.")
|
2023-03-16 02:11:18 -07:00
|
|
|
/// This may look strange but `CompactBlockProcessor` has mechanisms which can handle this situation. So we are fine with calling
|
|
|
|
/// it's start here.
|
|
|
|
await blockProcessor.start(retry: retry)
|
2021-09-17 06:49:58 -07:00
|
|
|
|
2021-09-15 05:21:29 -07:00
|
|
|
case .stopped, .synced, .disconnected, .error:
|
2024-01-31 10:16:39 -08:00
|
|
|
let syncProgress = (try? await initializer.rustBackend.getWalletSummary()?.scanProgress?.progress()) ?? 0
|
|
|
|
await updateStatus(.syncing(syncProgress))
|
2023-03-16 02:11:18 -07:00
|
|
|
await blockProcessor.start(retry: retry)
|
2020-08-10 15:19:59 -07:00
|
|
|
}
|
2019-11-14 06:38:54 -08:00
|
|
|
}
|
2022-06-22 12:45:37 -07:00
|
|
|
|
|
|
|
/// Stops the synchronizer
|
2023-03-29 11:28:24 -07:00
|
|
|
public func stop() {
|
|
|
|
// Calling `await blockProcessor.stop()` make take some time. If the downloading of blocks is in progress then this method inside waits until
|
|
|
|
// downloading is really done. Which could block execution of the code on the client side. So it's better strategy to spin up new task and
|
|
|
|
// exit fast on client side.
|
|
|
|
Task(priority: .high) {
|
|
|
|
let status = await self.status
|
|
|
|
guard status != .stopped, status != .disconnected else {
|
|
|
|
logger.info("attempted to stop when status was: \(status)")
|
|
|
|
return
|
|
|
|
}
|
2022-11-21 07:22:43 -08:00
|
|
|
|
2023-03-29 11:28:24 -07:00
|
|
|
await blockProcessor.stop()
|
|
|
|
}
|
2019-11-14 06:38:54 -08:00
|
|
|
}
|
2022-11-21 07:22:43 -08:00
|
|
|
|
2023-02-07 05:22:28 -08:00
|
|
|
// MARK: Connectivity State
|
2021-09-17 06:49:58 -07:00
|
|
|
|
2023-03-22 05:47:32 -07:00
|
|
|
func connectivityStateChanged(oldState: ConnectionState, newState: ConnectionState) {
|
|
|
|
connectionState = newState
|
2023-03-15 04:17:43 -07:00
|
|
|
streamsUpdateQueue.async { [weak self] in
|
2023-03-22 05:47:32 -07:00
|
|
|
self?.eventSubject.send(.connectionStateChanged(newState))
|
2023-03-15 04:17:43 -07:00
|
|
|
}
|
2021-06-14 16:38:05 -07:00
|
|
|
}
|
2022-11-21 07:22:43 -08:00
|
|
|
|
2023-02-07 05:22:28 -08:00
|
|
|
// MARK: Handle CompactBlockProcessor.Flow
|
|
|
|
|
|
|
|
private func subscribeToProcessorEvents(_ processor: CompactBlockProcessor) async {
|
2023-03-16 02:11:18 -07:00
|
|
|
let eventClosure: CompactBlockProcessor.EventClosure = { [weak self] event in
|
|
|
|
switch event {
|
|
|
|
case let .failed(error):
|
|
|
|
await self?.failed(error: error)
|
2023-02-07 05:22:28 -08:00
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
case let .finished(height):
|
|
|
|
await self?.finished(lastScannedHeight: height)
|
2023-02-07 05:22:28 -08:00
|
|
|
|
2023-03-16 02:11:18 -07:00
|
|
|
case let .foundTransactions(transactions, range):
|
|
|
|
self?.foundTransactions(transactions: transactions, in: range)
|
2023-02-07 05:22:28 -08:00
|
|
|
|
2023-03-16 02:11:18 -07:00
|
|
|
case let .handledReorg(reorgHeight, rewindHeight):
|
2023-05-05 10:30:47 -07:00
|
|
|
// log reorg information
|
|
|
|
self?.logger.info("handling reorg at: \(reorgHeight) with rewind height: \(rewindHeight)")
|
2023-02-07 05:22:28 -08:00
|
|
|
|
2023-03-16 02:11:18 -07:00
|
|
|
case let .progressUpdated(progress):
|
|
|
|
await self?.progressUpdated(progress: progress)
|
2023-02-07 05:22:28 -08:00
|
|
|
|
2023-09-11 00:29:21 -07:00
|
|
|
case .syncProgress:
|
2023-05-05 08:04:13 -07:00
|
|
|
break
|
|
|
|
|
2023-03-16 02:11:18 -07:00
|
|
|
case let .storedUTXOs(utxos):
|
|
|
|
self?.storedUTXOs(utxos: utxos)
|
2023-02-07 05:22:28 -08:00
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
case .startedEnhancing, .startedFetching, .startedSyncing:
|
|
|
|
break
|
2023-02-07 05:22:28 -08:00
|
|
|
|
2023-03-16 02:11:18 -07:00
|
|
|
case .stopped:
|
|
|
|
await self?.updateStatus(.stopped)
|
2023-05-10 13:13:29 -07:00
|
|
|
|
|
|
|
case .minedTransaction(let transaction):
|
|
|
|
self?.notifyMinedTransaction(transaction)
|
2023-02-07 05:22:28 -08:00
|
|
|
}
|
2023-03-16 02:11:18 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
await processor.updateEventClosure(identifier: "SDKSynchronizer", closure: eventClosure)
|
2023-02-07 05:22:28 -08:00
|
|
|
}
|
|
|
|
|
2023-04-24 14:15:20 -07:00
|
|
|
private func failed(error: Error) async {
|
|
|
|
await updateStatus(.error(error))
|
2023-02-07 05:22:28 -08:00
|
|
|
}
|
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
private func finished(lastScannedHeight: BlockHeight) async {
|
2023-04-14 00:08:37 -07:00
|
|
|
await latestBlocksDataProvider.updateScannedData()
|
2023-05-05 10:30:47 -07:00
|
|
|
|
2023-03-16 02:11:18 -07:00
|
|
|
await updateStatus(.synced)
|
2023-02-07 05:22:28 -08:00
|
|
|
}
|
2021-09-17 06:49:58 -07:00
|
|
|
|
2023-02-07 05:22:28 -08:00
|
|
|
private func foundTransactions(transactions: [ZcashTransaction.Overview], in range: CompactBlockRange) {
|
2023-03-15 04:17:43 -07:00
|
|
|
streamsUpdateQueue.async { [weak self] in
|
|
|
|
self?.eventSubject.send(.foundTransactions(transactions, range))
|
|
|
|
}
|
2020-10-20 07:06:31 -07:00
|
|
|
}
|
2022-11-21 07:22:43 -08:00
|
|
|
|
2023-05-05 08:04:13 -07:00
|
|
|
private func progressUpdated(progress: Float) async {
|
2023-04-28 10:13:21 -07:00
|
|
|
let newStatus = InternalSyncStatus(progress)
|
2023-03-16 02:11:18 -07:00
|
|
|
await updateStatus(newStatus)
|
2019-11-14 06:38:54 -08:00
|
|
|
}
|
2022-11-21 07:22:43 -08:00
|
|
|
|
2023-02-07 05:22:28 -08:00
|
|
|
private func storedUTXOs(utxos: (inserted: [UnspentTransactionOutputEntity], skipped: [UnspentTransactionOutputEntity])) {
|
2023-03-15 04:17:43 -07:00
|
|
|
streamsUpdateQueue.async { [weak self] in
|
|
|
|
self?.eventSubject.send(.storedUTXOs(utxos.inserted, utxos.skipped))
|
|
|
|
}
|
2019-11-14 06:38:54 -08:00
|
|
|
}
|
2022-11-21 07:22:43 -08:00
|
|
|
|
2019-12-06 04:38:47 -08:00
|
|
|
// MARK: Synchronizer methods
|
2022-11-21 07:22:43 -08:00
|
|
|
|
2024-02-20 16:54:34 -08:00
|
|
|
public func proposeTransfer(accountIndex: Int, recipient: Recipient, amount: Zatoshi, memo: Memo?) async throws -> Proposal {
|
|
|
|
try throwIfUnprepared()
|
|
|
|
|
|
|
|
if case Recipient.transparent = recipient, memo != nil {
|
|
|
|
throw ZcashError.synchronizerSendMemoToTransparentAddress
|
|
|
|
}
|
|
|
|
|
|
|
|
let proposal = try await transactionEncoder.proposeTransfer(
|
|
|
|
accountIndex: accountIndex,
|
|
|
|
recipient: recipient.stringEncoded,
|
|
|
|
amount: amount,
|
|
|
|
memoBytes: memo?.asMemoBytes()
|
|
|
|
)
|
|
|
|
|
|
|
|
return proposal
|
|
|
|
}
|
|
|
|
|
2024-02-28 08:23:12 -08:00
|
|
|
public func proposeShielding(
|
|
|
|
accountIndex: Int,
|
|
|
|
shieldingThreshold: Zatoshi,
|
|
|
|
memo: Memo,
|
|
|
|
transparentReceiver: TransparentAddress? = nil
|
|
|
|
) async throws -> Proposal? {
|
2024-02-20 16:54:34 -08:00
|
|
|
try throwIfUnprepared()
|
|
|
|
|
2024-02-19 12:25:10 -08:00
|
|
|
return try await transactionEncoder.proposeShielding(
|
2024-02-20 16:54:34 -08:00
|
|
|
accountIndex: accountIndex,
|
|
|
|
shieldingThreshold: shieldingThreshold,
|
2024-02-28 08:23:12 -08:00
|
|
|
memoBytes: memo.asMemoBytes(),
|
|
|
|
transparentReceiver: transparentReceiver?.stringEncoded
|
2024-02-20 16:54:34 -08:00
|
|
|
)
|
2024-02-19 12:25:10 -08:00
|
|
|
}
|
2024-02-20 16:54:34 -08:00
|
|
|
|
2024-02-19 12:25:10 -08:00
|
|
|
public func proposefulfillingPaymentURI(
|
|
|
|
_ uri: String,
|
|
|
|
accountIndex: Int
|
|
|
|
) async throws -> Proposal {
|
|
|
|
do {
|
|
|
|
try throwIfUnprepared()
|
|
|
|
return try await transactionEncoder.proposeFulfillingPaymentFromURI(
|
|
|
|
uri,
|
|
|
|
accountIndex: accountIndex
|
|
|
|
)
|
|
|
|
} catch ZcashError.rustCreateToAddress(let e) {
|
|
|
|
throw ZcashError.rustProposeTransferFromURI(e)
|
|
|
|
} catch {
|
|
|
|
throw error
|
|
|
|
}
|
2024-02-20 16:54:34 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
public func createProposedTransactions(
|
|
|
|
proposal: Proposal,
|
|
|
|
spendingKey: UnifiedSpendingKey
|
|
|
|
) async throws -> AsyncThrowingStream<TransactionSubmitResult, Error> {
|
|
|
|
try throwIfUnprepared()
|
|
|
|
|
|
|
|
try await SaplingParameterDownloader.downloadParamsIfnotPresent(
|
|
|
|
spendURL: initializer.spendParamsURL,
|
|
|
|
spendSourceURL: initializer.saplingParamsSourceURL.spendParamFileURL,
|
|
|
|
outputURL: initializer.outputParamsURL,
|
|
|
|
outputSourceURL: initializer.saplingParamsSourceURL.outputParamFileURL,
|
|
|
|
logger: logger
|
|
|
|
)
|
|
|
|
|
|
|
|
let transactions = try await transactionEncoder.createProposedTransactions(
|
|
|
|
proposal: proposal,
|
|
|
|
spendingKey: spendingKey
|
|
|
|
)
|
|
|
|
var iterator = transactions.makeIterator()
|
|
|
|
var submitFailed = false
|
|
|
|
|
|
|
|
return AsyncThrowingStream() {
|
|
|
|
guard let transaction = iterator.next() else { return nil }
|
|
|
|
|
|
|
|
if submitFailed {
|
|
|
|
return .notAttempted(txId: transaction.rawID)
|
|
|
|
} else {
|
|
|
|
let encodedTransaction = try transaction.encodedTransaction()
|
|
|
|
|
|
|
|
do {
|
|
|
|
try await self.transactionEncoder.submit(transaction: encodedTransaction)
|
|
|
|
return TransactionSubmitResult.success(txId: transaction.rawID)
|
|
|
|
} catch ZcashError.serviceSubmitFailed(let error) {
|
|
|
|
submitFailed = true
|
|
|
|
return TransactionSubmitResult.grpcFailure(txId: transaction.rawID, error: error)
|
|
|
|
} catch TransactionEncoderError.submitError(let code, let message) {
|
|
|
|
submitFailed = true
|
|
|
|
return TransactionSubmitResult.submitFailure(txId: transaction.rawID, code: code, description: message)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-22 12:45:37 -07:00
|
|
|
public func sendToAddress(
|
2022-10-02 19:11:17 -07:00
|
|
|
spendingKey: UnifiedSpendingKey,
|
2022-06-22 12:45:37 -07:00
|
|
|
zatoshi: Zatoshi,
|
2022-08-20 15:10:22 -07:00
|
|
|
toAddress: Recipient,
|
2022-10-24 05:38:47 -07:00
|
|
|
memo: Memo?
|
2023-05-05 10:30:47 -07:00
|
|
|
) async throws -> ZcashTransaction.Overview {
|
2023-03-22 05:47:32 -07:00
|
|
|
try throwIfUnprepared()
|
|
|
|
|
2023-05-05 10:30:47 -07:00
|
|
|
if case Recipient.transparent = toAddress, memo != nil {
|
|
|
|
throw ZcashError.synchronizerSendMemoToTransparentAddress
|
|
|
|
}
|
|
|
|
|
2023-04-24 14:15:20 -07:00
|
|
|
try await SaplingParameterDownloader.downloadParamsIfnotPresent(
|
|
|
|
spendURL: initializer.spendParamsURL,
|
|
|
|
spendSourceURL: initializer.saplingParamsSourceURL.spendParamFileURL,
|
|
|
|
outputURL: initializer.outputParamsURL,
|
|
|
|
outputSourceURL: initializer.saplingParamsSourceURL.outputParamFileURL,
|
|
|
|
logger: logger
|
|
|
|
)
|
2022-10-24 05:38:47 -07:00
|
|
|
|
2022-09-13 03:19:56 -07:00
|
|
|
return try await createToAddress(
|
2022-06-22 12:45:37 -07:00
|
|
|
spendingKey: spendingKey,
|
2022-09-13 03:19:56 -07:00
|
|
|
zatoshi: zatoshi,
|
2022-09-30 06:45:51 -07:00
|
|
|
recipient: toAddress,
|
2022-10-02 19:11:17 -07:00
|
|
|
memo: memo
|
2022-06-22 12:45:37 -07:00
|
|
|
)
|
|
|
|
}
|
2022-11-21 07:22:43 -08:00
|
|
|
|
2021-09-15 05:21:29 -07:00
|
|
|
public func shieldFunds(
|
2022-10-02 19:11:17 -07:00
|
|
|
spendingKey: UnifiedSpendingKey,
|
2023-02-02 08:58:12 -08:00
|
|
|
memo: Memo,
|
|
|
|
shieldingThreshold: Zatoshi
|
2023-05-05 10:30:47 -07:00
|
|
|
) async throws -> ZcashTransaction.Overview {
|
2023-03-22 05:47:32 -07:00
|
|
|
try throwIfUnprepared()
|
|
|
|
|
2020-12-23 15:01:09 -08:00
|
|
|
// let's see if there are funds to shield
|
2022-10-02 19:11:17 -07:00
|
|
|
let accountIndex = Int(spendingKey.account)
|
2024-02-06 01:38:38 -08:00
|
|
|
|
|
|
|
guard let tBalance = try await self.getAccountBalance(accountIndex: accountIndex)?.unshielded else {
|
|
|
|
throw ZcashError.synchronizerSpendingKeyDoesNotBelongToTheWallet
|
|
|
|
}
|
2023-05-05 10:30:47 -07:00
|
|
|
|
2023-09-07 03:54:47 -07:00
|
|
|
// Verify that at least there are funds for the fee. Ideally this logic will be improved by the shielding wallet.
|
2024-02-06 01:38:38 -08:00
|
|
|
guard tBalance >= self.network.constants.defaultFee() else {
|
2023-04-24 14:15:20 -07:00
|
|
|
throw ZcashError.synchronizerShieldFundsInsuficientTransparentFunds
|
2020-12-23 15:01:09 -08:00
|
|
|
}
|
2023-05-05 10:30:47 -07:00
|
|
|
|
2024-02-28 08:23:12 -08:00
|
|
|
guard let proposal = try await transactionEncoder.proposeShielding(
|
2024-02-20 16:54:34 -08:00
|
|
|
accountIndex: Int(spendingKey.account),
|
2023-04-24 14:15:20 -07:00
|
|
|
shieldingThreshold: shieldingThreshold,
|
2024-02-28 08:23:12 -08:00
|
|
|
memoBytes: memo.asMemoBytes(),
|
|
|
|
transparentReceiver: nil
|
|
|
|
) else { throw ZcashError.synchronizerShieldFundsInsuficientTransparentFunds }
|
2024-02-20 16:54:34 -08:00
|
|
|
|
|
|
|
let transactions = try await transactionEncoder.createProposedTransactions(
|
|
|
|
proposal: proposal,
|
|
|
|
spendingKey: spendingKey
|
2023-04-24 14:15:20 -07:00
|
|
|
)
|
2023-05-05 10:30:47 -07:00
|
|
|
|
2024-02-20 16:54:34 -08:00
|
|
|
assert(transactions.count == 1, "Rust backend doesn't produce multiple transactions yet")
|
|
|
|
let transaction = transactions[0]
|
|
|
|
|
2023-05-05 10:30:47 -07:00
|
|
|
let encodedTx = try transaction.encodedTransaction()
|
|
|
|
|
|
|
|
try await transactionEncoder.submit(transaction: encodedTx)
|
|
|
|
|
|
|
|
return transaction
|
2020-12-23 15:01:09 -08:00
|
|
|
}
|
2021-09-17 06:49:58 -07:00
|
|
|
|
|
|
|
func createToAddress(
|
2022-10-02 19:11:17 -07:00
|
|
|
spendingKey: UnifiedSpendingKey,
|
2022-06-22 12:45:37 -07:00
|
|
|
zatoshi: Zatoshi,
|
2022-09-30 06:45:51 -07:00
|
|
|
recipient: Recipient,
|
2022-10-24 05:38:47 -07:00
|
|
|
memo: Memo?
|
2023-05-05 10:30:47 -07:00
|
|
|
) async throws -> ZcashTransaction.Overview {
|
|
|
|
do {
|
|
|
|
if
|
|
|
|
case .transparent = recipient,
|
|
|
|
memo != nil {
|
|
|
|
throw ZcashError.synchronizerSendMemoToTransparentAddress
|
|
|
|
}
|
|
|
|
|
2024-02-20 16:54:34 -08:00
|
|
|
let proposal = try await transactionEncoder.proposeTransfer(
|
|
|
|
accountIndex: Int(spendingKey.account),
|
|
|
|
recipient: recipient.stringEncoded,
|
|
|
|
amount: zatoshi,
|
|
|
|
memoBytes: memo?.asMemoBytes()
|
|
|
|
)
|
|
|
|
|
|
|
|
let transactions = try await transactionEncoder.createProposedTransactions(
|
|
|
|
proposal: proposal,
|
|
|
|
spendingKey: spendingKey
|
2023-05-05 10:30:47 -07:00
|
|
|
)
|
2022-11-21 07:22:43 -08:00
|
|
|
|
2024-02-20 16:54:34 -08:00
|
|
|
assert(transactions.count == 1, "Rust backend doesn't produce multiple transactions yet")
|
|
|
|
let transaction = transactions[0]
|
|
|
|
|
2023-05-05 10:30:47 -07:00
|
|
|
let encodedTransaction = try transaction.encodedTransaction()
|
|
|
|
|
|
|
|
try await transactionEncoder.submit(transaction: encodedTransaction)
|
|
|
|
|
|
|
|
return transaction
|
|
|
|
} catch {
|
|
|
|
throw error
|
|
|
|
}
|
2019-12-06 04:38:47 -08:00
|
|
|
}
|
2022-11-21 07:22:43 -08:00
|
|
|
|
2023-05-05 10:30:47 -07:00
|
|
|
public func allReceivedTransactions() async throws -> [ZcashTransaction.Overview] {
|
2023-03-27 07:12:06 -07:00
|
|
|
try await transactionRepository.findReceived(offset: 0, limit: Int.max)
|
2019-12-06 04:38:47 -08:00
|
|
|
}
|
2022-11-21 07:22:43 -08:00
|
|
|
|
2023-05-05 10:30:47 -07:00
|
|
|
public func allTransactions() async throws -> [ZcashTransaction.Overview] {
|
2023-03-27 07:12:06 -07:00
|
|
|
return try await transactionRepository.find(offset: 0, limit: Int.max, kind: .all)
|
2019-12-06 04:38:47 -08:00
|
|
|
}
|
2022-11-21 07:22:43 -08:00
|
|
|
|
2023-05-05 10:30:47 -07:00
|
|
|
public func allSentTransactions() async throws -> [ZcashTransaction.Overview] {
|
2023-03-27 07:12:06 -07:00
|
|
|
return try await transactionRepository.findSent(offset: 0, limit: Int.max)
|
2019-12-16 14:25:45 -08:00
|
|
|
}
|
2022-11-21 07:22:43 -08:00
|
|
|
|
2023-05-05 10:30:47 -07:00
|
|
|
public func allTransactions(from transaction: ZcashTransaction.Overview, limit: Int) async throws -> [ZcashTransaction.Overview] {
|
2023-03-27 07:12:06 -07:00
|
|
|
return try await transactionRepository.find(from: transaction, limit: limit, kind: .all)
|
2020-10-19 17:01:46 -07:00
|
|
|
}
|
2022-11-21 07:22:43 -08:00
|
|
|
|
2019-12-16 14:25:45 -08:00
|
|
|
public func paginatedTransactions(of kind: TransactionKind = .all) -> PaginatedTransactionRepository {
|
|
|
|
PagedTransactionRepositoryBuilder.build(initializer: initializer, kind: .all)
|
|
|
|
}
|
2022-11-21 07:22:43 -08:00
|
|
|
|
2023-03-27 07:12:06 -07:00
|
|
|
public func getMemos(for transaction: ZcashTransaction.Overview) async throws -> [Memo] {
|
|
|
|
return try await transactionRepository.findMemos(for: transaction)
|
2023-01-05 00:43:33 -08:00
|
|
|
}
|
|
|
|
|
2023-03-27 07:12:06 -07:00
|
|
|
public func getRecipients(for transaction: ZcashTransaction.Overview) async -> [TransactionRecipient] {
|
2023-09-18 14:23:44 -07:00
|
|
|
return (try? await transactionRepository.getRecipients(for: transaction.rawID)) ?? []
|
2023-02-07 12:03:02 -08:00
|
|
|
}
|
|
|
|
|
2023-05-05 10:30:47 -07:00
|
|
|
public func getTransactionOutputs(for transaction: ZcashTransaction.Overview) async -> [ZcashTransaction.Output] {
|
2023-09-18 14:23:44 -07:00
|
|
|
return (try? await transactionRepository.getTransactionOutputs(for: transaction.rawID)) ?? []
|
2023-02-07 12:03:02 -08:00
|
|
|
}
|
|
|
|
|
2022-10-27 03:51:38 -07:00
|
|
|
public func latestHeight() async throws -> BlockHeight {
|
2023-05-05 08:04:13 -07:00
|
|
|
try await blockProcessor.latestHeight()
|
2020-10-06 16:35:17 -07:00
|
|
|
}
|
2022-11-21 07:22:43 -08:00
|
|
|
|
2022-10-02 19:11:17 -07:00
|
|
|
public func refreshUTXOs(address: TransparentAddress, from height: BlockHeight) async throws -> RefreshedUTXOs {
|
2023-03-22 05:47:32 -07:00
|
|
|
try throwIfUnprepared()
|
|
|
|
return try await blockProcessor.refreshUTXOs(tAddress: address, startHeight: height)
|
2021-03-08 10:47:36 -08:00
|
|
|
}
|
2023-03-30 03:49:28 -07:00
|
|
|
|
2024-02-06 01:38:38 -08:00
|
|
|
public func getAccountBalance(accountIndex: Int = 0) async throws -> AccountBalance? {
|
2024-01-31 10:16:39 -08:00
|
|
|
try await initializer.rustBackend.getWalletSummary()?.accountBalances[UInt32(accountIndex)]
|
|
|
|
}
|
|
|
|
|
2023-03-30 03:49:28 -07:00
|
|
|
public func getUnifiedAddress(accountIndex: Int) async throws -> UnifiedAddress {
|
|
|
|
try await blockProcessor.getUnifiedAddress(accountIndex: accountIndex)
|
2021-04-08 10:18:16 -07:00
|
|
|
}
|
2022-11-21 07:22:43 -08:00
|
|
|
|
2023-03-30 03:49:28 -07:00
|
|
|
public func getSaplingAddress(accountIndex: Int) async throws -> SaplingAddress {
|
|
|
|
try await blockProcessor.getSaplingAddress(accountIndex: accountIndex)
|
2021-04-08 10:18:16 -07:00
|
|
|
}
|
2022-11-21 07:22:43 -08:00
|
|
|
|
2023-03-30 03:49:28 -07:00
|
|
|
public func getTransparentAddress(accountIndex: Int) async throws -> TransparentAddress {
|
|
|
|
try await blockProcessor.getTransparentAddress(accountIndex: accountIndex)
|
2021-04-08 10:18:16 -07:00
|
|
|
}
|
2022-10-02 19:11:17 -07:00
|
|
|
|
2023-03-02 04:19:25 -08:00
|
|
|
// MARK: Rewind
|
2022-11-21 07:22:43 -08:00
|
|
|
|
2023-03-02 04:19:25 -08:00
|
|
|
public func rewind(_ policy: RewindPolicy) -> AnyPublisher<Void, Error> {
|
|
|
|
let subject = PassthroughSubject<Void, Error>()
|
2023-03-22 05:47:32 -07:00
|
|
|
Task(priority: .high) {
|
2023-04-28 10:13:21 -07:00
|
|
|
if !latestState.internalSyncStatus.isPrepared {
|
2023-04-24 14:15:20 -07:00
|
|
|
subject.send(completion: .failure(ZcashError.synchronizerNotPrepared))
|
2023-03-22 05:47:32 -07:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-03-02 04:19:25 -08:00
|
|
|
let height: BlockHeight?
|
2022-11-21 07:22:43 -08:00
|
|
|
|
2023-03-02 04:19:25 -08:00
|
|
|
switch policy {
|
|
|
|
case .quick:
|
|
|
|
height = nil
|
2021-09-17 06:49:58 -07:00
|
|
|
|
2023-03-02 04:19:25 -08:00
|
|
|
case .birthday:
|
|
|
|
let birthday = await self.blockProcessor.config.walletBirthday
|
|
|
|
height = birthday
|
2022-11-21 07:22:43 -08:00
|
|
|
|
2023-03-02 04:19:25 -08:00
|
|
|
case .height(let rewindHeight):
|
|
|
|
height = rewindHeight
|
2022-11-21 07:22:43 -08:00
|
|
|
|
2023-03-02 04:19:25 -08:00
|
|
|
case .transaction(let transaction):
|
|
|
|
guard let txHeight = transaction.anchor(network: self.network) else {
|
2023-04-24 14:15:20 -07:00
|
|
|
throw ZcashError.synchronizerRewindUnknownArchorHeight
|
2023-03-02 04:19:25 -08:00
|
|
|
}
|
|
|
|
height = txHeight
|
2021-03-26 15:56:51 -07:00
|
|
|
}
|
2022-11-21 07:22:43 -08:00
|
|
|
|
2023-03-02 04:19:25 -08:00
|
|
|
let context = AfterSyncHooksManager.RewindContext(
|
|
|
|
height: height,
|
2023-05-05 10:30:47 -07:00
|
|
|
completion: { result in
|
2023-03-02 04:19:25 -08:00
|
|
|
switch result {
|
2023-05-05 10:30:47 -07:00
|
|
|
case .success:
|
|
|
|
subject.send(completion: .finished)
|
2023-03-02 04:19:25 -08:00
|
|
|
|
|
|
|
case let .failure(error):
|
|
|
|
subject.send(completion: .failure(error))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2023-05-21 09:48:29 -07:00
|
|
|
do {
|
|
|
|
try await blockProcessor.rewind(context: context)
|
|
|
|
} catch {
|
|
|
|
subject.send(completion: .failure(error))
|
|
|
|
}
|
2021-03-26 15:56:51 -07:00
|
|
|
}
|
2023-03-02 04:19:25 -08:00
|
|
|
return subject.eraseToAnyPublisher()
|
2021-03-26 15:56:51 -07:00
|
|
|
}
|
2022-11-21 07:22:43 -08:00
|
|
|
|
2023-03-02 04:19:25 -08:00
|
|
|
// MARK: Wipe
|
|
|
|
|
2023-02-20 01:53:04 -08:00
|
|
|
public func wipe() -> AnyPublisher<Void, Error> {
|
2023-03-02 04:19:25 -08:00
|
|
|
let subject = PassthroughSubject<Void, Error>()
|
2023-02-20 01:53:04 -08:00
|
|
|
Task(priority: .high) {
|
2023-03-22 05:47:32 -07:00
|
|
|
if let error = checkIfCanContinueInitialisation() {
|
|
|
|
subject.send(completion: .failure(error))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-02-20 01:53:04 -08:00
|
|
|
let context = AfterSyncHooksManager.WipeContext(
|
|
|
|
prewipe: { [weak self] in
|
2023-05-05 10:30:47 -07:00
|
|
|
self?.transactionEncoder.closeDBConnection()
|
2023-02-20 01:53:04 -08:00
|
|
|
self?.transactionRepository.closeDBConnection()
|
|
|
|
},
|
|
|
|
completion: { [weak self] possibleError in
|
2023-03-16 02:11:18 -07:00
|
|
|
await self?.updateStatus(.unprepared)
|
2023-02-20 01:53:04 -08:00
|
|
|
if let error = possibleError {
|
2023-03-02 04:19:25 -08:00
|
|
|
subject.send(completion: .failure(error))
|
2023-02-20 01:53:04 -08:00
|
|
|
} else {
|
2023-03-02 04:19:25 -08:00
|
|
|
subject.send(completion: .finished)
|
2023-02-20 01:53:04 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
)
|
2022-12-19 06:15:23 -08:00
|
|
|
|
2023-05-21 09:48:29 -07:00
|
|
|
do {
|
|
|
|
try await blockProcessor.wipe(context: context)
|
|
|
|
} catch {
|
|
|
|
subject.send(completion: .failure(error))
|
|
|
|
}
|
2023-02-20 01:53:04 -08:00
|
|
|
}
|
2022-12-19 06:15:23 -08:00
|
|
|
|
2023-03-02 04:19:25 -08:00
|
|
|
return subject.eraseToAnyPublisher()
|
2022-12-19 06:15:23 -08:00
|
|
|
}
|
2023-08-09 01:03:36 -07:00
|
|
|
|
2024-02-07 02:48:59 -08:00
|
|
|
// MARK: Server switch
|
|
|
|
|
|
|
|
public func switchTo(endpoint: LightWalletEndpoint) async throws {
|
|
|
|
// Stop synchronization
|
|
|
|
let status = await self.status
|
|
|
|
if status != .stopped && status != .disconnected {
|
|
|
|
await blockProcessor.stop()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Validation of the server is first because any custom endpoint can be passed here
|
|
|
|
// Extra instance of the service is created with lower timeout ofr a single call
|
|
|
|
initializer.container.register(type: LightWalletService.self, isSingleton: true) { _ in
|
|
|
|
LightWalletGRPCService(
|
|
|
|
host: endpoint.host,
|
|
|
|
port: endpoint.port,
|
|
|
|
secure: endpoint.secure,
|
|
|
|
singleCallTimeout: 5000,
|
|
|
|
streamingCallTimeout: endpoint.streamingCallTimeoutInMillis
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
let validateSever = ValidateServerAction(
|
|
|
|
container: initializer.container,
|
|
|
|
configProvider: CompactBlockProcessor.ConfigProvider(config: await blockProcessor.config)
|
|
|
|
)
|
|
|
|
|
|
|
|
do {
|
|
|
|
_ = try await validateSever.run(with: ActionContextImpl(state: .idle)) { _ in }
|
|
|
|
} catch {
|
|
|
|
throw ZcashError.synchronizerServerSwitch
|
|
|
|
}
|
|
|
|
|
|
|
|
// The `ValidateServerAction` confirmed the server is ok and we can continue
|
|
|
|
// final instance of the service will be instantiated and propagated to the all parties
|
|
|
|
|
|
|
|
// SWITCH TO NEW ENDPOINT
|
|
|
|
|
|
|
|
// LightWalletService dependency update
|
|
|
|
initializer.container.register(type: LightWalletService.self, isSingleton: true) { _ in
|
|
|
|
LightWalletGRPCService(endpoint: endpoint)
|
|
|
|
}
|
|
|
|
|
|
|
|
// DEPENDENCIES
|
|
|
|
|
|
|
|
// BlockDownloaderService dependency update
|
|
|
|
initializer.container.register(type: BlockDownloaderService.self, isSingleton: true) { di in
|
|
|
|
let service = di.resolve(LightWalletService.self)
|
|
|
|
let storage = di.resolve(CompactBlockRepository.self)
|
|
|
|
|
|
|
|
return BlockDownloaderServiceImpl(service: service, storage: storage)
|
|
|
|
}
|
|
|
|
|
|
|
|
// LatestBlocksDataProvider dependency update
|
|
|
|
initializer.container.register(type: LatestBlocksDataProvider.self, isSingleton: true) { di in
|
|
|
|
let service = di.resolve(LightWalletService.self)
|
|
|
|
let rustBackend = di.resolve(ZcashRustBackendWelding.self)
|
|
|
|
|
|
|
|
return LatestBlocksDataProviderImpl(service: service, rustBackend: rustBackend)
|
|
|
|
}
|
|
|
|
|
|
|
|
// CompactBlockProcessor dependency update
|
|
|
|
Dependencies.setupCompactBlockProcessor(
|
|
|
|
in: initializer.container,
|
|
|
|
config: await blockProcessor.config,
|
|
|
|
accountRepository: initializer.accountRepository
|
|
|
|
)
|
|
|
|
|
|
|
|
// INITIALIZER
|
|
|
|
initializer.lightWalletService = initializer.container.resolve(LightWalletService.self)
|
|
|
|
initializer.blockDownloaderService = initializer.container.resolve(BlockDownloaderService.self)
|
|
|
|
initializer.endpoint = endpoint
|
|
|
|
|
|
|
|
// SELF
|
|
|
|
self.latestBlocksDataProvider = initializer.container.resolve(LatestBlocksDataProvider.self)
|
|
|
|
|
|
|
|
// COMPACT BLOCK PROCESSOR
|
|
|
|
await blockProcessor.updateService(initializer.container)
|
|
|
|
|
|
|
|
// Start synchronization
|
|
|
|
if status != .unprepared {
|
|
|
|
try await start(retry: true)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-16 14:25:45 -08:00
|
|
|
// MARK: notify state
|
2022-11-11 09:46:13 -08:00
|
|
|
|
2023-04-28 10:13:21 -07:00
|
|
|
private func snapshotState(status: InternalSyncStatus) async -> SynchronizerState {
|
2024-01-31 10:16:39 -08:00
|
|
|
await SynchronizerState(
|
2023-04-07 05:02:05 -07:00
|
|
|
syncSessionID: syncSession.value,
|
2024-02-06 01:38:38 -08:00
|
|
|
accountBalance: try? await getAccountBalance(),
|
2023-04-28 10:13:21 -07:00
|
|
|
internalSyncStatus: status,
|
2023-09-07 03:54:47 -07:00
|
|
|
latestBlockHeight: latestBlocksDataProvider.latestBlockHeight
|
2022-11-11 09:46:13 -08:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2023-05-24 06:24:09 -07:00
|
|
|
private func notify(oldStatus: InternalSyncStatus, newStatus: InternalSyncStatus, updateExternalStatus: Bool = true) async {
|
2023-03-15 04:17:43 -07:00
|
|
|
guard oldStatus != newStatus else { return }
|
|
|
|
|
2023-03-16 02:11:18 -07:00
|
|
|
let newState: SynchronizerState
|
|
|
|
|
2023-03-15 04:17:43 -07:00
|
|
|
// When the wipe happens status is switched to `unprepared`. And we expect that everything is deleted. All the databases including data DB.
|
|
|
|
// When new snapshot is created balance is checked. And when balance is checked and data DB doesn't exist then rust initialise new database.
|
|
|
|
// So it's necessary to not create new snapshot after status is switched to `unprepared` otherwise data DB exists after wipe
|
|
|
|
if newStatus == .unprepared {
|
2023-04-07 05:02:05 -07:00
|
|
|
var nextState = SynchronizerState.zero
|
|
|
|
|
|
|
|
let nextSessionID = await self.syncSession.update(.nullID)
|
|
|
|
|
|
|
|
nextState.syncSessionID = nextSessionID
|
|
|
|
newState = nextState
|
2023-03-15 04:17:43 -07:00
|
|
|
} else {
|
2023-04-14 00:08:37 -07:00
|
|
|
if SessionTicker.live.isNewSyncSession(oldStatus, newStatus) {
|
|
|
|
await self.syncSession.newSession(with: self.syncSessionIDGenerator)
|
2022-10-27 03:51:38 -07:00
|
|
|
}
|
2023-04-14 00:08:37 -07:00
|
|
|
newState = await snapshotState(status: newStatus)
|
2019-12-16 14:25:45 -08:00
|
|
|
}
|
2023-03-16 02:11:18 -07:00
|
|
|
|
|
|
|
latestState = newState
|
2023-05-24 06:24:09 -07:00
|
|
|
|
|
|
|
if updateExternalStatus {
|
|
|
|
updateStateStream(with: latestState)
|
|
|
|
}
|
2019-12-16 14:25:45 -08:00
|
|
|
}
|
2023-03-15 04:17:43 -07:00
|
|
|
|
|
|
|
private func updateStateStream(with newState: SynchronizerState) {
|
|
|
|
streamsUpdateQueue.async { [weak self] in
|
|
|
|
self?.stateSubject.send(newState)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-05 10:30:47 -07:00
|
|
|
private func notifyMinedTransaction(_ transaction: ZcashTransaction.Overview) {
|
2023-03-15 04:17:43 -07:00
|
|
|
streamsUpdateQueue.async { [weak self] in
|
|
|
|
self?.eventSubject.send(.minedTransaction(transaction))
|
2019-12-17 12:11:21 -08:00
|
|
|
}
|
|
|
|
}
|
2019-11-14 06:38:54 -08:00
|
|
|
}
|
2020-01-29 21:34:03 -08:00
|
|
|
|
|
|
|
extension SDKSynchronizer {
|
2023-05-05 10:30:47 -07:00
|
|
|
public var transactions: [ZcashTransaction.Overview] {
|
2023-03-31 01:49:06 -07:00
|
|
|
get async {
|
2023-05-05 10:30:47 -07:00
|
|
|
(try? await self.allTransactions()) ?? []
|
2023-03-31 01:49:06 -07:00
|
|
|
}
|
2020-01-29 21:34:03 -08:00
|
|
|
}
|
2022-11-21 07:22:43 -08:00
|
|
|
|
2023-05-05 10:30:47 -07:00
|
|
|
public var sentTransactions: [ZcashTransaction.Overview] {
|
2023-03-27 07:12:06 -07:00
|
|
|
get async {
|
2023-05-05 10:30:47 -07:00
|
|
|
(try? await allSentTransactions()) ?? []
|
2023-03-27 07:12:06 -07:00
|
|
|
}
|
2020-01-29 21:34:03 -08:00
|
|
|
}
|
2022-11-21 07:22:43 -08:00
|
|
|
|
2023-05-05 10:30:47 -07:00
|
|
|
public var receivedTransactions: [ZcashTransaction.Overview] {
|
2023-03-27 07:12:06 -07:00
|
|
|
get async {
|
2023-05-05 10:30:47 -07:00
|
|
|
(try? await allReceivedTransactions()) ?? []
|
2023-03-27 07:12:06 -07:00
|
|
|
}
|
2020-01-29 21:34:03 -08:00
|
|
|
}
|
|
|
|
}
|
2023-04-07 05:02:05 -07:00
|
|
|
|
2023-04-28 10:13:21 -07:00
|
|
|
extension InternalSyncStatus {
|
|
|
|
func isDifferent(from otherStatus: InternalSyncStatus) -> Bool {
|
2023-04-07 05:02:05 -07:00
|
|
|
switch (self, otherStatus) {
|
|
|
|
case (.unprepared, .unprepared): return false
|
|
|
|
case (.syncing, .syncing): return false
|
|
|
|
case (.synced, .synced): return false
|
|
|
|
case (.stopped, .stopped): return false
|
|
|
|
case (.disconnected, .disconnected): return false
|
|
|
|
case (.error, .error): return false
|
|
|
|
default: return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct SessionTicker {
|
|
|
|
/// Helper function to determine whether we are in front of a SyncSession change for a given syncStatus
|
|
|
|
/// transition we consider that every sync attempt is a new sync session and should have it's unique UUID reported.
|
2023-04-28 10:13:21 -07:00
|
|
|
var isNewSyncSession: (InternalSyncStatus, InternalSyncStatus) -> Bool
|
2023-04-07 05:02:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
extension SessionTicker {
|
|
|
|
static let live = SessionTicker { oldStatus, newStatus in
|
|
|
|
// if the state hasn't changed to a different syncStatus member
|
|
|
|
guard oldStatus.isDifferent(from: newStatus) else { return false }
|
|
|
|
|
|
|
|
switch (oldStatus, newStatus) {
|
|
|
|
case (.unprepared, .syncing):
|
|
|
|
return true
|
|
|
|
case (.error, .syncing),
|
|
|
|
(.disconnected, .syncing),
|
|
|
|
(.stopped, .syncing),
|
|
|
|
(.synced, .syncing):
|
|
|
|
return true
|
|
|
|
default:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|