// // BlockScanOperationTests.swift // ZcashLightClientKitTests // // Created by Francisco Gindre on 10/17/19. // Copyright © 2019 Electric Coin Company. All rights reserved. // import XCTest import SQLite @testable import ZcashLightClientKit class BlockScanOperationTests: XCTestCase { var operationQueue = OperationQueue() var cacheDbURL: URL! var dataDbURL: URL! let rustWelding = ZcashRustBackend.self var uvk = UVFakeKey(extfvk: "zxviewtestsapling1qw88ayg8qqqqpqyhg7jnh9mlldejfqwu46pm40ruwstd8znq3v3l4hjf33qcu2a5e36katshcfhcxhzgyfugj2lkhmt40j45cv38rv3frnghzkxcx73k7m7afw9j7ujk7nm4dx5mv02r26umxqgar7v3x390w2h3crqqgjsjly7jy4vtwzrmustm5yudpgcydw7x78awca8wqjvkqj8p8e3ykt7lrgd7xf92fsfqjs5vegfsja4ekzpfh5vtccgvs5747xqm6qflmtqpr8s9u", extpub: "02075a7f5f7507d64022dad5954849f216b0f1b09b2d588be663d8e7faeb5aaf61") var walletBirthDay = WalletBirthday.birthday(with: 1386000) var blockRepository: BlockRepository! override func setUp() { // Put setup code here. This method is called before the invocation of each test method in the class. self.cacheDbURL = try! __cacheDbURL() self.dataDbURL = try! __dataDbURL() deleteDBs() operationQueue.maxConcurrentOperationCount = 1 } private func deleteDBs() { try? FileManager.default.removeItem(at: cacheDbURL) try? FileManager.default.removeItem(at: dataDbURL) } override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. operationQueue.cancelAllOperations() try? FileManager.default.removeItem(at: cacheDbURL) try? FileManager.default.removeItem(at: dataDbURL) } func testSingleDownloadAndScanOperation() { logger = SampleLogger(logLevel: .debug) XCTAssertNoThrow(try rustWelding.initDataDb(dbData: dataDbURL)) let downloadStartedExpect = XCTestExpectation(description: self.description + "download started") let downloadExpect = XCTestExpectation(description: self.description + "download") let scanStartedExpect = XCTestExpectation(description: self.description + "scan started") let scanExpect = XCTestExpectation(description: self.description + "scan") let latestScannedBlockExpect = XCTestExpectation(description: self.description + "latestScannedHeight") let service = LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.default) let blockCount = 100 let range = ZcashSDK.SAPLING_ACTIVATION_HEIGHT ... ZcashSDK.SAPLING_ACTIVATION_HEIGHT + blockCount let downloadOperation = CompactBlockDownloadOperation(downloader: CompactBlockDownloader.sqlDownloader(service: service, at: cacheDbURL)!, range: range) let scanOperation = CompactBlockScanningOperation(rustWelding: rustWelding, cacheDb: cacheDbURL, dataDb: dataDbURL) downloadOperation.startedHandler = { downloadStartedExpect.fulfill() } downloadOperation.completionHandler = { (finished, cancelled) in downloadExpect.fulfill() XCTAssertTrue(finished) XCTAssertFalse(cancelled) } downloadOperation.errorHandler = { (error) in XCTFail("Download Operation failed with Error: \(error)") } scanOperation.startedHandler = { scanStartedExpect.fulfill() } scanOperation.completionHandler = { (finished, cancelled) in scanExpect.fulfill() XCTAssertFalse(cancelled) XCTAssertTrue(finished) } scanOperation.errorHandler = { (error) in XCTFail("Scan Operation failed with Error: \(error)") } scanOperation.addDependency(downloadOperation) var latestScannedheight = BlockHeight.empty() let latestScannedBlockOperation = BlockOperation { let repository = BlockSQLDAO(dbProvider: SimpleConnectionProvider.init(path: self.dataDbURL.absoluteString, readonly: true)) latestScannedheight = repository.lastScannedBlockHeight() } latestScannedBlockOperation.completionBlock = { latestScannedBlockExpect.fulfill() XCTAssertEqual(latestScannedheight, range.upperBound) } latestScannedBlockOperation.addDependency(scanOperation) operationQueue.addOperations([downloadOperation,scanOperation,latestScannedBlockOperation], waitUntilFinished: false) wait(for: [downloadStartedExpect, downloadExpect, scanStartedExpect, scanExpect,latestScannedBlockExpect], timeout: 10, enforceOrder: true) } @objc func observeBenchmark(_ notification: Notification) { guard let report = SDKMetrics.blockReportFromNotification(notification) else { return } print("observed benchmark: \(report)") } func testScanValidateDownload() throws { logger = SampleLogger(logLevel: .debug) NotificationCenter.default.addObserver(self, selector: #selector(observeBenchmark(_:)), name: SDKMetrics.notificationName, object: nil) try self.rustWelding.initDataDb(dbData: dataDbURL) guard try self.rustWelding.initAccountsTable(dbData: self.dataDbURL, uvks: [uvk]) else { XCTFail("failed to init account table") return } try self.rustWelding.initBlocksTable(dbData: dataDbURL, height: Int32(walletBirthDay.height), hash: walletBirthDay.hash, time: walletBirthDay.time, saplingTree: walletBirthDay.tree) let service = LightWalletGRPCService(host: Constants.address, port: 9067, secure: true, singleCallTimeout: 100000, streamingCallTimeout: 1000000000) let storage = CompactBlockStorage(url: cacheDbURL, readonly: false) try storage.createTable() let downloadExpectation = XCTestExpectation(description: "download expectation") let validateExpectation = XCTestExpectation(description: "validate expectation") let scanExpectation = XCTestExpectation(description: "scan expectation") let downloadOperation = CompactBlockStreamDownloadOperation(service: service, storage: storage, startHeight: walletBirthDay.height, targetHeight: walletBirthDay.height + 10000, progressDelegate: self) downloadOperation.completionHandler = { (finished, cancelled) in XCTAssert(finished) XCTAssertFalse(cancelled) downloadExpectation.fulfill() } downloadOperation.errorHandler = { error in if let lwdError = error as? LightWalletServiceError { switch lwdError { case .timeOut: XCTAssert(true) default: XCTFail("LWD Service error found, but should have been a timeLimit reached Error - \(lwdError)") } } else { XCTFail("Error should have been a timeLimit reached Error - \(error)") } } let validationOperation = CompactBlockValidationOperation(rustWelding: rustWelding, cacheDb: cacheDbURL, dataDb: dataDbURL) validationOperation.errorHandler = { error in self.operationQueue.cancelAllOperations() XCTFail("failed with error \(error)") } validationOperation.completionHandler = { (finished,cancelled) in XCTAssert(finished) XCTAssertFalse(cancelled) validateExpectation.fulfill() } let transactionRepository = TransactionRepositoryBuilder.build(dataDbURL: dataDbURL) let scanningOperation = CompactBlockBatchScanningOperation(rustWelding: rustWelding, cacheDb: cacheDbURL, dataDb: dataDbURL, transactionRepository: transactionRepository, range: CompactBlockRange(uncheckedBounds: (walletBirthDay.height, walletBirthDay.height + 10000)), batchSize: 1000, progressDelegate: self) scanningOperation.completionHandler = { (finished,cancelled) in XCTAssert(finished) XCTAssertFalse(cancelled) scanExpectation.fulfill() } operationQueue.addOperations([downloadOperation, validationOperation, scanningOperation], waitUntilFinished: false) wait(for: [downloadExpectation,validateExpectation,scanExpectation], timeout: 300, enforceOrder: true) } } extension BlockScanOperationTests: BlockStreamProgressDelegate { func progressUpdated(_ progress: BlockStreamProgressReporting) { print("progressHeight: \(progress.progressHeight) startHeight: \(progress.startHeight), targetHeight: \(progress.targetHeight)") } } struct UVFakeKey: UnifiedViewingKey { var extfvk: ExtendedFullViewingKey var extpub: ExtendedPublicKey }