313 lines
12 KiB
Swift
313 lines
12 KiB
Swift
//
|
|
// CompactBlockProcessorTests.swift
|
|
// ZcashLightClientKitTests
|
|
//
|
|
// Created by Francisco Gindre on 20/09/2019.
|
|
// Copyright © 2019 Electric Coin Company. All rights reserved.
|
|
//
|
|
|
|
import Combine
|
|
import XCTest
|
|
@testable import TestUtils
|
|
@testable import ZcashLightClientKit
|
|
|
|
class CompactBlockProcessorTests: ZcashTestCase {
|
|
var processorConfig: CompactBlockProcessor.Configuration!
|
|
var cancellables: [AnyCancellable] = []
|
|
var processorEventHandler: CompactBlockProcessorEventHandler! = CompactBlockProcessorEventHandler()
|
|
var rustBackend: ZcashRustBackendWelding!
|
|
var processor: CompactBlockProcessor!
|
|
var syncStartedExpect: XCTestExpectation!
|
|
var updatedNotificationExpectation: XCTestExpectation!
|
|
var stopNotificationExpectation: XCTestExpectation!
|
|
var finishedNotificationExpectation: XCTestExpectation!
|
|
let network = ZcashNetworkBuilder.network(for: .testnet)
|
|
let mockLatestHeight = ZcashNetworkBuilder.network(for: .testnet).constants.saplingActivationHeight + 2000
|
|
|
|
let testFileManager = FileManager()
|
|
|
|
override func setUp() async throws {
|
|
try await super.setUp()
|
|
logger = OSLogger(logLevel: .debug)
|
|
|
|
for key in InternalSyncProgress.Key.allCases {
|
|
UserDefaults.standard.set(0, forKey: key.with(.default))
|
|
}
|
|
|
|
let pathProvider = DefaultResourceProvider(network: network)
|
|
processorConfig = 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 liveService = LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.eccTestnet).make()
|
|
let service = MockLightWalletService(
|
|
latestBlockHeight: mockLatestHeight,
|
|
service: liveService
|
|
)
|
|
|
|
rustBackend = ZcashRustBackend.makeForTests(
|
|
dbData: processorConfig.dataDb,
|
|
fsBlockDbRoot: processorConfig.fsBlockCacheRoot,
|
|
networkType: network.networkType
|
|
)
|
|
|
|
let branchID = try rustBackend.consensusBranchIdFor(height: Int32(mockLatestHeight))
|
|
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 transactionRepository = MockTransactionRepository(
|
|
unminedCount: 0,
|
|
receivedCount: 0,
|
|
sentCount: 0,
|
|
scannedHeight: 0,
|
|
network: network
|
|
)
|
|
|
|
Dependencies.setup(
|
|
in: mockContainer,
|
|
urls: Initializer.URLs(
|
|
fsBlockDbRoot: testTempDirectory,
|
|
dataDbURL: processorConfig.dataDb,
|
|
generalStorageURL: testGeneralStorageDirectory,
|
|
spendParamsURL: processorConfig.spendParamsURL,
|
|
outputParamsURL: processorConfig.outputParamsURL
|
|
),
|
|
alias: .default,
|
|
networkType: .testnet,
|
|
endpoint: LightWalletEndpointBuilder.default,
|
|
loggingPolicy: .default(.debug)
|
|
)
|
|
|
|
mockContainer.mock(type: LatestBlocksDataProvider.self, isSingleton: true) { _ in
|
|
LatestBlocksDataProviderImpl(service: service, transactionRepository: transactionRepository)
|
|
}
|
|
mockContainer.mock(type: ZcashRustBackendWelding.self, isSingleton: true) { _ in self.rustBackend }
|
|
mockContainer.mock(type: LightWalletService.self, isSingleton: true) { _ in service }
|
|
try await mockContainer.resolve(CompactBlockRepository.self).create()
|
|
|
|
processor = CompactBlockProcessor(container: mockContainer, config: processorConfig)
|
|
|
|
let dbInit = try await rustBackend.initDataDb(seed: nil)
|
|
|
|
guard case .success = dbInit else {
|
|
XCTFail("Failed to initDataDb. Expected `.success` got: \(dbInit)")
|
|
return
|
|
}
|
|
|
|
syncStartedExpect = XCTestExpectation(description: "\(self.description) syncStartedExpect")
|
|
stopNotificationExpectation = XCTestExpectation(description: "\(self.description) stopNotificationExpectation")
|
|
updatedNotificationExpectation = XCTestExpectation(description: "\(self.description) updatedNotificationExpectation")
|
|
finishedNotificationExpectation = XCTestExpectation(description: "\(self.description) finishedNotificationExpectation")
|
|
|
|
let eventClosure: CompactBlockProcessor.EventClosure = { [weak self] event in
|
|
switch event {
|
|
case .failed: self?.processorFailed(event: event)
|
|
default: break
|
|
}
|
|
}
|
|
|
|
await self.processor.updateEventClosure(identifier: "tests", closure: eventClosure)
|
|
}
|
|
|
|
override func tearDown() async throws {
|
|
try await super.tearDown()
|
|
await processor.stop()
|
|
try? FileManager.default.removeItem(at: processorConfig.dataDb)
|
|
cancellables = []
|
|
processor = nil
|
|
processorEventHandler = nil
|
|
rustBackend = nil
|
|
testTempDirectory = nil
|
|
}
|
|
|
|
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
|
|
]
|
|
|
|
await processorEventHandler.subscribe(to: processor, expectations: expectations)
|
|
await processor.start()
|
|
}
|
|
|
|
func testStartNotifiesSuscriptors() async {
|
|
await startProcessing()
|
|
|
|
await fulfillment(
|
|
of: [
|
|
syncStartedExpect,
|
|
finishedNotificationExpectation
|
|
],
|
|
timeout: 30,
|
|
enforceOrder: false
|
|
)
|
|
}
|
|
|
|
func testProgressNotifications() async {
|
|
let expectedUpdates = expectedBatches(
|
|
currentHeight: processorConfig.walletBirthday,
|
|
targetHeight: mockLatestHeight,
|
|
batchSize: processorConfig.batchSize
|
|
)
|
|
updatedNotificationExpectation.expectedFulfillmentCount = expectedUpdates
|
|
|
|
await startProcessing()
|
|
await fulfillment(of: [updatedNotificationExpectation, finishedNotificationExpectation], timeout: 300)
|
|
}
|
|
|
|
private func expectedBatches(currentHeight: BlockHeight, targetHeight: BlockHeight, batchSize: Int) -> Int {
|
|
(abs(currentHeight - targetHeight) / batchSize)
|
|
}
|
|
|
|
func testNextBatchBlockRange() async throws {
|
|
// test first range
|
|
var latestDownloadedHeight = processorConfig.walletBirthday // this can be either this or Wallet Birthday.
|
|
var latestBlockchainHeight = BlockHeight(network.constants.saplingActivationHeight + 1000)
|
|
|
|
var expectedSyncRanges = SyncRanges(
|
|
latestBlockHeight: latestBlockchainHeight,
|
|
downloadRange: latestDownloadedHeight...latestBlockchainHeight,
|
|
scanRange: latestDownloadedHeight...latestBlockchainHeight,
|
|
enhanceRange: processorConfig.walletBirthday...latestBlockchainHeight,
|
|
fetchUTXORange: processorConfig.walletBirthday...latestBlockchainHeight,
|
|
latestScannedHeight: 0,
|
|
latestDownloadedBlockHeight: latestDownloadedHeight
|
|
)
|
|
|
|
var internalSyncProgress = InternalSyncProgress(
|
|
alias: .default,
|
|
storage: InternalSyncProgressMemoryStorage(),
|
|
logger: logger
|
|
)
|
|
try await internalSyncProgress.migrateIfNeeded(latestDownloadedBlockHeightFromCacheDB: latestDownloadedHeight, alias: .default)
|
|
|
|
var syncRanges = try await internalSyncProgress.computeSyncRanges(
|
|
birthday: processorConfig.walletBirthday,
|
|
latestBlockHeight: latestBlockchainHeight,
|
|
latestScannedHeight: 0
|
|
)
|
|
|
|
XCTAssertEqual(
|
|
expectedSyncRanges,
|
|
syncRanges,
|
|
"Failure when testing first range"
|
|
)
|
|
|
|
// Test mid-range
|
|
latestDownloadedHeight = BlockHeight(network.constants.saplingActivationHeight + ZcashSDK.DefaultBatchSize)
|
|
latestBlockchainHeight = BlockHeight(network.constants.saplingActivationHeight + 1000)
|
|
|
|
expectedSyncRanges = SyncRanges(
|
|
latestBlockHeight: latestBlockchainHeight,
|
|
downloadRange: latestDownloadedHeight + 1...latestBlockchainHeight,
|
|
scanRange: processorConfig.walletBirthday...latestBlockchainHeight,
|
|
enhanceRange: processorConfig.walletBirthday...latestBlockchainHeight,
|
|
fetchUTXORange: processorConfig.walletBirthday...latestBlockchainHeight,
|
|
latestScannedHeight: 0,
|
|
latestDownloadedBlockHeight: latestDownloadedHeight
|
|
)
|
|
|
|
internalSyncProgress = InternalSyncProgress(
|
|
alias: .default,
|
|
storage: InternalSyncProgressMemoryStorage(),
|
|
logger: logger
|
|
)
|
|
try await internalSyncProgress.migrateIfNeeded(latestDownloadedBlockHeightFromCacheDB: latestDownloadedHeight, alias: .default)
|
|
|
|
syncRanges = try await internalSyncProgress.computeSyncRanges(
|
|
birthday: processorConfig.walletBirthday,
|
|
latestBlockHeight: latestBlockchainHeight,
|
|
latestScannedHeight: 0
|
|
)
|
|
|
|
XCTAssertEqual(
|
|
expectedSyncRanges,
|
|
syncRanges,
|
|
"Failure when testing mid range"
|
|
)
|
|
|
|
// Test last batch range
|
|
|
|
latestDownloadedHeight = BlockHeight(network.constants.saplingActivationHeight + 950)
|
|
latestBlockchainHeight = BlockHeight(network.constants.saplingActivationHeight + 1000)
|
|
|
|
expectedSyncRanges = SyncRanges(
|
|
latestBlockHeight: latestBlockchainHeight,
|
|
downloadRange: latestDownloadedHeight + 1...latestBlockchainHeight,
|
|
scanRange: processorConfig.walletBirthday...latestBlockchainHeight,
|
|
enhanceRange: processorConfig.walletBirthday...latestBlockchainHeight,
|
|
fetchUTXORange: processorConfig.walletBirthday...latestBlockchainHeight,
|
|
latestScannedHeight: 0,
|
|
latestDownloadedBlockHeight: latestDownloadedHeight
|
|
)
|
|
|
|
internalSyncProgress = InternalSyncProgress(
|
|
alias: .default,
|
|
storage: InternalSyncProgressMemoryStorage(),
|
|
logger: logger
|
|
)
|
|
try await internalSyncProgress.migrateIfNeeded(latestDownloadedBlockHeightFromCacheDB: latestDownloadedHeight, alias: .default)
|
|
|
|
syncRanges = try await internalSyncProgress.computeSyncRanges(
|
|
birthday: processorConfig.walletBirthday,
|
|
latestBlockHeight: latestBlockchainHeight,
|
|
latestScannedHeight: 0
|
|
)
|
|
|
|
XCTAssertEqual(
|
|
expectedSyncRanges,
|
|
syncRanges,
|
|
"Failure when testing last range"
|
|
)
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|