ZcashLightClientKit/Tests/NetworkTests/CompactBlockReorgTests.swift

193 lines
7.7 KiB
Swift

//
// CompactBlockReorgTests.swift
// ZcashLightClientKit-Unit-Tests
//
// Created by Francisco Gindre on 11/13/19.
//
// Copyright © 2019 Electric Coin Company. All rights reserved.
import Combine
import XCTest
@testable import TestUtils
@testable import ZcashLightClientKit
class CompactBlockReorgTests: XCTestCase {
lazy var processorConfig = {
let pathProvider = DefaultResourceProvider(network: network)
return CompactBlockProcessor.Configuration(
alias: .default,
fsBlockCacheRoot: testTempDirectory,
dataDb: pathProvider.dataDbURL,
spendParamsURL: pathProvider.spendParamsURL,
outputParamsURL: pathProvider.outputParamsURL,
saplingParamsSourceURL: SaplingParamsSourceURL.tests,
walletBirthdayProvider: { ZcashNetworkBuilder.network(for: .testnet).constants.saplingActivationHeight },
network: ZcashNetworkBuilder.network(for: .testnet)
)
}()
let testTempDirectory = URL(fileURLWithPath: NSString(
string: NSTemporaryDirectory()
)
.appendingPathComponent("tmp-\(Int.random(in: 0 ... .max))"))
let testFileManager = FileManager()
var cancellables: [AnyCancellable] = []
var processorEventHandler: CompactBlockProcessorEventHandler! = CompactBlockProcessorEventHandler()
var processor: CompactBlockProcessor!
var syncStartedExpect: XCTestExpectation!
var updatedNotificationExpectation: XCTestExpectation!
var stopNotificationExpectation: XCTestExpectation!
var finishedNotificationExpectation: XCTestExpectation!
var reorgNotificationExpectation: XCTestExpectation!
let network = ZcashNetworkBuilder.network(for: .testnet)
let mockLatestHeight = ZcashNetworkBuilder.network(for: .testnet).constants.saplingActivationHeight + 2000
override func setUpWithError() throws {
try super.setUpWithError()
logger = OSLogger(logLevel: .debug)
try self.testFileManager.createDirectory(at: self.testTempDirectory, withIntermediateDirectories: false)
XCTestCase.wait {
await InternalSyncProgress(
alias: .default,
storage: UserDefaults.standard,
logger: logger
).rewind(to: 0)
}
let liveService = LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.eccTestnet).make()
let service = MockLightWalletService(
latestBlockHeight: mockLatestHeight,
service: liveService
)
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 realRustBackend = ZcashRustBackend.self
let realCache = FSCompactBlockRepository(
fsBlockDbRoot: processorConfig.fsBlockCacheRoot,
metadataStore: FSMetadataStore.live(
fsBlockDbRoot: processorConfig.fsBlockCacheRoot,
rustBackend: realRustBackend,
logger: logger
),
blockDescriptor: .live,
contentProvider: DirectoryListingProviders.defaultSorted,
logger: logger
)
try realCache.create()
guard case .success = try realRustBackend.initDataDb(dbData: processorConfig.dataDb, seed: nil, networkType: .testnet) else {
XCTFail("initDataDb failed. Expected Success but got .seedRequired")
return
}
let mockBackend = MockRustBackend.self
mockBackend.mockValidateCombinedChainFailAfterAttempts = 3
mockBackend.mockValidateCombinedChainKeepFailing = false
mockBackend.mockValidateCombinedChainFailureHeight = self.network.constants.saplingActivationHeight + 320
processor = CompactBlockProcessor(
service: service,
storage: realCache,
backend: mockBackend,
config: processorConfig,
metrics: SDKMetrics(),
logger: logger
)
syncStartedExpect = XCTestExpectation(description: "\(self.description) syncStartedExpect")
stopNotificationExpectation = XCTestExpectation(description: "\(self.description) stopNotificationExpectation")
updatedNotificationExpectation = XCTestExpectation(description: "\(self.description) updatedNotificationExpectation")
finishedNotificationExpectation = XCTestExpectation(description: "\(self.description) finishedNotificationExpectation")
reorgNotificationExpectation = XCTestExpectation(description: "\(self.description) reorgNotificationExpectation")
let eventClosure: CompactBlockProcessor.EventClosure = { [weak self] event in
switch event {
case .failed: self?.processorFailed(event: event)
case .handledReorg: self?.processorHandledReorg(event: event)
default: break
}
}
XCTestCase.wait { await self.processor.updateEventClosure(identifier: "tests", closure: eventClosure) }
}
override func tearDownWithError() throws {
try super.tearDownWithError()
XCTestCase.wait { await self.processor.stop() }
try! FileManager.default.removeItem(at: processorConfig.fsBlockCacheRoot)
try? FileManager.default.removeItem(at: processorConfig.dataDb)
NotificationCenter.default.removeObserver(self)
cancellables = []
processorEventHandler = nil
processor = nil
}
func processorHandledReorg(event: CompactBlockProcessor.Event) {
if case let .handledReorg(reorg, rewind) = event {
XCTAssertTrue( reorg == 0 || reorg > self.network.constants.saplingActivationHeight)
XCTAssertTrue( rewind == 0 || rewind > self.network.constants.saplingActivationHeight)
XCTAssertTrue( rewind <= reorg )
reorgNotificationExpectation.fulfill()
} else {
XCTFail("CompactBlockProcessor reorg notification is malformed")
}
}
func processorFailed(event: CompactBlockProcessor.Event) {
if case let .failed(error) = event {
XCTFail("CompactBlockProcessor failed with Error: \(error)")
} else {
XCTFail("CompactBlockProcessor failed")
}
}
private func startProcessing() async {
XCTAssertNotNil(processor)
let expectations: [CompactBlockProcessorEventHandler.EventIdentifier: XCTestExpectation] = [
.startedSyncing: syncStartedExpect,
.stopped: stopNotificationExpectation,
.progressUpdated: updatedNotificationExpectation,
.finished: finishedNotificationExpectation,
.handleReorg: reorgNotificationExpectation
]
await processorEventHandler.subscribe(to: processor, expectations: expectations)
await processor.start()
}
func testNotifiesReorg() async {
await startProcessing()
wait(
for: [
syncStartedExpect,
reorgNotificationExpectation,
finishedNotificationExpectation
],
timeout: 300,
enforceOrder: true
)
}
private func expectedBatches(currentHeight: BlockHeight, targetHeight: BlockHeight, batchSize: Int) -> Int {
(abs(currentHeight - targetHeight) / batchSize)
}
}