2022-09-16 12:59:31 -07:00
|
|
|
//
|
|
|
|
// SynchronizerTests.swift
|
|
|
|
// DarksideTests
|
|
|
|
//
|
|
|
|
// Created by Francisco Gindre on 9/16/22.
|
|
|
|
//
|
|
|
|
|
2023-02-07 05:22:28 -08:00
|
|
|
import Combine
|
2022-09-16 12:59:31 -07:00
|
|
|
import XCTest
|
|
|
|
@testable import TestUtils
|
|
|
|
@testable import ZcashLightClientKit
|
|
|
|
|
2023-05-01 07:28:59 -07:00
|
|
|
final class SynchronizerTests: ZcashTestCase {
|
2022-09-16 12:59:31 -07:00
|
|
|
let sendAmount = Zatoshi(1000)
|
|
|
|
var birthday: BlockHeight = 663150
|
|
|
|
let defaultLatestHeight: BlockHeight = 663175
|
|
|
|
var coordinator: TestCoordinator!
|
|
|
|
var expectedReorgHeight: BlockHeight = 665188
|
|
|
|
var expectedRewindHeight: BlockHeight = 665188
|
|
|
|
var reorgExpectation = XCTestExpectation(description: "reorg")
|
|
|
|
let branchID = "2bb40e60"
|
|
|
|
let chainName = "main"
|
|
|
|
let network = DarksideWalletDNetwork()
|
2023-02-07 05:22:28 -08:00
|
|
|
var cancellables: [AnyCancellable] = []
|
2023-04-28 10:13:21 -07:00
|
|
|
var sdkSynchronizerInternalSyncStatusHandler: SDKSynchronizerInternalSyncStatusHandler! = SDKSynchronizerInternalSyncStatusHandler()
|
2022-09-16 12:59:31 -07:00
|
|
|
|
2023-03-30 10:01:47 -07:00
|
|
|
override func setUp() async throws {
|
|
|
|
try await super.setUp()
|
2022-11-28 22:40:45 -08:00
|
|
|
|
2023-03-30 10:01:47 -07:00
|
|
|
// don't use an exact birthday, users never do.
|
2023-05-01 07:28:59 -07:00
|
|
|
self.coordinator = try await TestCoordinator(
|
|
|
|
container: mockContainer,
|
|
|
|
walletBirthday: birthday + 50,
|
|
|
|
network: network
|
|
|
|
)
|
2023-05-05 08:04:13 -07:00
|
|
|
try await coordinator.reset(saplingActivation: 663150, branchID: self.branchID, chainName: self.chainName)
|
2023-02-07 05:22:28 -08:00
|
|
|
|
2023-03-16 02:11:18 -07:00
|
|
|
let eventClosure: CompactBlockProcessor.EventClosure = { [weak self] event in
|
|
|
|
switch event {
|
|
|
|
case .handledReorg: self?.handleReorg(event: event)
|
|
|
|
default: break
|
2023-02-07 05:22:28 -08:00
|
|
|
}
|
2023-03-16 02:11:18 -07:00
|
|
|
}
|
|
|
|
|
2023-03-30 10:01:47 -07:00
|
|
|
await self.coordinator.synchronizer.blockProcessor.updateEventClosure(identifier: "tests", closure: eventClosure)
|
2022-09-16 12:59:31 -07:00
|
|
|
}
|
|
|
|
|
2023-03-30 10:01:47 -07:00
|
|
|
override func tearDown() async throws {
|
|
|
|
try await super.tearDown()
|
|
|
|
let coordinator = self.coordinator!
|
|
|
|
self.coordinator = nil
|
2023-04-28 10:13:21 -07:00
|
|
|
sdkSynchronizerInternalSyncStatusHandler = nil
|
2023-03-30 10:01:47 -07:00
|
|
|
cancellables = []
|
|
|
|
|
|
|
|
try await coordinator.stop()
|
2023-02-02 08:58:12 -08:00
|
|
|
try? FileManager.default.removeItem(at: coordinator.databases.fsCacheDbRoot)
|
2022-09-16 12:59:31 -07:00
|
|
|
try? FileManager.default.removeItem(at: coordinator.databases.dataDB)
|
|
|
|
}
|
|
|
|
|
2023-02-07 05:22:28 -08:00
|
|
|
func handleReorg(event: CompactBlockProcessor.Event) {
|
|
|
|
guard case let .handledReorg(reorgHeight, rewindHeight) = event else { return XCTFail("empty reorg notification") }
|
2022-09-16 12:59:31 -07:00
|
|
|
|
2023-03-22 05:47:32 -07:00
|
|
|
logger.debug("--- REORG DETECTED \(reorgHeight)--- RewindHeight: \(rewindHeight)", file: #file, function: #function, line: #line)
|
2022-09-16 12:59:31 -07:00
|
|
|
|
|
|
|
XCTAssertEqual(reorgHeight, expectedReorgHeight)
|
|
|
|
reorgExpectation.fulfill()
|
|
|
|
}
|
|
|
|
|
2022-10-27 03:51:38 -07:00
|
|
|
func testSynchronizerStops() async throws {
|
2022-09-16 12:59:31 -07:00
|
|
|
/*
|
|
|
|
1. create fake chain
|
|
|
|
*/
|
|
|
|
let fullSyncLength = 100_000
|
|
|
|
|
|
|
|
try FakeChainBuilder.buildChain(darksideWallet: coordinator.service, branchID: branchID, chainName: chainName, length: fullSyncLength)
|
|
|
|
|
|
|
|
try coordinator.applyStaged(blockheight: birthday + fullSyncLength)
|
|
|
|
|
|
|
|
sleep(10)
|
|
|
|
|
|
|
|
let syncStoppedExpectation = XCTestExpectation(description: "SynchronizerStopped Expectation")
|
2023-04-28 10:13:21 -07:00
|
|
|
sdkSynchronizerInternalSyncStatusHandler.subscribe(
|
2023-03-15 04:17:43 -07:00
|
|
|
to: coordinator.synchronizer.stateStream,
|
|
|
|
expectations: [.stopped: syncStoppedExpectation]
|
|
|
|
)
|
2022-09-16 12:59:31 -07:00
|
|
|
|
|
|
|
/*
|
|
|
|
sync to latest height
|
|
|
|
*/
|
2023-03-16 02:11:18 -07:00
|
|
|
try await coordinator.sync(
|
|
|
|
completion: { _ in
|
|
|
|
XCTFail("Sync should have stopped")
|
|
|
|
},
|
|
|
|
error: self.handleError
|
|
|
|
)
|
2022-09-16 12:59:31 -07:00
|
|
|
|
2022-10-27 03:51:38 -07:00
|
|
|
try await Task.sleep(nanoseconds: 5_000_000_000)
|
2023-05-05 10:30:47 -07:00
|
|
|
self.coordinator.synchronizer.stop()
|
2022-09-16 12:59:31 -07:00
|
|
|
|
2023-04-03 06:30:08 -07:00
|
|
|
await fulfillment(of: [syncStoppedExpectation], timeout: 6)
|
2022-09-16 12:59:31 -07:00
|
|
|
|
2023-03-16 02:11:18 -07:00
|
|
|
let status = await coordinator.synchronizer.status
|
|
|
|
XCTAssertEqual(status, .stopped)
|
2022-09-16 12:59:31 -07:00
|
|
|
}
|
|
|
|
|
2023-03-02 04:19:25 -08:00
|
|
|
// MARK: Wipe tests
|
|
|
|
|
2023-03-27 07:12:06 -07:00
|
|
|
func testWipeCalledWhichSyncDoesntRun() async throws {
|
2023-02-20 01:53:04 -08:00
|
|
|
/*
|
|
|
|
create fake chain
|
|
|
|
*/
|
|
|
|
let fullSyncLength = 1000
|
|
|
|
|
|
|
|
try FakeChainBuilder.buildChain(darksideWallet: coordinator.service, branchID: branchID, chainName: chainName, length: fullSyncLength)
|
|
|
|
|
|
|
|
try coordinator.applyStaged(blockheight: birthday + fullSyncLength)
|
|
|
|
|
|
|
|
sleep(2)
|
|
|
|
|
|
|
|
let syncFinished = XCTestExpectation(description: "SynchronizerSyncFinished Expectation")
|
|
|
|
|
|
|
|
/*
|
|
|
|
sync to latest height
|
|
|
|
*/
|
2023-03-16 02:11:18 -07:00
|
|
|
try await coordinator.sync(
|
2023-02-20 01:53:04 -08:00
|
|
|
completion: { _ in
|
|
|
|
syncFinished.fulfill()
|
|
|
|
},
|
|
|
|
error: handleError
|
|
|
|
)
|
|
|
|
|
2023-04-03 06:30:08 -07:00
|
|
|
await fulfillment(of: [syncFinished], timeout: 3)
|
2023-02-20 01:53:04 -08:00
|
|
|
|
|
|
|
let wipeFinished = XCTestExpectation(description: "SynchronizerWipeFinished Expectation")
|
|
|
|
|
|
|
|
/*
|
|
|
|
Call wipe
|
|
|
|
*/
|
|
|
|
coordinator.synchronizer.wipe()
|
|
|
|
.sink(
|
|
|
|
receiveCompletion: { completion in
|
|
|
|
switch completion {
|
|
|
|
case .finished:
|
|
|
|
wipeFinished.fulfill()
|
|
|
|
|
|
|
|
case .failure(let error):
|
|
|
|
XCTFail("Wipe should finish successfully. \(error)")
|
|
|
|
}
|
|
|
|
},
|
|
|
|
receiveValue: {
|
|
|
|
XCTFail("No no value should be received from wipe.")
|
|
|
|
}
|
|
|
|
)
|
|
|
|
.store(in: &cancellables)
|
|
|
|
|
2023-04-03 06:30:08 -07:00
|
|
|
await fulfillment(of: [wipeFinished], timeout: 1)
|
2023-02-20 01:53:04 -08:00
|
|
|
|
|
|
|
/*
|
|
|
|
Check that wipe cleared everything that is expected
|
|
|
|
*/
|
2023-05-21 09:48:29 -07:00
|
|
|
try await checkThatWipeWorked()
|
2023-02-20 01:53:04 -08:00
|
|
|
}
|
|
|
|
|
2023-03-27 07:12:06 -07:00
|
|
|
func testWipeCalledWhileSyncRuns() async throws {
|
2023-02-20 01:53:04 -08:00
|
|
|
/*
|
|
|
|
1. create fake chain
|
|
|
|
*/
|
|
|
|
let fullSyncLength = 50_000
|
|
|
|
|
|
|
|
try FakeChainBuilder.buildChain(darksideWallet: coordinator.service, branchID: branchID, chainName: chainName, length: fullSyncLength)
|
|
|
|
|
|
|
|
try coordinator.applyStaged(blockheight: birthday + fullSyncLength)
|
|
|
|
|
|
|
|
sleep(5)
|
|
|
|
|
|
|
|
/*
|
|
|
|
Start sync
|
|
|
|
*/
|
2023-03-16 02:11:18 -07:00
|
|
|
try await coordinator.sync(
|
|
|
|
completion: { _ in
|
|
|
|
XCTFail("Sync should have stopped")
|
|
|
|
},
|
|
|
|
error: self.handleError
|
|
|
|
)
|
2023-02-20 01:53:04 -08:00
|
|
|
|
|
|
|
try await Task.sleep(nanoseconds: 2_000_000_000)
|
|
|
|
|
|
|
|
// Just to be sure that blockProcessor is still syncing and that this test does what it should.
|
2023-05-05 08:04:13 -07:00
|
|
|
let synchronizerState = coordinator.synchronizer.latestState.syncStatus
|
|
|
|
switch synchronizerState {
|
|
|
|
case .syncing:
|
|
|
|
break
|
|
|
|
default:
|
|
|
|
XCTFail("Synchornizer should be in syncing state.")
|
|
|
|
}
|
2023-02-20 01:53:04 -08:00
|
|
|
|
|
|
|
let wipeFinished = XCTestExpectation(description: "SynchronizerWipeFinished Expectation")
|
|
|
|
/*
|
|
|
|
Call wipe
|
|
|
|
*/
|
|
|
|
coordinator.synchronizer.wipe()
|
|
|
|
.sink(
|
|
|
|
receiveCompletion: { completion in
|
|
|
|
switch completion {
|
|
|
|
case .finished:
|
|
|
|
wipeFinished.fulfill()
|
|
|
|
|
|
|
|
case .failure(let error):
|
|
|
|
XCTFail("Wipe should finish successfully. \(error)")
|
|
|
|
}
|
|
|
|
},
|
|
|
|
receiveValue: {
|
|
|
|
XCTFail("No no value should be received from wipe.")
|
|
|
|
}
|
|
|
|
)
|
|
|
|
.store(in: &cancellables)
|
|
|
|
|
2023-04-03 06:30:08 -07:00
|
|
|
await fulfillment(of: [wipeFinished], timeout: 6)
|
2023-02-20 01:53:04 -08:00
|
|
|
|
|
|
|
/*
|
|
|
|
Check that wipe cleared everything that is expected
|
|
|
|
*/
|
2023-05-21 09:48:29 -07:00
|
|
|
try await checkThatWipeWorked()
|
2023-02-20 01:53:04 -08:00
|
|
|
}
|
|
|
|
|
2023-05-21 09:48:29 -07:00
|
|
|
private func checkThatWipeWorked() async throws {
|
2023-02-20 01:53:04 -08:00
|
|
|
let storage = await self.coordinator.synchronizer.blockProcessor.storage as! FSCompactBlockRepository
|
|
|
|
let fm = FileManager.default
|
2023-05-05 08:04:13 -07:00
|
|
|
|
2023-03-15 04:17:43 -07:00
|
|
|
XCTAssertFalse(fm.fileExists(atPath: coordinator.synchronizer.initializer.dataDbURL.path), "Data DB should be deleted.")
|
|
|
|
XCTAssertTrue(fm.fileExists(atPath: storage.blocksDirectory.path), "FS Cache directory should exist")
|
|
|
|
XCTAssertEqual(try fm.contentsOfDirectory(atPath: storage.blocksDirectory.path), [], "FS Cache directory should be empty")
|
2023-02-20 01:53:04 -08:00
|
|
|
|
2023-03-16 02:11:18 -07:00
|
|
|
let status = await coordinator.synchronizer.status
|
|
|
|
XCTAssertEqual(status, .unprepared, "SDKSynchronizer state should be unprepared")
|
2023-02-20 01:53:04 -08:00
|
|
|
}
|
|
|
|
|
2023-03-16 02:11:18 -07:00
|
|
|
func handleError(_ error: Error?) async {
|
|
|
|
_ = try? await coordinator.stop()
|
2022-09-16 12:59:31 -07:00
|
|
|
guard let testError = error else {
|
|
|
|
XCTFail("failed with nil error")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
XCTFail("Failed with error: \(testError)")
|
|
|
|
}
|
2023-03-02 04:19:25 -08:00
|
|
|
|
|
|
|
// MARK: Rewind tests
|
|
|
|
|
2023-03-27 07:12:06 -07:00
|
|
|
func testRewindCalledWhileSyncRuns() async throws {
|
2023-03-02 04:19:25 -08:00
|
|
|
// 1 sync and get spendable funds
|
|
|
|
try FakeChainBuilder.buildChain(darksideWallet: coordinator.service, branchID: branchID, chainName: chainName)
|
|
|
|
|
2023-05-05 10:30:47 -07:00
|
|
|
try coordinator.applyStaged(blockheight: 663200)
|
2023-03-30 03:49:28 -07:00
|
|
|
let initialVerifiedBalance: Zatoshi = try await coordinator.synchronizer.getShieldedVerifiedBalance()
|
|
|
|
let initialTotalBalance: Zatoshi = try await coordinator.synchronizer.getShieldedBalance()
|
2023-03-02 04:19:25 -08:00
|
|
|
sleep(1)
|
|
|
|
let firstSyncExpectation = XCTestExpectation(description: "first sync expectation")
|
|
|
|
|
|
|
|
do {
|
2023-03-16 02:11:18 -07:00
|
|
|
try await coordinator.sync(
|
2023-03-02 04:19:25 -08:00
|
|
|
completion: { _ in
|
|
|
|
firstSyncExpectation.fulfill()
|
|
|
|
},
|
|
|
|
error: self.handleError
|
|
|
|
)
|
|
|
|
} catch {
|
2023-03-16 02:11:18 -07:00
|
|
|
await handleError(error)
|
2023-03-02 04:19:25 -08:00
|
|
|
}
|
|
|
|
|
2023-04-03 06:30:08 -07:00
|
|
|
await fulfillment(of: [firstSyncExpectation], timeout: 12)
|
2023-03-02 04:19:25 -08:00
|
|
|
|
2023-05-05 10:30:47 -07:00
|
|
|
let verifiedBalance: Zatoshi = try await coordinator.synchronizer.getShieldedVerifiedBalance()
|
|
|
|
let totalBalance: Zatoshi = try await coordinator.synchronizer.getShieldedBalance()
|
|
|
|
// 2 check that there are no unconfirmed funds
|
|
|
|
XCTAssertTrue(verifiedBalance > network.constants.defaultFee(for: defaultLatestHeight))
|
|
|
|
XCTAssertEqual(verifiedBalance, totalBalance)
|
|
|
|
|
2023-03-02 04:19:25 -08:00
|
|
|
// Add more blocks to the chain so the long sync can start.
|
|
|
|
try FakeChainBuilder.buildChain(darksideWallet: coordinator.service, branchID: branchID, chainName: chainName, length: 10000)
|
|
|
|
try coordinator.applyStaged(blockheight: birthday + 10000)
|
|
|
|
|
|
|
|
sleep(2)
|
|
|
|
|
|
|
|
do {
|
|
|
|
// Start the long sync.
|
2023-03-16 02:11:18 -07:00
|
|
|
try await coordinator.sync(
|
2023-03-02 04:19:25 -08:00
|
|
|
completion: { _ in },
|
|
|
|
error: self.handleError
|
|
|
|
)
|
|
|
|
} catch {
|
2023-03-16 02:11:18 -07:00
|
|
|
await handleError(error)
|
2023-03-02 04:19:25 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Wait 0.5 second and then start rewind while sync is in progress.
|
|
|
|
let waitExpectation = XCTestExpectation()
|
|
|
|
DispatchQueue.global().asyncAfter(deadline: .now() + 0.5) {
|
|
|
|
waitExpectation.fulfill()
|
|
|
|
}
|
|
|
|
|
2023-04-03 06:30:08 -07:00
|
|
|
await fulfillment(of: [waitExpectation], timeout: 1)
|
2023-03-02 04:19:25 -08:00
|
|
|
|
|
|
|
let rewindExpectation = XCTestExpectation(description: "RewindExpectation")
|
|
|
|
|
|
|
|
// rewind to birthday
|
|
|
|
coordinator.synchronizer.rewind(.birthday)
|
|
|
|
.sink(
|
|
|
|
receiveCompletion: { result in
|
|
|
|
switch result {
|
|
|
|
case .finished:
|
|
|
|
break
|
|
|
|
case let .failure(error):
|
|
|
|
XCTFail("Rewind failed with error: \(error)")
|
|
|
|
}
|
|
|
|
rewindExpectation.fulfill()
|
|
|
|
},
|
2023-05-05 10:30:47 -07:00
|
|
|
receiveValue: { _ in rewindExpectation.fulfill() }
|
2023-03-02 04:19:25 -08:00
|
|
|
)
|
|
|
|
.store(in: &cancellables)
|
|
|
|
|
2023-04-03 06:30:08 -07:00
|
|
|
await fulfillment(of: [rewindExpectation], timeout: 5)
|
2023-03-02 04:19:25 -08:00
|
|
|
|
|
|
|
// assert that after the new height is
|
2023-03-27 07:12:06 -07:00
|
|
|
let lastScannedHeight = try await coordinator.synchronizer.initializer.transactionRepository.lastScannedHeight()
|
|
|
|
XCTAssertEqual(lastScannedHeight, self.birthday)
|
2023-03-02 04:19:25 -08:00
|
|
|
|
|
|
|
// check that the balance is cleared
|
2023-03-30 03:49:28 -07:00
|
|
|
let expectedVerifiedBalance = try await coordinator.synchronizer.getShieldedVerifiedBalance()
|
|
|
|
let expectedBalance = try await coordinator.synchronizer.getShieldedBalance()
|
|
|
|
XCTAssertEqual(initialVerifiedBalance, expectedVerifiedBalance)
|
|
|
|
XCTAssertEqual(initialTotalBalance, expectedBalance)
|
2023-03-02 04:19:25 -08:00
|
|
|
}
|
2022-09-16 12:59:31 -07:00
|
|
|
}
|