ZcashLightClientKit/Tests/TestUtils/TestCoordinator.swift

362 lines
12 KiB
Swift
Raw Normal View History

//
// TestCoordinator.swift
// ZcashLightClientKit-Unit-Tests
//
// Created by Francisco Gindre on 4/29/20.
//
import Foundation
@testable import ZcashLightClientKit
/**
2021-09-23 06:26:41 -07:00
This is the TestCoordinator
What does it do? quite a lot.
Is it a nice "SOLID" "Clean Code" piece of source code?
Hell no. It's your testing overlord and you will be grateful it is.
*/
// swiftlint:disable force_try function_parameter_count
class TestCoordinator {
enum CoordinatorError: Error {
case notDarksideWallet
case notificationFromUnknownSynchronizer
case notMockLightWalletService
2020-11-05 14:02:01 -08:00
case builderError
case seedRequiredForMigration
}
enum SyncThreshold {
case upTo(height: BlockHeight)
case latestHeight
}
enum DarksideData {
case `default`
case predefined(dataset: DarksideDataset)
case url(urlString: String, startHeigth: BlockHeight)
}
var completionHandler: ((SDKSynchronizer) throws -> Void)?
var errorHandler: ((Error?) -> Void)?
var spendingKey: UnifiedSpendingKey
var birthday: BlockHeight
var channelProvider: ChannelProvider
var synchronizer: SDKSynchronizer
var service: DarksideWalletService
var spendingKeys: [UnifiedSpendingKey]?
var databases: TemporaryTestDatabases
2021-07-28 09:59:10 -07:00
let network: ZcashNetwork
2021-09-23 06:26:41 -07:00
convenience init(
seed: String,
walletBirthday: BlockHeight,
channelProvider: ChannelProvider,
network: ZcashNetwork
2022-10-31 05:57:10 -07:00
) async throws {
2021-07-28 09:59:10 -07:00
let derivationTool = DerivationTool(networkType: network.networkType)
2021-09-23 06:26:41 -07:00
let spendingKey = try derivationTool.deriveUnifiedSpendingKey(
seed: TestSeed().seed(),
accountIndex: 0
)
let ufvk = try derivationTool.deriveUnifiedFullViewingKey(from: spendingKey)
2022-10-31 05:57:10 -07:00
await try self.init(
2021-09-23 06:26:41 -07:00
spendingKey: spendingKey,
unifiedFullViewingKey: ufvk,
2021-09-23 06:26:41 -07:00
walletBirthday: walletBirthday,
channelProvider: channelProvider,
network: network
)
2020-11-05 14:02:01 -08:00
}
2021-04-02 15:18:16 -07:00
required init(
spendingKey: UnifiedSpendingKey,
unifiedFullViewingKey: UnifiedFullViewingKey,
2021-07-28 09:59:10 -07:00
walletBirthday: BlockHeight,
channelProvider: ChannelProvider,
2021-09-23 06:26:41 -07:00
network: ZcashNetwork
2022-10-31 05:57:10 -07:00
) async throws {
2020-11-05 14:02:01 -08:00
self.spendingKey = spendingKey
self.birthday = walletBirthday
self.channelProvider = channelProvider
self.databases = TemporaryDbBuilder.build()
2021-07-28 09:59:10 -07:00
self.network = network
2021-09-23 06:26:41 -07:00
self.service = DarksideWalletService(
service: LightWalletGRPCService(
host: Constants.address,
port: 9067,
secure: false,
singleCallTimeout: 10000,
streamingCallTimeout: 1000000
)
)
let storage = CompactBlockStorage(url: databases.cacheDB, readonly: false)
try storage.createTable()
2022-10-31 05:57:10 -07:00
let buildResult = try await TestSynchronizerBuilder.build(
2021-09-23 06:26:41 -07:00
rustBackend: ZcashRustBackend.self,
lowerBoundHeight: self.birthday,
cacheDbURL: databases.cacheDB,
dataDbURL: databases.dataDB,
pendingDbURL: databases.pendingDB,
endpoint: LightWalletEndpointBuilder.default,
service: self.service,
repository: TransactionSQLDAO(dbProvider: SimpleConnectionProvider(path: databases.dataDB.absoluteString)),
accountRepository: AccountRepositoryBuilder.build(
dataDbURL: databases.dataDB,
readOnly: true
),
storage: storage,
spendParamsURL: try __spendParamsURL(),
outputParamsURL: try __outputParamsURL(),
spendingKey: spendingKey,
unifiedFullViewingKey: unifiedFullViewingKey,
walletBirthday: walletBirthday,
2021-09-23 06:26:41 -07:00
network: network,
loggerProxy: SampleLogger(logLevel: .debug)
)
self.synchronizer = buildResult.synchronizer
self.spendingKeys = buildResult.spendingKeys
subscribeToNotifications(synchronizer: self.synchronizer)
}
func stop() throws {
2020-10-14 15:50:26 -07:00
synchronizer.stop()
self.completionHandler = nil
self.errorHandler = nil
}
func setDarksideWalletState(_ state: DarksideData) throws {
switch state {
case .default:
try service.useDataset(DarksideDataset.beforeReOrg.rawValue)
case .predefined(let dataset):
try service.useDataset(dataset.rawValue)
2021-09-23 06:26:41 -07:00
case .url(let urlString, _):
try service.useDataset(from: urlString)
}
}
func setLatestHeight(height: BlockHeight) throws {
try service.applyStaged(nextLatestHeight: height)
}
func sync(completion: @escaping (SDKSynchronizer) throws -> Void, error: @escaping (Error?) -> Void) throws {
self.completionHandler = completion
self.errorHandler = error
2020-12-05 10:10:22 -08:00
try synchronizer.start(retry: true)
}
/**
2021-09-23 06:26:41 -07:00
Notifications
*/
func subscribeToNotifications(synchronizer: Synchronizer) {
NotificationCenter.default.addObserver(self, selector: #selector(synchronizerFailed(_:)), name: .synchronizerFailed, object: synchronizer)
NotificationCenter.default.addObserver(self, selector: #selector(synchronizerSynced(_:)), name: .synchronizerSynced, object: synchronizer)
}
@objc func synchronizerFailed(_ notification: Notification) {
self.errorHandler?(notification.userInfo?[SDKSynchronizer.NotificationKeys.error] as? Error)
}
@objc func synchronizerSynced(_ notification: Notification) throws {
if case .stopped = self.synchronizer.status {
LoggerProxy.debug("WARNING: notification received after synchronizer was stopped")
return
}
try self.completionHandler?(self.synchronizer)
}
@objc func synchronizerDisconnected(_ notification: Notification) {
/// TODO: See if we need hooks for this
}
@objc func synchronizerStarted(_ notification: Notification) {
/// TODO: See if we need hooks for this
}
@objc func synchronizerStopped(_ notification: Notification) {
/// TODO: See if we need hooks for this
}
@objc func synchronizerSyncing(_ notification: Notification) {
/// TODO: See if we need hooks for this
}
}
extension CompactBlockProcessor {
public func setConfig(_ config: Configuration) {
self.config = config
}
}
extension TestCoordinator {
func resetBlocks(dataset: DarksideData) throws {
switch dataset {
case .default:
try service.useDataset(DarksideDataset.beforeReOrg.rawValue)
case .predefined(let blocks):
try service.useDataset(blocks.rawValue)
2021-09-23 06:26:41 -07:00
case .url(let urlString, _):
try service.useDataset(urlString)
}
}
func stageBlockCreate(height: BlockHeight, count: Int = 1, nonce: Int = 0) throws {
try service.stageBlocksCreate(from: height, count: count, nonce: 0)
}
func applyStaged(blockheight: BlockHeight) throws {
try service.applyStaged(nextLatestHeight: blockheight)
}
2021-09-23 06:26:41 -07:00
func stageTransaction(_ transaction: RawTransaction, at height: BlockHeight) throws {
try service.stageTransaction(transaction, at: height)
}
func stageTransaction(url: String, at height: BlockHeight) throws {
try service.stageTransaction(from: url, at: height)
}
func latestHeight() throws -> BlockHeight {
try service.latestBlockHeight()
}
2021-05-18 14:22:29 -07:00
func reset(saplingActivation: BlockHeight, branchID: String, chainName: String) throws {
Task {
await self.synchronizer.blockProcessor.stop()
let config = await self.synchronizer.blockProcessor.config
let newConfig = CompactBlockProcessor.Configuration(
cacheDb: config.cacheDb,
dataDb: config.dataDb,
downloadBatchSize: config.downloadBatchSize,
retries: config.retries,
maxBackoffInterval: config.maxBackoffInterval,
rewindDistance: config.rewindDistance,
walletBirthday: config.walletBirthday,
saplingActivation: config.saplingActivation,
network: config.network
)
await self.synchronizer.blockProcessor.setConfig(newConfig)
}
2021-05-18 14:22:29 -07:00
try service.reset(saplingActivation: saplingActivation, branchID: branchID, chainName: chainName)
}
func getIncomingTransactions() throws -> [RawTransaction]? {
return try service.getIncomingTransactions()
}
}
struct TemporaryTestDatabases {
2021-09-23 06:26:41 -07:00
var cacheDB: URL
var dataDB: URL
var pendingDB: URL
}
2021-09-23 06:26:41 -07:00
enum TemporaryDbBuilder {
static func build() -> TemporaryTestDatabases {
let tempUrl = try! __documentsDirectory()
let timestamp = String(Int(Date().timeIntervalSince1970))
2021-09-23 06:26:41 -07:00
return TemporaryTestDatabases(
cacheDB: tempUrl.appendingPathComponent("cache_db_\(timestamp).db"),
dataDB: tempUrl.appendingPathComponent("data_db_\(timestamp).db"),
pendingDB: tempUrl.appendingPathComponent("pending_db_\(timestamp).db")
)
}
}
2021-09-23 06:26:41 -07:00
enum TestSynchronizerBuilder {
static func build(
rustBackend: ZcashRustBackendWelding.Type,
lowerBoundHeight: BlockHeight,
cacheDbURL: URL,
dataDbURL: URL,
pendingDbURL: URL,
2020-10-14 15:50:26 -07:00
endpoint: LightWalletEndpoint,
service: LightWalletService,
repository: TransactionRepository,
2021-04-08 10:18:16 -07:00
accountRepository: AccountRepository,
storage: CompactBlockStorage,
spendParamsURL: URL,
outputParamsURL: URL,
spendingKey: UnifiedSpendingKey,
unifiedFullViewingKey: UnifiedFullViewingKey,
walletBirthday: BlockHeight,
2021-07-28 09:59:10 -07:00
network: ZcashNetwork,
seed: [UInt8]? = nil,
loggerProxy: Logger? = nil
2022-10-31 05:57:10 -07:00
) async throws -> (spendingKeys: [UnifiedSpendingKey]?, synchronizer: SDKSynchronizer) {
let initializer = Initializer(
cacheDbURL: cacheDbURL,
dataDbURL: dataDbURL,
pendingDbURL: pendingDbURL,
2020-10-14 15:50:26 -07:00
endpoint: endpoint,
network: network,
spendParamsURL: spendParamsURL,
outputParamsURL: outputParamsURL,
viewingKeys: [unifiedFullViewingKey],
walletBirthday: walletBirthday,
alias: "",
loggerProxy: loggerProxy
)
2021-09-23 06:26:41 -07:00
let synchronizer = try SDKSynchronizer(initializer: initializer)
if case .seedRequired = try await synchronizer.prepare(with: seed) {
throw TestCoordinator.CoordinatorError.seedRequiredForMigration
}
2021-05-07 11:50:50 -07:00
return ([spendingKey], synchronizer)
}
2021-09-23 06:26:41 -07:00
2020-11-05 14:02:01 -08:00
static func build(
rustBackend: ZcashRustBackendWelding.Type,
lowerBoundHeight: BlockHeight,
cacheDbURL: URL,
dataDbURL: URL,
pendingDbURL: URL,
endpoint: LightWalletEndpoint,
service: LightWalletService,
repository: TransactionRepository,
2021-04-08 10:18:16 -07:00
accountRepository: AccountRepository,
storage: CompactBlockStorage,
2020-11-05 14:02:01 -08:00
spendParamsURL: URL,
outputParamsURL: URL,
seedBytes: [UInt8],
walletBirthday: BlockHeight,
2021-07-28 09:59:10 -07:00
network: ZcashNetwork,
2020-11-05 14:02:01 -08:00
loggerProxy: Logger? = nil
2022-10-31 05:57:10 -07:00
) async throws -> (spendingKeys: [UnifiedSpendingKey]?, synchronizer: SDKSynchronizer) {
let spendingKey = try DerivationTool(networkType: network.networkType)
.deriveUnifiedSpendingKey(seed: seedBytes, accountIndex: 0)
2021-04-02 15:18:16 -07:00
let uvk = try DerivationTool(networkType: network.networkType)
.deriveUnifiedFullViewingKey(from: spendingKey)
2021-09-23 06:26:41 -07:00
2022-10-31 05:57:10 -07:00
return try await build(
2021-09-23 06:26:41 -07:00
rustBackend: rustBackend,
2020-11-05 14:02:01 -08:00
lowerBoundHeight: lowerBoundHeight,
cacheDbURL: cacheDbURL,
dataDbURL: dataDbURL,
pendingDbURL: pendingDbURL,
endpoint: endpoint,
service: service,
repository: repository,
2021-04-08 10:18:16 -07:00
accountRepository: accountRepository,
storage: storage,
2020-11-05 14:02:01 -08:00
spendParamsURL: spendParamsURL,
outputParamsURL: outputParamsURL,
spendingKey: spendingKey,
unifiedFullViewingKey: uvk,
2021-07-28 09:59:10 -07:00
walletBirthday: walletBirthday,
2021-09-23 06:26:41 -07:00
network: network
)
2020-11-05 14:02:01 -08:00
}
}