ZcashLightClientKit/Tests/DarksideTests/ReOrgTests.swift

206 lines
7.2 KiB
Swift
Raw Normal View History

//
// ReOrgTests.swift
// ZcashLightClientKit-Unit-Tests
//
// Created by Francisco Gindre on 3/23/20.
//
import Combine
import XCTest
2022-02-28 09:03:20 -08:00
@testable import TestUtils
@testable import ZcashLightClientKit
2021-09-23 06:26:41 -07:00
/**
2021-09-23 06:26:41 -07:00
basic reorg test. Scan, get a reorg and then reach latest height.
2021-07-28 09:59:10 -07:00
2021-09-23 06:26:41 -07:00
* connect to dLWD
* request latest height -> receive 663250
* download and sync blocks from 663150 to 663250
* trigger reorg by calling API (no need to pass params)**
* request latest height -> receive 663251!
* download that block
* observe that the prev hash of that block does not match the hash that we have for 663250
* rewind 10 blocks and request blocks 663241 to 663251
*/
class ReOrgTests: XCTestCase {
2021-07-28 09:59:10 -07:00
let sendAmount: Int64 = 1000
let defaultLatestHeight: BlockHeight = 663175
2021-09-23 06:26:41 -07:00
let network = DarksideWalletDNetwork()
let branchID = "2bb40e60"
let chainName = "main"
let mockLatestHeight = BlockHeight(663250)
let targetLatestHeight = BlockHeight(663251)
let walletBirthday = BlockHeight(663150)
var birthday: BlockHeight = 663150
var reorgExpectation = XCTestExpectation(description: "reorg")
2021-07-28 09:59:10 -07:00
var coordinator: TestCoordinator!
var syncedExpectation = XCTestExpectation(description: "synced")
var sentTransactionExpectation = XCTestExpectation(description: "sent")
var expectedReorgHeight: BlockHeight = 665188
var expectedRewindHeight: BlockHeight = 665188
var cancellables: [AnyCancellable] = []
2021-09-23 06:26:41 -07:00
2021-07-28 09:59:10 -07:00
override func setUpWithError() throws {
2021-09-23 06:26:41 -07:00
try super.setUpWithError()
2022-10-31 05:57:10 -07:00
self.coordinator = try TestCoordinator(
seed: Environment.seedPhrase,
walletBirthday: self.birthday,
network: self.network
)
2022-10-31 05:57:10 -07:00
try self.coordinator.reset(saplingActivation: self.birthday, branchID: self.branchID, chainName: self.chainName)
try self.coordinator.resetBlocks(dataset: .default)
var stream: AnyPublisher<CompactBlockProcessor.Event, Never>!
XCTestCase.wait { await stream = self.coordinator.synchronizer.blockProcessor.eventStream }
stream
.sink { [weak self] event in
switch event {
case .handledReorg: self?.handleReOrgNotification(event: event)
default: break
}
}
.store(in: &cancellables)
2021-09-23 06:26:41 -07:00
}
override func tearDownWithError() throws {
try super.tearDownWithError()
- [#679] Implementation of the File-system based block cache (#679) Closes https://github.com/zcash/ZcashLightClientKit/issues/697 Closes https://github.com/zcash/ZcashLightClientKit/issues/720 Closes https://github.com/zcash/ZcashLightClientKit/issues/587 Closes https://github.com/zcash/ZcashLightClientKit/issues/667 Closes https://github.com/zcash/ZcashLightClientKit/issues/443 Closes https://github.com/zcash/ZcashLightClientKit/issues/754 - [#790] Fix ShieldFundsTests Closes #790 Removes comments on `ShieldFundsTests` since those issues have been fixed Depends on zcash-light-client-ffi changes that adopt newer versions of librustzcash crates `zcash_primitives 0.10`, `zcash_client_backend 0.7`, `zcash_proofs 0.10`, `zcash_client_sqlite 0.5.0`. Also allows wallets to define a shielding_threshold and will set foundations to customize minimum confirmations for balances, spends and shielding operations. **Test Bootstrapping** - `ZcashCompactBlockDescriptor`: struct that holds functions to describe blocks as filenames and compare those filenames `ZcashCompactBlockDescriptor.live` has the actual implementation but it can be replaced by mocks if needed on Tests main implementations are held under `FSCompactBlockRepository.filenameDescription` and `FSCompactBlockRepository.filenameComparison` on a separate extention `DirectoryListingProviders` provide two default implementations of listing a directory deterministically. `FileManager` does not define a sorting and needs to be done in-memory by calling `.sorted()` on the resulting collection. If this is a big toll on performance it can be changed to a POSIX implementation but this is good for now. `ZcashCompactBlockDescriptor` adds a `height` helper function to turn a filename into the height of the block stored. Implemented `func latestHeight() throws -> BlockHeight ` that returns the blockheight by querying the cache directory in a sorted fashion and getting the last value and turning the filename into a `BlockHeight` Added `Meta` struct to ZcashCompactBlock. Tests implemented: - `filterBlockFiles` - `testClearTheCache` - `testLatestHeightEmptyCacheThrows` - `testLatestHeightEmptyCacheThrowsAsync` - `testRewindEmptyCacheDoesNothing` - `testRewindEmptyCacheDoesNothingAsync` - `testWhenBlockIsStoredItFollowsTheDescribedFormat` - `testWhenBlockIsStoredItFollowsTheFilenameConvention` - `testGetLatestHeight` - `testRewindDeletesTheRightBlocks` test - `testPerformanceExample` test. This isn't a real performance test because the API doesn't work with async/await yet adopts `shield_funds` shielding threshold parameter Implements `initBlockMetadataDb` and fix tests Renames dbCache parameter to `fsBlockDbRoot`. Builds but tests don't pass. Removes cacheDb uses from code. Testing utilities still persist. Added needed information in MIGRATING and CHANGELOG. Added helper to perform deletion of legacy db and creation a the new file system backed cache. Renames parameters and changes code where needed. Network Constants turned into `enum` with static methods. DeletelastDownloadedBlock helper from initializer Removes CompactBlockStorage and CompactBlockEntity. Implements `latestCachedBlockHeight` on rustbackend. *Replaces dependencies on ZcashRustWelding with `FSMetadataStore`* This allows the tests to not depend in a particular implementation of either the MockRustBackend of or ZcashRustBackend. Also provides a way to test errors properly and switch implementations of critical areas like `writeBlocks`.
2023-02-02 08:58:12 -08:00
try? FileManager.default.removeItem(at: coordinator.databases.fsCacheDbRoot)
2021-09-23 06:26:41 -07:00
try? FileManager.default.removeItem(at: coordinator.databases.dataDB)
try? FileManager.default.removeItem(at: coordinator.databases.pendingDB)
}
func handleReOrgNotification(event: CompactBlockProcessor.Event) {
reorgExpectation.fulfill()
guard case let .handledReorg(reorgHeight, rewindHeight) = event else { return XCTFail("malformed reorg userInfo") }
print("reorgHeight: \(reorgHeight)")
print("rewindHeight: \(rewindHeight)")
XCTAssertTrue(reorgHeight > 0)
XCTAssertNoThrow(rewindHeight > 0)
}
func testBasicReOrg() throws {
let mockLatestHeight = BlockHeight(663200)
let targetLatestHeight = BlockHeight(663202)
let reOrgHeight = BlockHeight(663195)
let walletBirthday = Checkpoint.birthday(with: 663150, network: network).height
2021-09-23 06:26:41 -07:00
try basicReOrgTest(
baseDataset: .beforeReOrg,
reorgDataset: .afterSmallReorg,
firstLatestHeight: mockLatestHeight,
reorgHeight: reOrgHeight,
walletBirthday: walletBirthday,
targetHeight: targetLatestHeight
)
}
func testTenPlusBlockReOrg() throws {
let mockLatestHeight = BlockHeight(663200)
let targetLatestHeight = BlockHeight(663250)
let reOrgHeight = BlockHeight(663180)
let walletBirthday = Checkpoint.birthday(with: BlockHeight(663150), network: network).height
2021-09-23 06:26:41 -07:00
try basicReOrgTest(
baseDataset: .beforeReOrg,
reorgDataset: .afterLargeReorg,
firstLatestHeight: mockLatestHeight,
reorgHeight: reOrgHeight,
walletBirthday: walletBirthday,
targetHeight: targetLatestHeight
)
}
2021-09-23 06:26:41 -07:00
func basicReOrgTest(
baseDataset: DarksideDataset,
reorgDataset: DarksideDataset,
firstLatestHeight: BlockHeight,
reorgHeight: BlockHeight,
walletBirthday: BlockHeight,
targetHeight: BlockHeight
) throws {
do {
2021-07-28 15:25:47 -07:00
try coordinator.reset(saplingActivation: birthday, branchID: branchID, chainName: chainName)
try coordinator.resetBlocks(dataset: .predefined(dataset: .beforeReOrg))
try coordinator.applyStaged(blockheight: firstLatestHeight)
sleep(1)
2021-09-23 06:26:41 -07:00
} catch {
XCTFail("Error: \(error)")
return
}
let firstSyncExpectation = XCTestExpectation(description: "firstSyncExpectation")
/**
2021-09-23 06:26:41 -07:00
download and sync blocks from walletBirthday to firstLatestHeight
*/
var synchronizer: SDKSynchronizer?
try coordinator.sync(
completion: { synchro in
synchronizer = synchro
firstSyncExpectation.fulfill()
},
error: self.handleError
)
wait(for: [firstSyncExpectation], timeout: 5)
guard let syncedSynchronizer = synchronizer else {
XCTFail("nil synchronizer")
return
}
2021-09-23 06:26:41 -07:00
/**
2021-09-23 06:26:41 -07:00
verify that mock height has been reached
*/
var latestDownloadedHeight = BlockHeight(0)
XCTAssertNoThrow(try { latestDownloadedHeight = try syncedSynchronizer.initializer.blockDownloaderService.latestBlockHeight() }())
XCTAssertTrue(latestDownloadedHeight > 0)
/**
2021-09-23 06:26:41 -07:00
trigger reorg!
*/
try coordinator.resetBlocks(dataset: .predefined(dataset: reorgDataset))
try coordinator.applyStaged(blockheight: targetHeight)
/**
2021-09-23 06:26:41 -07:00
request latest height -> receive targetHeight!
download that block
observe that the prev hash of that block does not match the hash that we have for firstLatestHeight
rewind 10 blocks and request blocks targetHeight-10 to targetHeight
*/
let secondSyncExpectation = XCTestExpectation(description: "second sync")
sleep(2)
2021-09-23 06:26:41 -07:00
try coordinator.sync(
completion: { _ in
secondSyncExpectation.fulfill()
},
error: self.handleError
)
// now reorg should happen and reorg notifications and idle notification should be triggered
2021-09-23 06:26:41 -07:00
wait(for: [reorgExpectation, secondSyncExpectation], timeout: 5)
// now everything should be fine. latest block should be targetHeight
XCTAssertNoThrow(try { latestDownloadedHeight = try syncedSynchronizer.initializer.blockDownloaderService.latestBlockHeight() }())
XCTAssertEqual(latestDownloadedHeight, targetHeight)
}
func handleError(_ error: Error?) {
guard let testError = error else {
XCTFail("failed with nil error")
return
}
XCTFail("Failed with error: \(testError)")
}
}