// // CompactBlockProcessorTests.swift // ZcashLightClientKitTests // // Created by Francisco Gindre on 20/09/2019. // Copyright © 2019 Electric Coin Company. All rights reserved. // import XCTest @testable import TestUtils @testable import ZcashLightClientKit // swiftlint:disable force_try implicitly_unwrapped_optional class CompactBlockProcessorTests: XCTestCase { let processorConfig = CompactBlockProcessor.Configuration.standard( for: ZcashNetworkBuilder.network(for: .testnet), walletBirthday: ZcashNetworkBuilder.network(for: .testnet).constants.saplingActivationHeight ) var processor: CompactBlockProcessor! var downloadStartedExpect: XCTestExpectation! var updatedNotificationExpectation: XCTestExpectation! var stopNotificationExpectation: XCTestExpectation! var startedScanningNotificationExpectation: XCTestExpectation! var startedValidatingNotificationExpectation: XCTestExpectation! var idleNotificationExpectation: XCTestExpectation! let network = ZcashNetworkBuilder.network(for: .testnet) let mockLatestHeight = ZcashNetworkBuilder.network(for: .testnet).constants.saplingActivationHeight + 2000 override func setUpWithError() throws { try super.setUpWithError() logger = SampleLogger(logLevel: .debug) let service = MockLightWalletService( latestBlockHeight: mockLatestHeight, service: LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.eccTestnet) ) let branchID = try ZcashRustBackend.consensusBranchIdFor(height: Int32(mockLatestHeight), networkType: network.networkType) service.mockLightDInfo = LightdInfo.with({ info in info.blockHeight = UInt64(mockLatestHeight) info.branch = "asdf" info.buildDate = "today" info.buildUser = "testUser" info.chainName = "test" info.consensusBranchID = branchID.toString() info.estimatedHeight = UInt64(mockLatestHeight) info.saplingActivationHeight = UInt64(network.constants.saplingActivationHeight) }) let storage = CompactBlockStorage.init(connectionProvider: SimpleConnectionProvider(path: processorConfig.cacheDb.absoluteString)) try! storage.createTable() processor = CompactBlockProcessor( service: service, storage: storage, backend: ZcashRustBackend.self, config: processorConfig ) let dbInit = try ZcashRustBackend.initDataDb(dbData: processorConfig.dataDb, seed: nil, networkType: .testnet) guard case .success = dbInit else { XCTFail("Failed to initDataDb. Expected `.success` got: \(dbInit)") return } downloadStartedExpect = XCTestExpectation(description: "\(self.description) downloadStartedExpect") stopNotificationExpectation = XCTestExpectation(description: "\(self.description) stopNotificationExpectation") updatedNotificationExpectation = XCTestExpectation(description: "\(self.description) updatedNotificationExpectation") startedValidatingNotificationExpectation = XCTestExpectation( description: "\(self.description) startedValidatingNotificationExpectation" ) startedScanningNotificationExpectation = XCTestExpectation( description: "\(self.description) startedScanningNotificationExpectation" ) idleNotificationExpectation = XCTestExpectation(description: "\(self.description) idleNotificationExpectation") NotificationCenter.default.addObserver( self, selector: #selector(processorFailed(_:)), name: Notification.Name.blockProcessorFailed, object: processor ) } override func tearDown() { super.tearDown() try! FileManager.default.removeItem(at: processorConfig.cacheDb) try? FileManager.default.removeItem(at: processorConfig.dataDb) downloadStartedExpect.unsubscribeFromNotifications() stopNotificationExpectation.unsubscribeFromNotifications() updatedNotificationExpectation.unsubscribeFromNotifications() startedScanningNotificationExpectation.unsubscribeFromNotifications() startedValidatingNotificationExpectation.unsubscribeFromNotifications() idleNotificationExpectation.unsubscribeFromNotifications() NotificationCenter.default.removeObserver(self) } @objc func processorFailed(_ notification: Notification) { XCTAssertNotNil(notification.userInfo) if let error = notification.userInfo?["error"] { XCTFail("CompactBlockProcessor failed with Error: \(error)") } else { XCTFail("CompactBlockProcessor failed") } } private func startProcessing() async { XCTAssertNotNil(processor) // Subscribe to notifications downloadStartedExpect.subscribe(to: Notification.Name.blockProcessorStartedDownloading, object: processor) stopNotificationExpectation.subscribe(to: Notification.Name.blockProcessorStopped, object: processor) updatedNotificationExpectation.subscribe(to: Notification.Name.blockProcessorUpdated, object: processor) startedValidatingNotificationExpectation.subscribe(to: Notification.Name.blockProcessorStartedValidating, object: processor) startedScanningNotificationExpectation.subscribe(to: Notification.Name.blockProcessorStartedScanning, object: processor) idleNotificationExpectation.subscribe(to: Notification.Name.blockProcessorIdle, object: processor) do { try await processor.start() } catch { XCTFail("shouldn't fail") } } // FIXME: disabled see https://github.com/zcash/ZcashLightClientKit/issues/590 func testStartNotifiesSuscriptors() async { await startProcessing() wait( for: [ downloadStartedExpect, startedValidatingNotificationExpectation, startedScanningNotificationExpectation, idleNotificationExpectation ], timeout: 30, enforceOrder: false ) } // FIXME: disabled see https://github.com/zcash/ZcashLightClientKit/issues/590 func testProgressNotifications() async { let expectedUpdates = expectedBatches( currentHeight: processorConfig.walletBirthday, targetHeight: mockLatestHeight, batchSize: processorConfig.downloadBatchSize ) updatedNotificationExpectation.expectedFulfillmentCount = expectedUpdates await startProcessing() wait(for: [updatedNotificationExpectation], timeout: 300) } private func expectedBatches(currentHeight: BlockHeight, targetHeight: BlockHeight, batchSize: Int) -> Int { (abs(currentHeight - targetHeight) / batchSize) } func testNextBatchBlockRange() { // test first range var latestDownloadedHeight = processorConfig.walletBirthday // this can be either this or Wallet Birthday. var latestBlockchainHeight = BlockHeight(network.constants.saplingActivationHeight + 1000) var expectedBatchRange = CompactBlockRange(uncheckedBounds: (lower: latestDownloadedHeight, upper:latestBlockchainHeight)) XCTAssertEqual( expectedBatchRange, CompactBlockProcessor.nextBatchBlockRange( latestHeight: latestBlockchainHeight, latestDownloadedHeight: latestDownloadedHeight, walletBirthday: processorConfig.walletBirthday ) ) // Test mid-range latestDownloadedHeight = BlockHeight(network.constants.saplingActivationHeight + ZcashSDK.DefaultDownloadBatch) latestBlockchainHeight = BlockHeight(network.constants.saplingActivationHeight + 1000) expectedBatchRange = CompactBlockRange(uncheckedBounds: (lower: latestDownloadedHeight + 1, upper: latestBlockchainHeight)) XCTAssertEqual( expectedBatchRange, CompactBlockProcessor.nextBatchBlockRange( latestHeight: latestBlockchainHeight, latestDownloadedHeight: latestDownloadedHeight, walletBirthday: processorConfig.walletBirthday ) ) // Test last batch range latestDownloadedHeight = BlockHeight(network.constants.saplingActivationHeight + 950) latestBlockchainHeight = BlockHeight(network.constants.saplingActivationHeight + 1000) expectedBatchRange = CompactBlockRange(uncheckedBounds: (lower: latestDownloadedHeight + 1, upper: latestBlockchainHeight)) XCTAssertEqual( expectedBatchRange, CompactBlockProcessor.nextBatchBlockRange( latestHeight: latestBlockchainHeight, latestDownloadedHeight: latestDownloadedHeight, walletBirthday: processorConfig.walletBirthday ) ) } func testDetermineLowerBoundPastBirthday() async { let errorHeight = 781_906 let walletBirthday = 781_900 let result = await processor.determineLowerBound(errorHeight: errorHeight, consecutiveErrors: 1, walletBirthday: walletBirthday) let expected = 781_886 XCTAssertEqual(result, expected) } func testDetermineLowerBound() async { let errorHeight = 781_906 let walletBirthday = 780_900 let result = await processor.determineLowerBound(errorHeight: errorHeight, consecutiveErrors: 0, walletBirthday: walletBirthday) let expected = 781_896 XCTAssertEqual(result, expected) } }