ZcashLightClientKit/Tests/TestUtils/TestCoordinator.swift

403 lines
14 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
}
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) -> Void)?
var errorHandler: ((Error?) -> Void)?
2020-11-05 14:02:01 -08:00
var spendingKey: String
var birthday: BlockHeight
var channelProvider: ChannelProvider
var synchronizer: SDKSynchronizer
var service: DarksideWalletService
var spendingKeys: [String]?
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
) throws {
2021-07-28 09:59:10 -07:00
let derivationTool = DerivationTool(networkType: network.networkType)
2021-09-23 06:26:41 -07:00
guard
let spendingKey = try derivationTool
.deriveSpendingKeys(
seed: TestSeed().seed(),
numberOfAccounts: 1
)
.first
else {
2020-11-05 14:02:01 -08:00
throw CoordinatorError.builderError
}
2021-04-02 15:18:16 -07:00
2021-09-23 06:26:41 -07:00
guard
let uvk = try derivationTool
.deriveUnifiedViewingKeysFromSeed(
TestSeed().seed(),
numberOfAccounts: 1
)
.first
else {
2021-04-02 15:18:16 -07:00
throw CoordinatorError.builderError
}
2021-09-23 06:26:41 -07:00
try self.init(
spendingKey: spendingKey,
unifiedViewingKey: uvk,
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: String,
unifiedViewingKey: UnifiedViewingKey,
2021-07-28 09:59:10 -07:00
walletBirthday: BlockHeight,
channelProvider: ChannelProvider,
2021-09-23 06:26:41 -07:00
network: ZcashNetwork
) 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()
let buildResult = try 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,
unifiedViewingKey: unifiedViewingKey,
walletBirthday: Checkpoint.birthday(with: birthday, network: network),
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) -> 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) {
if case .stopped = self.synchronizer.status {
LoggerProxy.debug("WARNING: notification received after synchronizer was stopped")
return
}
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 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 {
let config = self.synchronizer.blockProcessor.config
self.synchronizer.blockProcessor.config = CompactBlockProcessor.Configuration(
2021-09-23 06:26:41 -07:00
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
)
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,
2020-11-05 14:02:01 -08:00
spendingKey: String,
2021-04-02 15:18:16 -07:00
unifiedViewingKey: UnifiedViewingKey,
walletBirthday: Checkpoint,
2021-07-28 09:59:10 -07:00
network: ZcashNetwork,
loggerProxy: Logger? = nil
2021-09-23 06:26:41 -07:00
) throws -> (spendingKeys: [String]?, synchronizer: SDKSynchronizer) {
let initializer = Initializer(
rustBackend: rustBackend,
lowerBoundHeight: lowerBoundHeight,
2021-07-28 09:59:10 -07:00
network: network,
cacheDbURL: cacheDbURL,
dataDbURL: dataDbURL,
pendingDbURL: pendingDbURL,
2020-10-14 15:50:26 -07:00
endpoint: endpoint,
service: service,
repository: repository,
2021-04-08 10:18:16 -07:00
accountRepository: accountRepository,
storage: CompactBlockStorage(url: cacheDbURL, readonly: false),
spendParamsURL: spendParamsURL,
outputParamsURL: outputParamsURL,
2021-05-07 11:50:50 -07:00
viewingKeys: [unifiedViewingKey],
2021-04-02 15:18:16 -07:00
walletBirthday: walletBirthday.height,
loggerProxy: loggerProxy
)
2021-09-23 06:26:41 -07:00
2021-05-18 14:22:29 -07:00
let config = CompactBlockProcessor.Configuration(
2021-09-23 06:26:41 -07:00
cacheDb: initializer.cacheDbURL,
dataDb: initializer.dataDbURL,
downloadBatchSize: 100,
retries: 5,
maxBackoffInterval: ZcashSDK.defaultMaxBackOffInterval,
rewindDistance: ZcashSDK.defaultRewindDistance,
walletBirthday: walletBirthday.height,
saplingActivation: lowerBoundHeight,
network: network
)
2021-05-18 14:22:29 -07:00
2021-09-23 06:26:41 -07:00
let processor = CompactBlockProcessor(
service: service,
storage: storage,
backend: rustBackend,
config: config,
repository: repository,
accountRepository: accountRepository
)
2020-11-05 14:02:01 -08:00
2021-09-23 06:26:41 -07:00
let synchronizer = try SDKSynchronizer(
status: .unprepared,
initializer: initializer,
transactionManager: OutboundTransactionManagerBuilder.build(initializer: initializer),
transactionRepository: repository,
utxoRepository: UTXORepositoryBuilder.build(initializer: initializer),
blockProcessor: processor
)
2021-05-07 11:50:50 -07:00
try synchronizer.prepare()
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: Checkpoint,
2021-07-28 09:59:10 -07:00
network: ZcashNetwork,
2020-11-05 14:02:01 -08:00
loggerProxy: Logger? = nil
) throws -> (spendingKeys: [String]?, synchronizer: SDKSynchronizer) {
2021-09-23 06:26:41 -07:00
guard
let spendingKey = try DerivationTool(networkType: network.networkType)
.deriveSpendingKeys(seed: seedBytes, numberOfAccounts: 1)
.first
else {
2020-11-05 14:02:01 -08:00
throw TestCoordinator.CoordinatorError.builderError
}
2021-04-02 15:18:16 -07:00
2021-09-23 06:26:41 -07:00
guard let uvk = try DerivationTool(networkType: network.networkType)
.deriveUnifiedViewingKeysFromSeed(seedBytes, numberOfAccounts: 1)
.first
else {
2021-04-02 15:18:16 -07:00
throw TestCoordinator.CoordinatorError.builderError
}
2021-09-23 06:26:41 -07:00
return try build(
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,
2021-04-02 15:18:16 -07:00
unifiedViewingKey: 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
}
}