FigureNextBatchOperation.swift tests

This commit is contained in:
Francisco Gindre 2021-06-17 20:58:30 -03:00
parent 292d08af7a
commit b5cd1eb84f
6 changed files with 455 additions and 34 deletions

View File

@ -506,34 +506,32 @@ public class CompactBlockProcessor {
saplingActivation: BlockHeight,
rustBackend: ZcashRustBackendWelding.Type) throws {
do {
// check network types
guard let remoteNetworkType = ZcashSDK.NetworkType(info.chainName) else {
throw CompactBlockProcessorError.generalError(message: "Chain name does not match. Expected either 'test' or 'main' but received '\(info.chainName)'. this is probably an API or programming error")
}
guard remoteNetworkType == ZcashSDK.networkType else {
throw CompactBlockProcessorError.networkMismatch(expected: ZcashSDK.networkType, found: remoteNetworkType)
}
guard saplingActivation == info.saplingActivationHeight else {
throw CompactBlockProcessorError.saplingActivationMismatch(expected: saplingActivation, found: BlockHeight(info.saplingActivationHeight))
}
// check branch id
let localBranch = try rustBackend.consensusBranchIdFor(height: Int32(info.blockHeight))
guard let remoteBranchID = ConsensusBranchID.fromString(info.consensusBranchID)
else {
throw CompactBlockProcessorError.generalError(message: "Consensus BranchIDs don't match this is probably an API or programming error")
}
guard remoteBranchID == localBranch else {
throw CompactBlockProcessorError.wrongConsensusBranchId(expectedLocally: localBranch, found: remoteBranchID)
}
} catch {
throw CompactBlockProcessorError.unspecifiedError(underlyingError: error)
// check network types
guard let remoteNetworkType = ZcashSDK.NetworkType(info.chainName) else {
throw CompactBlockProcessorError.generalError(message: "Chain name does not match. Expected either 'test' or 'main' but received '\(info.chainName)'. this is probably an API or programming error")
}
guard remoteNetworkType == ZcashSDK.networkType else {
throw CompactBlockProcessorError.networkMismatch(expected: ZcashSDK.networkType, found: remoteNetworkType)
}
guard saplingActivation == info.saplingActivationHeight else {
throw CompactBlockProcessorError.saplingActivationMismatch(expected: saplingActivation, found: BlockHeight(info.saplingActivationHeight))
}
// check branch id
let localBranch = try rustBackend.consensusBranchIdFor(height: Int32(info.blockHeight))
guard let remoteBranchID = ConsensusBranchID.fromString(info.consensusBranchID)
else {
throw CompactBlockProcessorError.generalError(message: "Consensus BranchIDs don't match this is probably an API or programming error")
}
guard remoteBranchID == localBranch else {
throw CompactBlockProcessorError.wrongConsensusBranchId(expectedLocally: localBranch, found: remoteBranchID)
}
}
/**

View File

@ -0,0 +1,62 @@
//
// FigureNextBatchOperation.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 6/17/21.
//
import Foundation
class FigureNextBatchOperation: ZcashOperation {
enum NextState {
case finishProcessing(height: BlockHeight)
case processNewBlocks(range: CompactBlockRange)
case wait(latestHeight: BlockHeight, latestDownloadHeight: BlockHeight)
}
private var service: LightWalletService
private var downloader: CompactBlockDownloading
private var config: CompactBlockProcessor.Configuration
private var rustBackend: ZcashRustBackendWelding.Type
private(set) var result: NextState?
required init(downloader: CompactBlockDownloading,
service: LightWalletService,
config: CompactBlockProcessor.Configuration,
rustBackend: ZcashRustBackendWelding.Type) {
self.service = service
self.config = config
self.downloader = downloader
self.rustBackend = rustBackend
super.init()
self.name = "Next Batch Operation"
}
override func main() {
guard !shouldCancel() else {
cancel()
return
}
self.startedHandler?()
do {
let info = try service.getInfo()
try CompactBlockProcessor.validateServerInfo(info, saplingActivation: config.saplingActivation, rustBackend: self.rustBackend)
// get latest block height
let latestDownloadedBlockHeight: BlockHeight = max(config.walletBirthday,try downloader.lastDownloadedBlockHeight())
let latestBlockheight = BlockHeight(info.blockHeight)
if latestDownloadedBlockHeight < latestBlockheight {
result = .processNewBlocks(range: CompactBlockProcessor.nextBatchBlockRange(latestHeight: latestBlockheight, latestDownloadedHeight: latestDownloadedBlockHeight, walletBirthday: config.walletBirthday))
} else if latestBlockheight == latestDownloadedBlockHeight {
result = .finishProcessing(height: latestBlockheight)
} else {
result = .wait(latestHeight: latestBlockheight, latestDownloadHeight: latestBlockheight)
}
} catch {
self.fail(error: error)
}
}
}

View File

@ -267,7 +267,6 @@ class AdvancedReOrgTests: XCTestCase {
try FakeChainBuilder.buildChain(darksideWallet: self.coordinator.service, branchID: branchID, chainName: chainName)
let receivedTxHeight: BlockHeight = 663188
var initialTotalBalance: Int64 = -1
var initialVerifiedBalance: Int64 = -1
/*
2. applyStaged(received_Tx_height)
@ -281,7 +280,6 @@ class AdvancedReOrgTests: XCTestCase {
3. sync up to received_Tx_height
*/
try coordinator.sync(completion: { (synchronizer) in
initialVerifiedBalance = synchronizer.initializer.getVerifiedBalance()
initialTotalBalance = synchronizer.initializer.getBalance()
preTxExpectation.fulfill()
}, error: self.handleError)

View File

@ -0,0 +1,349 @@
//
// BlockBatchValidationTests.swift
// ZcashLightClientKit-Unit-Tests
//
// Created by Francisco Gindre on 6/17/21.
//
import XCTest
@testable import ZcashLightClientKit
class BlockBatchValidationTests: XCTestCase {
var queue: OperationQueue = {
let q = OperationQueue()
q.name = "Test Queue"
q.maxConcurrentOperationCount = 1
return q
}()
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
func testBranchIdFailure() throws {
let service = MockLightWalletService(latestBlockHeight: 1210000)
let repository = ZcashConsoleFakeStorage(latestBlockHeight: 1220000)
let downloader = CompactBlockDownloader(service: service, storage: repository)
let config = CompactBlockProcessor.Configuration(cacheDb: try! __cacheDbURL(), dataDb: try! __dataDbURL(), downloadBatchSize: 100, retries: 5, maxBackoffInterval: 10, rewindDistance: 100, walletBirthday: 1210000, saplingActivation: ZcashSDK.SAPLING_ACTIVATION_HEIGHT)
var info = LightdInfo()
info.blockHeight = 130000
info.branch = "d34db33f"
info.chainName = "main"
info.buildUser = "test user"
info.consensusBranchID = "d34db33f"
info.saplingActivationHeight = UInt64(ZcashSDK.SAPLING_ACTIVATION_HEIGHT)
service.mockLightDInfo = info
let mockRust = MockRustBackend.self
mockRust.consensusBranchID = Int32(0xd34d)
let operation = FigureNextBatchOperation(downloader: downloader, service: service, config: config, rustBackend: mockRust)
let expectation = XCTestExpectation(description: "failure expectation")
let startedExpectation = XCTestExpectation(description: "start Expectation")
operation.startedHandler = {
startedExpectation.fulfill()
}
operation.errorHandler = { error in
expectation.fulfill()
switch error {
case CompactBlockProcessorError.wrongConsensusBranchId:
break
default:
XCTFail("Expected CompactBlockProcessorError.wrongConsensusBranchId but found \(error)")
}
}
queue.addOperations([operation], waitUntilFinished: false)
wait(for: [startedExpectation,expectation], timeout: 1, enforceOrder: true)
XCTAssertNotNil(operation.error)
XCTAssertTrue(operation.isCancelled)
}
func testBranchNetworkMismatchFailure() throws {
let service = MockLightWalletService(latestBlockHeight: 1210000)
let repository = ZcashConsoleFakeStorage(latestBlockHeight: 1220000)
let downloader = CompactBlockDownloader(service: service, storage: repository)
let config = CompactBlockProcessor.Configuration(cacheDb: try! __cacheDbURL(), dataDb: try! __dataDbURL(), downloadBatchSize: 100, retries: 5, maxBackoffInterval: 10, rewindDistance: 100, walletBirthday: 1210000, saplingActivation: ZcashSDK.SAPLING_ACTIVATION_HEIGHT)
var info = LightdInfo()
info.blockHeight = 130000
info.branch = "d34db33f"
info.chainName = "test"
info.buildUser = "test user"
info.consensusBranchID = "d34db4d"
info.saplingActivationHeight = UInt64(ZcashSDK.SAPLING_ACTIVATION_HEIGHT)
service.mockLightDInfo = info
let mockRust = MockRustBackend.self
mockRust.consensusBranchID = 0xd34db4d
let operation = FigureNextBatchOperation(downloader: downloader, service: service, config: config, rustBackend: mockRust)
let expectation = XCTestExpectation(description: "failure expectation")
let startedExpectation = XCTestExpectation(description: "start Expectation")
operation.startedHandler = {
startedExpectation.fulfill()
}
operation.errorHandler = { error in
expectation.fulfill()
switch error {
case CompactBlockProcessorError.networkMismatch(expected: .mainnet, found: .testnet):
break
default:
XCTFail("Expected CompactBlockProcessorError.networkMismatch but found \(error)")
}
}
queue.addOperations([operation], waitUntilFinished: false)
wait(for: [startedExpectation,expectation], timeout: 1, enforceOrder: true)
XCTAssertNotNil(operation.error)
XCTAssertTrue(operation.isCancelled)
}
func testBranchNetworkTypeWrongFailure() throws {
let service = MockLightWalletService(latestBlockHeight: 1210000)
let repository = ZcashConsoleFakeStorage(latestBlockHeight: 1220000)
let downloader = CompactBlockDownloader(service: service, storage: repository)
let config = CompactBlockProcessor.Configuration(cacheDb: try! __cacheDbURL(), dataDb: try! __dataDbURL(), downloadBatchSize: 100, retries: 5, maxBackoffInterval: 10, rewindDistance: 100, walletBirthday: 1210000, saplingActivation: ZcashSDK.SAPLING_ACTIVATION_HEIGHT)
var info = LightdInfo()
info.blockHeight = 130000
info.branch = "d34db33f"
info.chainName = "another"
info.buildUser = "test user"
info.consensusBranchID = "d34db4d"
info.saplingActivationHeight = UInt64(ZcashSDK.SAPLING_ACTIVATION_HEIGHT)
service.mockLightDInfo = info
let mockRust = MockRustBackend.self
mockRust.consensusBranchID = 0xd34db4d
let operation = FigureNextBatchOperation(downloader: downloader, service: service, config: config, rustBackend: mockRust)
let expectation = XCTestExpectation(description: "failure expectation")
let startedExpectation = XCTestExpectation(description: "start Expectation")
operation.startedHandler = {
startedExpectation.fulfill()
}
operation.errorHandler = { error in
expectation.fulfill()
switch error {
case CompactBlockProcessorError.generalError:
break
default:
XCTFail("Expected CompactBlockProcessorError.generalError but found \(error)")
}
}
queue.addOperations([operation], waitUntilFinished: false)
wait(for: [startedExpectation,expectation], timeout: 1, enforceOrder: true)
XCTAssertNotNil(operation.error)
XCTAssertTrue(operation.isCancelled)
}
func testSaplingActivationHeightMismatch() throws {
let service = MockLightWalletService(latestBlockHeight: 1210000)
let repository = ZcashConsoleFakeStorage(latestBlockHeight: 1220000)
let downloader = CompactBlockDownloader(service: service, storage: repository)
let config = CompactBlockProcessor.Configuration(cacheDb: try! __cacheDbURL(), dataDb: try! __dataDbURL(), downloadBatchSize: 100, retries: 5, maxBackoffInterval: 10, rewindDistance: 100, walletBirthday: 1210000, saplingActivation: ZcashSDK.SAPLING_ACTIVATION_HEIGHT)
var info = LightdInfo()
info.blockHeight = 130000
info.branch = "d34db33f"
info.chainName = "main"
info.buildUser = "test user"
info.consensusBranchID = "d34db4d"
info.saplingActivationHeight = UInt64(3434343)
service.mockLightDInfo = info
let mockRust = MockRustBackend.self
mockRust.consensusBranchID = 0xd34db4d
let operation = FigureNextBatchOperation(downloader: downloader, service: service, config: config, rustBackend: mockRust)
let expectation = XCTestExpectation(description: "failure expectation")
let startedExpectation = XCTestExpectation(description: "start Expectation")
operation.startedHandler = {
startedExpectation.fulfill()
}
operation.errorHandler = { error in
expectation.fulfill()
switch error {
case CompactBlockProcessorError.saplingActivationMismatch(expected: ZcashSDK.SAPLING_ACTIVATION_HEIGHT, found: BlockHeight(info.saplingActivationHeight)):
break
default:
XCTFail("Expected CompactBlockProcessorError.saplingActivationMismatch but found \(error)")
}
}
queue.addOperations([operation], waitUntilFinished: false)
wait(for: [startedExpectation,expectation], timeout: 1, enforceOrder: true)
XCTAssertNotNil(operation.error)
XCTAssertTrue(operation.isCancelled)
}
func testResultIsWait() throws {
let expectedLatestHeight = BlockHeight(1210000)
let service = MockLightWalletService(latestBlockHeight: expectedLatestHeight)
let expectedStoreLatestHeight = BlockHeight(1220000)
let expectedResult = FigureNextBatchOperation.NextState.wait(latestHeight: expectedLatestHeight, latestDownloadHeight: expectedLatestHeight)
let repository = ZcashConsoleFakeStorage(latestBlockHeight: expectedStoreLatestHeight)
let downloader = CompactBlockDownloader(service: service, storage: repository)
let config = CompactBlockProcessor.Configuration(cacheDb: try! __cacheDbURL(), dataDb: try! __dataDbURL(), downloadBatchSize: 100, retries: 5, maxBackoffInterval: 10, rewindDistance: 100, walletBirthday: 1210000, saplingActivation: ZcashSDK.SAPLING_ACTIVATION_HEIGHT)
var info = LightdInfo()
info.blockHeight = UInt64(expectedLatestHeight)
info.branch = "d34db33f"
info.chainName = "main"
info.buildUser = "test user"
info.consensusBranchID = "d34db4d"
info.saplingActivationHeight = UInt64(ZcashSDK.SAPLING_ACTIVATION_HEIGHT)
service.mockLightDInfo = info
let mockRust = MockRustBackend.self
mockRust.consensusBranchID = 0xd34db4d
let operation = FigureNextBatchOperation(downloader: downloader, service: service, config: config, rustBackend: mockRust)
let completedExpectation = XCTestExpectation(description: "completed expectation")
let startedExpectation = XCTestExpectation(description: "start Expectation")
operation.startedHandler = {
startedExpectation.fulfill()
}
operation.errorHandler = { error in
XCTFail("this shouldn't happen")
}
operation.completionHandler = { (finished, cancelled) in
completedExpectation.fulfill()
XCTAssertTrue(finished)
XCTAssertFalse(cancelled)
}
queue.addOperations([operation], waitUntilFinished: false)
wait(for: [startedExpectation,completedExpectation], timeout: 1, enforceOrder: true)
XCTAssertNil(operation.error)
XCTAssertFalse(operation.isCancelled)
guard let result = operation.result else {
XCTFail("result should not be nil")
return
}
XCTAssertTrue({
switch result {
case .wait(latestHeight: expectedLatestHeight, latestDownloadHeight: expectedLatestHeight):
return true
default:
return false
}
}(), "Expected \(expectedResult) got: \(result)")
}
func testResultProcessNew() throws {
let expectedLatestHeight = BlockHeight(1230000)
let service = MockLightWalletService(latestBlockHeight: expectedLatestHeight)
let expectedStoreLatestHeight = BlockHeight(1220000)
let walletBirthday = BlockHeight(1210000)
let expectedResult = FigureNextBatchOperation.NextState.processNewBlocks(range: CompactBlockProcessor.nextBatchBlockRange(latestHeight: expectedLatestHeight, latestDownloadedHeight: expectedStoreLatestHeight, walletBirthday: walletBirthday))
let repository = ZcashConsoleFakeStorage(latestBlockHeight: expectedStoreLatestHeight)
let downloader = CompactBlockDownloader(service: service, storage: repository)
let config = CompactBlockProcessor.Configuration(cacheDb: try! __cacheDbURL(), dataDb: try! __dataDbURL(), downloadBatchSize: 100, retries: 5, maxBackoffInterval: 10, rewindDistance: 100, walletBirthday: walletBirthday, saplingActivation: ZcashSDK.SAPLING_ACTIVATION_HEIGHT)
var info = LightdInfo()
info.blockHeight = UInt64(expectedLatestHeight)
info.branch = "d34db33f"
info.chainName = "main"
info.buildUser = "test user"
info.consensusBranchID = "d34db4d"
info.saplingActivationHeight = UInt64(ZcashSDK.SAPLING_ACTIVATION_HEIGHT)
service.mockLightDInfo = info
let mockRust = MockRustBackend.self
mockRust.consensusBranchID = 0xd34db4d
let operation = FigureNextBatchOperation(downloader: downloader, service: service, config: config, rustBackend: mockRust)
let completedExpectation = XCTestExpectation(description: "completed expectation")
let startedExpectation = XCTestExpectation(description: "start Expectation")
operation.startedHandler = {
startedExpectation.fulfill()
}
operation.errorHandler = { error in
XCTFail("this shouldn't happen")
}
operation.completionHandler = { (finished, cancelled) in
completedExpectation.fulfill()
XCTAssertTrue(finished)
XCTAssertFalse(cancelled)
}
queue.addOperations([operation], waitUntilFinished: false)
wait(for: [startedExpectation,completedExpectation], timeout: 1, enforceOrder: true)
XCTAssertNil(operation.error)
XCTAssertFalse(operation.isCancelled)
guard let result = operation.result else {
XCTFail("result should not be nil")
return
}
XCTAssertTrue({
switch result {
case .processNewBlocks(range: CompactBlockRange(uncheckedBounds: (expectedStoreLatestHeight + 1, expectedLatestHeight))):
return true
default:
return false
}
}(), "Expected \(expectedResult) got: \(result)")
}
func testResultProcessorFinished() throws {
let expectedLatestHeight = BlockHeight(1230000)
let service = MockLightWalletService(latestBlockHeight: expectedLatestHeight)
let expectedStoreLatestHeight = BlockHeight(1230000)
let walletBirthday = BlockHeight(1210000)
let expectedResult = FigureNextBatchOperation.NextState.finishProcessing(height: expectedStoreLatestHeight)
let repository = ZcashConsoleFakeStorage(latestBlockHeight: expectedStoreLatestHeight)
let downloader = CompactBlockDownloader(service: service, storage: repository)
let config = CompactBlockProcessor.Configuration(cacheDb: try! __cacheDbURL(), dataDb: try! __dataDbURL(), downloadBatchSize: 100, retries: 5, maxBackoffInterval: 10, rewindDistance: 100, walletBirthday: walletBirthday, saplingActivation: ZcashSDK.SAPLING_ACTIVATION_HEIGHT)
var info = LightdInfo()
info.blockHeight = UInt64(expectedLatestHeight)
info.branch = "d34db33f"
info.chainName = "main"
info.buildUser = "test user"
info.consensusBranchID = "d34db4d"
info.saplingActivationHeight = UInt64(ZcashSDK.SAPLING_ACTIVATION_HEIGHT)
service.mockLightDInfo = info
let mockRust = MockRustBackend.self
mockRust.consensusBranchID = 0xd34db4d
let operation = FigureNextBatchOperation(downloader: downloader, service: service, config: config, rustBackend: mockRust)
let completedExpectation = XCTestExpectation(description: "completed expectation")
let startedExpectation = XCTestExpectation(description: "start Expectation")
operation.startedHandler = {
startedExpectation.fulfill()
}
operation.errorHandler = { error in
XCTFail("this shouldn't happen")
}
operation.completionHandler = { (finished, cancelled) in
completedExpectation.fulfill()
XCTAssertTrue(finished)
XCTAssertFalse(cancelled)
}
queue.addOperations([operation], waitUntilFinished: false)
wait(for: [startedExpectation,completedExpectation], timeout: 1, enforceOrder: true)
XCTAssertNil(operation.error)
XCTAssertFalse(operation.isCancelled)
guard let result = operation.result else {
XCTFail("result should not be nil")
return
}
XCTAssertTrue({
switch result {
case .finishProcessing(height: expectedLatestHeight):
return true
default:
return false
}
}(), "Expected \(expectedResult) got: \(result)")
}
}

View File

@ -20,17 +20,28 @@ struct MockCancellable: CancellableCall {
func cancel() {}
}
class MockLightWalletService: LightWalletService {
var mockLightDInfo: LightWalletdInfo?
var queue = DispatchQueue(label: "mock service queue")
@discardableResult func blockStream(startHeight: BlockHeight, endHeight: BlockHeight, result: @escaping (Result<GRPCResult, LightWalletServiceError>) -> Void, handler: @escaping (ZcashCompactBlock) -> Void, progress: @escaping (BlockProgressReporting) -> Void) -> CancellableCall {
return MockCancellable()
}
func getInfo() throws -> LightWalletdInfo {
throw LightWalletServiceError.generalError(message: "Not Implemented")
guard let info = mockLightDInfo else {
throw LightWalletServiceError.generalError(message: "Not Implemented")
}
return info
}
func getInfo(result: @escaping (Result<LightWalletdInfo, LightWalletServiceError>) -> Void) {
return result(.failure(LightWalletServiceError.generalError(message: "Not Implemented")))
queue.async { [weak self] in
guard let info = self?.mockLightDInfo else {
result(.failure(LightWalletServiceError.generalError(message: "Not Implemented")))
return
}
result(.success(info))
}
}
func closeConnection() {

View File

@ -168,7 +168,10 @@ class MockRustBackend: ZcashRustBackendWelding {
static func consensusBranchIdFor(height: Int32) throws -> Int32 {
-1
guard let c = consensusBranchID else {
return try rustBackend.consensusBranchIdFor(height: height)
}
return c
}
@ -189,7 +192,7 @@ class MockRustBackend: ZcashRustBackendWelding {
static var mockScanblocksSuccessRate: Float?
static var mockCreateToAddress: Int64?
static var rustBackend = ZcashRustBackend.self
static var consensusBranchID: Int32?
static func lastError() -> RustWeldingError? {
mockError ?? rustBackend.lastError()