createNewZircle darksidelightwalletd tests
This commit is contained in:
parent
74e1da5cdd
commit
c870646780
|
@ -54,6 +54,20 @@
|
|||
0DB5331224A6413D0090D722 /* LazyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB5331124A6413D0090D722 /* LazyView.swift */; };
|
||||
0DB5331424A6B8B60090D722 /* memo.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB5331324A6B8B60090D722 /* memo.pb.swift */; };
|
||||
0DC1B47B24AAA44B00AEF3D0 /* ZircleService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DC1B47A24AAA44B00AEF3D0 /* ZircleService.swift */; };
|
||||
0DC4B44A24ACB62400B3CBA2 /* darkside.grpc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DC4B43D24ACB62400B3CBA2 /* darkside.grpc.swift */; };
|
||||
0DC4B44B24ACB62400B3CBA2 /* darkside.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DC4B43E24ACB62400B3CBA2 /* darkside.pb.swift */; };
|
||||
0DC4B44D24ACB62400B3CBA2 /* MockTransactionRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DC4B44124ACB62400B3CBA2 /* MockTransactionRepository.swift */; };
|
||||
0DC4B44E24ACB62400B3CBA2 /* SampleLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DC4B44224ACB62400B3CBA2 /* SampleLogger.swift */; };
|
||||
0DC4B44F24ACB62400B3CBA2 /* FakeService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DC4B44324ACB62400B3CBA2 /* FakeService.swift */; };
|
||||
0DC4B45024ACB62400B3CBA2 /* FakeStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DC4B44424ACB62400B3CBA2 /* FakeStorage.swift */; };
|
||||
0DC4B45124ACB62400B3CBA2 /* TestDbBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DC4B44524ACB62400B3CBA2 /* TestDbBuilder.swift */; };
|
||||
0DC4B45224ACB62400B3CBA2 /* FakeChainBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DC4B44624ACB62400B3CBA2 /* FakeChainBuilder.swift */; };
|
||||
0DC4B45324ACB62400B3CBA2 /* Stubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DC4B44724ACB62400B3CBA2 /* Stubs.swift */; };
|
||||
0DC4B45424ACB62400B3CBA2 /* DarkSideWalletService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DC4B44824ACB62400B3CBA2 /* DarkSideWalletService.swift */; };
|
||||
0DC4B45524ACB62400B3CBA2 /* Tests+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DC4B44924ACB62400B3CBA2 /* Tests+Utils.swift */; };
|
||||
0DC4B45724ACF8EE00B3CBA2 /* TestCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DC4B45624ACF8EE00B3CBA2 /* TestCoordinator.swift */; };
|
||||
0DC4B45824AD281100B3CBA2 /* sapling-output.params in Resources */ = {isa = PBXBuildFile; fileRef = 0DB5330E24A623AF0090D722 /* sapling-output.params */; };
|
||||
0DC4B45924AD281400B3CBA2 /* sapling-spend.params in Resources */ = {isa = PBXBuildFile; fileRef = 0DB5330D24A623AF0090D722 /* sapling-spend.params */; };
|
||||
0DD7278924A5532300C36D27 /* AllZirclesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DD7278824A5532300C36D27 /* AllZirclesView.swift */; };
|
||||
0DD7278B24A564AF00C36D27 /* ZircleListCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DD7278A24A564AF00C36D27 /* ZircleListCard.swift */; };
|
||||
0DEE59A824A24B7300447C15 /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DEE59A724A24B7300447C15 /* WelcomeView.swift */; };
|
||||
|
@ -132,6 +146,18 @@
|
|||
0DB5331124A6413D0090D722 /* LazyView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LazyView.swift; sourceTree = "<group>"; };
|
||||
0DB5331324A6B8B60090D722 /* memo.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = memo.pb.swift; sourceTree = "<group>"; };
|
||||
0DC1B47A24AAA44B00AEF3D0 /* ZircleService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZircleService.swift; sourceTree = "<group>"; };
|
||||
0DC4B43D24ACB62400B3CBA2 /* darkside.grpc.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = darkside.grpc.swift; sourceTree = "<group>"; };
|
||||
0DC4B43E24ACB62400B3CBA2 /* darkside.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = darkside.pb.swift; sourceTree = "<group>"; };
|
||||
0DC4B44124ACB62400B3CBA2 /* MockTransactionRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockTransactionRepository.swift; sourceTree = "<group>"; };
|
||||
0DC4B44224ACB62400B3CBA2 /* SampleLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SampleLogger.swift; sourceTree = "<group>"; };
|
||||
0DC4B44324ACB62400B3CBA2 /* FakeService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FakeService.swift; sourceTree = "<group>"; };
|
||||
0DC4B44424ACB62400B3CBA2 /* FakeStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FakeStorage.swift; sourceTree = "<group>"; };
|
||||
0DC4B44524ACB62400B3CBA2 /* TestDbBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestDbBuilder.swift; sourceTree = "<group>"; };
|
||||
0DC4B44624ACB62400B3CBA2 /* FakeChainBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FakeChainBuilder.swift; sourceTree = "<group>"; };
|
||||
0DC4B44724ACB62400B3CBA2 /* Stubs.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Stubs.swift; sourceTree = "<group>"; };
|
||||
0DC4B44824ACB62400B3CBA2 /* DarkSideWalletService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DarkSideWalletService.swift; sourceTree = "<group>"; };
|
||||
0DC4B44924ACB62400B3CBA2 /* Tests+Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Tests+Utils.swift"; sourceTree = "<group>"; };
|
||||
0DC4B45624ACF8EE00B3CBA2 /* TestCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestCoordinator.swift; sourceTree = "<group>"; };
|
||||
0DD7278824A5532300C36D27 /* AllZirclesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllZirclesView.swift; sourceTree = "<group>"; };
|
||||
0DD7278A24A564AF00C36D27 /* ZircleListCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZircleListCard.swift; sourceTree = "<group>"; };
|
||||
0DEE59A724A24B7300447C15 /* WelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeView.swift; sourceTree = "<group>"; };
|
||||
|
@ -266,6 +292,8 @@
|
|||
0D1366C124991A6200F0EB54 /* ZirclesTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0DC4B43C24ACB62400B3CBA2 /* proto */,
|
||||
0DC4B44024ACB62400B3CBA2 /* utils */,
|
||||
0D1366C224991A6200F0EB54 /* ZirclesTests.swift */,
|
||||
0D1366C424991A6200F0EB54 /* Info.plist */,
|
||||
);
|
||||
|
@ -337,6 +365,32 @@
|
|||
path = ZircleService;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
0DC4B43C24ACB62400B3CBA2 /* proto */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0DC4B43D24ACB62400B3CBA2 /* darkside.grpc.swift */,
|
||||
0DC4B43E24ACB62400B3CBA2 /* darkside.pb.swift */,
|
||||
);
|
||||
path = proto;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
0DC4B44024ACB62400B3CBA2 /* utils */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0DC4B45624ACF8EE00B3CBA2 /* TestCoordinator.swift */,
|
||||
0DC4B44124ACB62400B3CBA2 /* MockTransactionRepository.swift */,
|
||||
0DC4B44224ACB62400B3CBA2 /* SampleLogger.swift */,
|
||||
0DC4B44324ACB62400B3CBA2 /* FakeService.swift */,
|
||||
0DC4B44424ACB62400B3CBA2 /* FakeStorage.swift */,
|
||||
0DC4B44524ACB62400B3CBA2 /* TestDbBuilder.swift */,
|
||||
0DC4B44624ACB62400B3CBA2 /* FakeChainBuilder.swift */,
|
||||
0DC4B44724ACB62400B3CBA2 /* Stubs.swift */,
|
||||
0DC4B44824ACB62400B3CBA2 /* DarkSideWalletService.swift */,
|
||||
0DC4B44924ACB62400B3CBA2 /* Tests+Utils.swift */,
|
||||
);
|
||||
path = utils;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E7393F4D108BAE27C875D7F0 /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -472,6 +526,8 @@
|
|||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
0DC4B45824AD281100B3CBA2 /* sapling-output.params in Resources */,
|
||||
0DC4B45924AD281400B3CBA2 /* sapling-spend.params in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -641,7 +697,19 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
0DC4B45024ACB62400B3CBA2 /* FakeStorage.swift in Sources */,
|
||||
0D1366C324991A6200F0EB54 /* ZirclesTests.swift in Sources */,
|
||||
0DC4B44F24ACB62400B3CBA2 /* FakeService.swift in Sources */,
|
||||
0DC4B44A24ACB62400B3CBA2 /* darkside.grpc.swift in Sources */,
|
||||
0DC4B44D24ACB62400B3CBA2 /* MockTransactionRepository.swift in Sources */,
|
||||
0DC4B45524ACB62400B3CBA2 /* Tests+Utils.swift in Sources */,
|
||||
0DC4B44B24ACB62400B3CBA2 /* darkside.pb.swift in Sources */,
|
||||
0DC4B45324ACB62400B3CBA2 /* Stubs.swift in Sources */,
|
||||
0DC4B45124ACB62400B3CBA2 /* TestDbBuilder.swift in Sources */,
|
||||
0DC4B45224ACB62400B3CBA2 /* FakeChainBuilder.swift in Sources */,
|
||||
0DC4B44E24ACB62400B3CBA2 /* SampleLogger.swift in Sources */,
|
||||
0DC4B45724ACF8EE00B3CBA2 /* TestCoordinator.swift in Sources */,
|
||||
0DC4B45424ACB62400B3CBA2 /* DarkSideWalletService.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
|
@ -14,7 +14,7 @@ class CombineSynchronizer {
|
|||
var initializer: Initializer {
|
||||
synchronizer.initializer
|
||||
}
|
||||
private var synchronizer: SDKSynchronizer
|
||||
var synchronizer: SDKSynchronizer
|
||||
|
||||
var status: CurrentValueSubject<Status, Never>
|
||||
var progress: CurrentValueSubject<Float,Never>
|
||||
|
|
|
@ -13,7 +13,7 @@ enum ZircleServiceError: Error {
|
|||
case generalError(message: String)
|
||||
}
|
||||
protocol ZircleService {
|
||||
func createNewZircle(name: String, goal zatoshi: Int64, frequency: ZircleFrequency, endDate: ZircleEndDate) throws -> Future<ZircleEntity, Error>
|
||||
func createNewZircle(name: String, goal zatoshi: Int64, frequency: ZircleFrequency, endDate: ZircleEndDate, spendingKey: String) throws -> Future<ZircleEntity, Error>
|
||||
func closeZircle(name: String) throws
|
||||
func contribute(zatoshi: Int64, zircle: ZircleEntity) throws
|
||||
func allOpenZircles() throws -> [ZircleEntity]?
|
||||
|
@ -70,6 +70,7 @@ struct ConcreteZircle: ZircleEntity {
|
|||
|
||||
}
|
||||
import MnemonicSwift
|
||||
|
||||
extension CombineSynchronizer: ZircleService {
|
||||
func closeZircle(name: String) throws {
|
||||
|
||||
|
@ -95,7 +96,7 @@ extension CombineSynchronizer: ZircleService {
|
|||
}
|
||||
}
|
||||
|
||||
func createNewZircle(name: String, goal zatoshi: Int64, frequency: ZircleFrequency, endDate: ZircleEndDate) -> Future<ZircleEntity, Error> {
|
||||
func createNewZircle(name: String, goal zatoshi: Int64, frequency: ZircleFrequency, endDate: ZircleEndDate, spendingKey: String) -> Future<ZircleEntity, Error> {
|
||||
|
||||
Future<ZircleEntity, Error>() { promise in
|
||||
var storage = [AnyCancellable]()
|
||||
|
@ -105,7 +106,7 @@ extension CombineSynchronizer: ZircleService {
|
|||
|
||||
// Get latest height from chain and generate a mnemonic seed for this zircle
|
||||
Publishers.Zip(self.latestHeight(),
|
||||
Mnemonic.generatePublisher(strength: 24)
|
||||
Mnemonic.generatePublisher(strength: 256)
|
||||
).sink(receiveCompletion: { error in
|
||||
switch error {
|
||||
case .failure(let e):
|
||||
|
@ -127,6 +128,7 @@ extension CombineSynchronizer: ZircleService {
|
|||
let extendedSpendingKey = extendedSpendingKeys.first,
|
||||
let extendedViewingKey = try derivationHelper.deriveExtendedFullViewingKey(extendedSpendingKey) else {
|
||||
promise(.failure(ZircleServiceError.generalError(message: "Key derivation error")))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
|
@ -162,10 +164,7 @@ extension CombineSynchronizer: ZircleService {
|
|||
|
||||
|
||||
// get supporting wallet spending keys to create zircle
|
||||
guard let mainSpendingKey = SeedManager.default.getKeys()?.first else {
|
||||
promise(.failure(ZircleServiceError.generalError(message: "error getting spending keys")))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
guard let freq = CreateZircleMessage.ContributionFrequency(rawValue: zircle.frequency) else {
|
||||
promise(.failure(ZircleServiceError.generalError(message: "could not create frequency with value \(zircle.frequency)")))
|
||||
|
@ -194,7 +193,7 @@ extension CombineSynchronizer: ZircleService {
|
|||
height: height,
|
||||
spendingKey: extendedSpendingKey)
|
||||
// fund zircle
|
||||
self.send(with: mainSpendingKey,
|
||||
self.send(with: spendingKey,
|
||||
zatoshi: 1000,
|
||||
to: zAddr,
|
||||
memo: memo,
|
||||
|
@ -226,11 +225,7 @@ extension CombineSynchronizer: ZircleService {
|
|||
|
||||
func openInvite(_ url: URL) throws {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
extension Mnemonic {
|
||||
|
@ -239,7 +234,7 @@ extension Mnemonic {
|
|||
Future<String,Error>() { promise in
|
||||
|
||||
DispatchQueue.global().async {
|
||||
guard let mnemonic = Mnemonic.generateMnemonic(strength: 24) else {
|
||||
guard let mnemonic = Mnemonic.generateMnemonic(strength: strength) else {
|
||||
promise(.failure(ZircleServiceError.generalError(message: "Error generating mnemonic")))
|
||||
return
|
||||
}
|
||||
|
|
|
@ -8,27 +8,151 @@
|
|||
|
||||
import XCTest
|
||||
@testable import Zircles
|
||||
|
||||
@testable import ZcashLightClientKit
|
||||
import Combine
|
||||
class ZirclesTests: XCTestCase {
|
||||
|
||||
|
||||
var seedPhrase = "still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread" //TODO: Parameterize this from environment?
|
||||
|
||||
let testRecipientAddress = "zs17mg40levjezevuhdp5pqrd52zere7r7vrjgdwn5sj4xsqtm20euwahv9anxmwr3y3kmwuz8k55a" //TODO: Parameterize this from environment
|
||||
|
||||
let sendAmount: Int64 = 1000
|
||||
var birthday: BlockHeight = 663150
|
||||
let defaultLatestHeight: BlockHeight = 663175
|
||||
var coordinator: TestCoordinator!
|
||||
var syncedExpectation = XCTestExpectation(description: "synced")
|
||||
var sentTransactionExpectation = XCTestExpectation(description: "sent")
|
||||
var expectedReorgHeight: BlockHeight = 665188
|
||||
var expectedRewindHeight: BlockHeight = 665188
|
||||
var reorgExpectation: XCTestExpectation = XCTestExpectation(description: "reorg")
|
||||
var cancellables = [AnyCancellable]()
|
||||
override func setUpWithError() throws {
|
||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||
SeedManager.default.nukeWallet()
|
||||
coordinator = try TestCoordinator(
|
||||
seed: seedPhrase,
|
||||
walletBirthday: birthday,
|
||||
channelProvider: ChannelProvider()
|
||||
)
|
||||
try coordinator.reset(saplingActivation: 663150)
|
||||
}
|
||||
|
||||
|
||||
override func tearDownWithError() throws {
|
||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||
NotificationCenter.default.removeObserver(self)
|
||||
try coordinator.stop()
|
||||
try? FileManager.default.removeItem(at: coordinator.databases.cacheDB)
|
||||
try? FileManager.default.removeItem(at: coordinator.databases.dataDB)
|
||||
try? FileManager.default.removeItem(at: coordinator.databases.pendingDB)
|
||||
SeedManager.default.nukeWallet()
|
||||
}
|
||||
|
||||
func testExample() throws {
|
||||
// This is an example of a functional test case.
|
||||
// Use XCTAssert and related functions to verify your tests produce the correct results.
|
||||
}
|
||||
|
||||
func testPerformanceExample() throws {
|
||||
// This is an example of a performance test case.
|
||||
self.measure {
|
||||
// Put the code you want to measure the time of here.
|
||||
|
||||
func testNewCreateZircle() throws {
|
||||
try FakeChainBuilder.buildChain(darksideWallet: self.coordinator.service)
|
||||
let receivedTxHeight: BlockHeight = 663188
|
||||
|
||||
/*
|
||||
2. applyStaged(received_Tx_height)
|
||||
*/
|
||||
try coordinator.applyStaged(blockheight: receivedTxHeight)
|
||||
|
||||
sleep(2)
|
||||
let preTxExpectation = XCTestExpectation(description: "pre receive")
|
||||
|
||||
/*
|
||||
3. sync up to received_Tx_height
|
||||
*/
|
||||
try coordinator.sync(completion: { (synchronizer) in
|
||||
preTxExpectation.fulfill()
|
||||
}, error: self.handleError)
|
||||
|
||||
wait(for: [preTxExpectation], timeout: 5)
|
||||
|
||||
let sendExpectation = XCTestExpectation(description: "sendToAddress")
|
||||
let endDate = Calendar.current.date(byAdding: .day, value: 7, to: Date())!
|
||||
|
||||
var incomingZircle: ZircleEntity? = nil
|
||||
|
||||
coordinator.combineSynchronizer
|
||||
.createNewZircle(name: "hackathon drinks",
|
||||
goal: 3000000,
|
||||
frequency: ZircleFrequency.daily,
|
||||
endDate: ZircleEndDate.onDate(date: endDate),
|
||||
spendingKey: coordinator.spendingKeys!.first!
|
||||
).receive(on: DispatchQueue.main)
|
||||
.sink { (errorCompletion) in
|
||||
switch errorCompletion {
|
||||
case .failure(let error):
|
||||
XCTFail("Test Failed - \(error)")
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
} receiveValue: { (zircle) in
|
||||
incomingZircle = zircle
|
||||
sendExpectation.fulfill()
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
wait(for: [sendExpectation], timeout: 60)
|
||||
try coordinator.stageBlockCreate(height: receivedTxHeight + 1, count: 20)
|
||||
|
||||
guard var receivedTx = try coordinator.getIncomingTransactions()?.first else {
|
||||
XCTFail("did not receive back previously sent transaction")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
let zircleTxHeight = 663190
|
||||
|
||||
receivedTx.height = UInt64(zircleTxHeight)
|
||||
|
||||
try coordinator.stageTransaction(receivedTx, at: zircleTxHeight)
|
||||
|
||||
try coordinator.applyStaged(blockheight: zircleTxHeight)
|
||||
|
||||
sleep(3)
|
||||
|
||||
let postSendExpectation = XCTestExpectation(description: "post send expectation")
|
||||
try coordinator.sync(completion: { (_) in
|
||||
postSendExpectation.fulfill()
|
||||
}, error: self.handleError)
|
||||
|
||||
wait(for: [postSendExpectation], timeout: 5)
|
||||
|
||||
guard let sentNewZircleTx = coordinator.synchronizer.sentTransactions.filter({ (sentTx) -> Bool in
|
||||
sentTx.minedHeight == zircleTxHeight
|
||||
}).first else {
|
||||
XCTFail("Could not find Create New Zircle Transaction at height \(zircleTxHeight)")
|
||||
return
|
||||
}
|
||||
|
||||
// let's try the memo thing out
|
||||
guard let memoData = sentNewZircleTx.memo else {
|
||||
XCTFail("retrieved transaction has no memo, when it should have")
|
||||
return
|
||||
}
|
||||
|
||||
guard let memoString = memoData.asZcashTransactionMemo() else {
|
||||
XCTFail("retrieved transaction has a memo that can't be converted to string")
|
||||
return
|
||||
}
|
||||
var memoMessage: CreateZircleMessage! = nil
|
||||
do {
|
||||
memoMessage = try CreateZircleMessage(jsonString: memoString)
|
||||
} catch {
|
||||
XCTFail("failed to parse create new circle message")
|
||||
return
|
||||
}
|
||||
XCTAssertEqual(memoMessage.frequency.rawValue, incomingZircle?.frequency)
|
||||
XCTAssertEqual(memoMessage.name, incomingZircle?.name)
|
||||
// XCTAssertEqual(memoMessage.goal, incomingZircle?.goal)
|
||||
|
||||
}
|
||||
|
||||
func handleError(_ error: Error?) {
|
||||
_ = try? coordinator.stop()
|
||||
guard let testError = error else {
|
||||
XCTFail("failed with nil error")
|
||||
return
|
||||
}
|
||||
XCTFail("Failed with error: \(testError)")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,217 @@
|
|||
//
|
||||
// DO NOT EDIT.
|
||||
//
|
||||
// Generated by the protocol buffer compiler.
|
||||
// Source: darkside.proto
|
||||
//
|
||||
|
||||
//
|
||||
// Copyright 2018, gRPC Authors All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
import Foundation
|
||||
import GRPC
|
||||
import NIO
|
||||
import NIOHTTP1
|
||||
import SwiftProtobuf
|
||||
@testable import ZcashLightClientKit
|
||||
|
||||
|
||||
/// Usage: instantiate DarksideStreamerClient, then call methods of this protocol to make API calls.
|
||||
internal protocol DarksideStreamerClientProtocol {
|
||||
func reset(_ request: DarksideMetaState, callOptions: CallOptions?) -> UnaryCall<DarksideMetaState, Empty>
|
||||
func stageBlocksStream(callOptions: CallOptions?) -> ClientStreamingCall<DarksideBlock, Empty>
|
||||
func stageBlocks(_ request: DarksideBlocksURL, callOptions: CallOptions?) -> UnaryCall<DarksideBlocksURL, Empty>
|
||||
func stageBlocksCreate(_ request: DarksideEmptyBlocks, callOptions: CallOptions?) -> UnaryCall<DarksideEmptyBlocks, Empty>
|
||||
func stageTransactionsStream(callOptions: CallOptions?) -> ClientStreamingCall<RawTransaction, Empty>
|
||||
func stageTransactions(_ request: DarksideTransactionsURL, callOptions: CallOptions?) -> UnaryCall<DarksideTransactionsURL, Empty>
|
||||
func applyStaged(_ request: DarksideHeight, callOptions: CallOptions?) -> UnaryCall<DarksideHeight, Empty>
|
||||
func getIncomingTransactions(_ request: Empty, callOptions: CallOptions?, handler: @escaping (RawTransaction) -> Void) -> ServerStreamingCall<Empty, RawTransaction>
|
||||
func clearIncomingTransactions(_ request: Empty, callOptions: CallOptions?) -> UnaryCall<Empty, Empty>
|
||||
}
|
||||
|
||||
internal final class DarksideStreamerClient: GRPCClient, DarksideStreamerClientProtocol {
|
||||
internal let channel: GRPCChannel
|
||||
internal var defaultCallOptions: CallOptions
|
||||
|
||||
/// Creates a client for the cash.z.wallet.sdk.rpc.DarksideStreamer service.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - channel: `GRPCChannel` to the service host.
|
||||
/// - defaultCallOptions: Options to use for each service call if the user doesn't provide them.
|
||||
internal init(channel: GRPCChannel, defaultCallOptions: CallOptions = CallOptions()) {
|
||||
self.channel = channel
|
||||
self.defaultCallOptions = defaultCallOptions
|
||||
}
|
||||
|
||||
/// Reset reverts all darksidewalletd state (active block range, latest height,
|
||||
/// staged blocks and transactions) and lightwalletd state (cache) to empty,
|
||||
/// the same as the initial state. This occurs synchronously and instantaneously;
|
||||
/// no reorg happens in lightwalletd. This is good to do before each independent
|
||||
/// test so that no state leaks from one test to another.
|
||||
/// Also sets (some of) the values returned by GetLightdInfo().
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - request: Request to send to Reset.
|
||||
/// - callOptions: Call options; `self.defaultCallOptions` is used if `nil`.
|
||||
/// - Returns: A `UnaryCall` with futures for the metadata, status and response.
|
||||
internal func reset(_ request: DarksideMetaState, callOptions: CallOptions? = nil) -> UnaryCall<DarksideMetaState, Empty> {
|
||||
return self.makeUnaryCall(path: "/cash.z.wallet.sdk.rpc.DarksideStreamer/Reset",
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions)
|
||||
}
|
||||
|
||||
/// StageBlocksStream accepts a list of blocks and saves them into the blocks
|
||||
/// staging area until ApplyStaged() is called; there is no immediate effect on
|
||||
/// the mock zcashd. Blocks are hex-encoded.
|
||||
///
|
||||
/// Callers should use the `send` method on the returned object to send messages
|
||||
/// to the server. The caller should send an `.end` after the final message has been sent.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - callOptions: Call options; `self.defaultCallOptions` is used if `nil`.
|
||||
/// - Returns: A `ClientStreamingCall` with futures for the metadata, status and response.
|
||||
internal func stageBlocksStream(callOptions: CallOptions? = nil) -> ClientStreamingCall<DarksideBlock, Empty> {
|
||||
return self.makeClientStreamingCall(path: "/cash.z.wallet.sdk.rpc.DarksideStreamer/StageBlocksStream",
|
||||
callOptions: callOptions ?? self.defaultCallOptions)
|
||||
}
|
||||
|
||||
/// StageBlocks is the same as StageBlocksStream() except the blocks are fetched
|
||||
/// from the given URL. Blocks are one per line, hex-encoded (not JSON).
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - request: Request to send to StageBlocks.
|
||||
/// - callOptions: Call options; `self.defaultCallOptions` is used if `nil`.
|
||||
/// - Returns: A `UnaryCall` with futures for the metadata, status and response.
|
||||
internal func stageBlocks(_ request: DarksideBlocksURL, callOptions: CallOptions? = nil) -> UnaryCall<DarksideBlocksURL, Empty> {
|
||||
return self.makeUnaryCall(path: "/cash.z.wallet.sdk.rpc.DarksideStreamer/StageBlocks",
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions)
|
||||
}
|
||||
|
||||
/// StageBlocksCreate is like the previous two, except it creates 'count'
|
||||
/// empty blocks at consecutive heights starting at height 'height'. The
|
||||
/// 'nonce' is part of the header, so it contributes to the block hash; this
|
||||
/// lets you create two fake blocks with the same transactions (or no
|
||||
/// transactions) and same height, with two different hashes.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - request: Request to send to StageBlocksCreate.
|
||||
/// - callOptions: Call options; `self.defaultCallOptions` is used if `nil`.
|
||||
/// - Returns: A `UnaryCall` with futures for the metadata, status and response.
|
||||
internal func stageBlocksCreate(_ request: DarksideEmptyBlocks, callOptions: CallOptions? = nil) -> UnaryCall<DarksideEmptyBlocks, Empty> {
|
||||
return self.makeUnaryCall(path: "/cash.z.wallet.sdk.rpc.DarksideStreamer/StageBlocksCreate",
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions)
|
||||
}
|
||||
|
||||
/// StageTransactions stores the given transaction-height pairs in the
|
||||
/// staging area until ApplyStaged() is called. Note that these transactions
|
||||
/// are not returned by the production GetTransaction() gRPC until they
|
||||
/// appear in a "mined" block (contained in the active blockchain presented
|
||||
/// by the mock zcashd).
|
||||
///
|
||||
/// Callers should use the `send` method on the returned object to send messages
|
||||
/// to the server. The caller should send an `.end` after the final message has been sent.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - callOptions: Call options; `self.defaultCallOptions` is used if `nil`.
|
||||
/// - Returns: A `ClientStreamingCall` with futures for the metadata, status and response.
|
||||
internal func stageTransactionsStream(callOptions: CallOptions? = nil) -> ClientStreamingCall<RawTransaction, Empty> {
|
||||
return self.makeClientStreamingCall(path: "/cash.z.wallet.sdk.rpc.DarksideStreamer/StageTransactionsStream",
|
||||
callOptions: callOptions ?? self.defaultCallOptions)
|
||||
}
|
||||
|
||||
/// Unary call to StageTransactions
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - request: Request to send to StageTransactions.
|
||||
/// - callOptions: Call options; `self.defaultCallOptions` is used if `nil`.
|
||||
/// - Returns: A `UnaryCall` with futures for the metadata, status and response.
|
||||
internal func stageTransactions(_ request: DarksideTransactionsURL, callOptions: CallOptions? = nil) -> UnaryCall<DarksideTransactionsURL, Empty> {
|
||||
return self.makeUnaryCall(path: "/cash.z.wallet.sdk.rpc.DarksideStreamer/StageTransactions",
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions)
|
||||
}
|
||||
|
||||
/// ApplyStaged iterates the list of blocks that were staged by the
|
||||
/// StageBlocks*() gRPCs, in the order they were staged, and "merges" each
|
||||
/// into the active, working blocks list that the mock zcashd is presenting
|
||||
/// to lightwalletd. The resulting working block list can't have gaps; if the
|
||||
/// working block range is 1000-1006, and the staged block range is 1003-1004,
|
||||
/// the resulting range is 1000-1004, with 1000-1002 unchanged, blocks
|
||||
/// 1003-1004 from the new range, and 1005-1006 dropped. After merging all
|
||||
/// blocks, ApplyStaged() appends staged transactions (in the order received)
|
||||
/// into each one's corresponding block. The staging area is then cleared.
|
||||
///
|
||||
/// The argument specifies the latest block height that mock zcashd reports
|
||||
/// (i.e. what's returned by GetLatestBlock). Note that ApplyStaged() can
|
||||
/// also be used to simply advance the latest block height presented by mock
|
||||
/// zcashd. That is, there doesn't need to be anything in the staging area.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - request: Request to send to ApplyStaged.
|
||||
/// - callOptions: Call options; `self.defaultCallOptions` is used if `nil`.
|
||||
/// - Returns: A `UnaryCall` with futures for the metadata, status and response.
|
||||
internal func applyStaged(_ request: DarksideHeight, callOptions: CallOptions? = nil) -> UnaryCall<DarksideHeight, Empty> {
|
||||
return self.makeUnaryCall(path: "/cash.z.wallet.sdk.rpc.DarksideStreamer/ApplyStaged",
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions)
|
||||
}
|
||||
|
||||
/// Calls to the production gRPC SendTransaction() store the transaction in
|
||||
/// a separate area (not the staging area); this method returns all transactions
|
||||
/// in this separate area, which is then cleared. The height returned
|
||||
/// with each transaction is -1 (invalid) since these transactions haven't
|
||||
/// been mined yet. The intention is that the transactions returned here can
|
||||
/// then, for example, be given to StageTransactions() to get them "mined"
|
||||
/// into a specified block on the next ApplyStaged().
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - request: Request to send to GetIncomingTransactions.
|
||||
/// - callOptions: Call options; `self.defaultCallOptions` is used if `nil`.
|
||||
/// - handler: A closure called when each response is received from the server.
|
||||
/// - Returns: A `ServerStreamingCall` with futures for the metadata and status.
|
||||
internal func getIncomingTransactions(_ request: Empty, callOptions: CallOptions? = nil, handler: @escaping (RawTransaction) -> Void) -> ServerStreamingCall<Empty, RawTransaction> {
|
||||
return self.makeServerStreamingCall(path: "/cash.z.wallet.sdk.rpc.DarksideStreamer/GetIncomingTransactions",
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions,
|
||||
handler: handler)
|
||||
}
|
||||
|
||||
/// Clear the incoming transaction pool.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - request: Request to send to ClearIncomingTransactions.
|
||||
/// - callOptions: Call options; `self.defaultCallOptions` is used if `nil`.
|
||||
/// - Returns: A `UnaryCall` with futures for the metadata, status and response.
|
||||
internal func clearIncomingTransactions(_ request: Empty, callOptions: CallOptions? = nil) -> UnaryCall<Empty, Empty> {
|
||||
return self.makeUnaryCall(path: "/cash.z.wallet.sdk.rpc.DarksideStreamer/ClearIncomingTransactions",
|
||||
request: request,
|
||||
callOptions: callOptions ?? self.defaultCallOptions)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Provides conformance to `GRPCPayload` for request and response messages
|
||||
extension DarksideMetaState: GRPCProtobufPayload {}
|
||||
//extension Empty: GRPCProtobufPayload {}
|
||||
extension DarksideBlock: GRPCProtobufPayload {}
|
||||
extension DarksideBlocksURL: GRPCProtobufPayload {}
|
||||
extension DarksideEmptyBlocks: GRPCProtobufPayload {}
|
||||
//extension RawTransaction: GRPCProtobufPayload {}
|
||||
extension DarksideTransactionsURL: GRPCProtobufPayload {}
|
||||
extension DarksideHeight: GRPCProtobufPayload {}
|
||||
|
|
@ -0,0 +1,320 @@
|
|||
// DO NOT EDIT.
|
||||
//
|
||||
// Generated by the Swift generator plugin for the protocol buffer compiler.
|
||||
// Source: darkside.proto
|
||||
//
|
||||
// For information on using the generated types, please see the documentation:
|
||||
// https://github.com/apple/swift-protobuf/
|
||||
|
||||
// Copyright (c) 2019-2020 The Zcash developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
||||
|
||||
import Foundation
|
||||
import SwiftProtobuf
|
||||
|
||||
// If the compiler emits an error on this type, it is because this file
|
||||
// was generated by a version of the `protoc` Swift plug-in that is
|
||||
// incompatible with the version of SwiftProtobuf to which you are linking.
|
||||
// Please ensure that you are building against the same version of the API
|
||||
// that was used to generate this file.
|
||||
fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
|
||||
struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {}
|
||||
typealias Version = _2
|
||||
}
|
||||
|
||||
struct DarksideMetaState {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
||||
var saplingActivation: Int32 = 0
|
||||
|
||||
var branchID: String = String()
|
||||
|
||||
var chainName: String = String()
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
init() {}
|
||||
}
|
||||
|
||||
/// A block is a hex-encoded string.
|
||||
struct DarksideBlock {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
||||
var block: String = String()
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
init() {}
|
||||
}
|
||||
|
||||
/// DarksideBlocksURL is typically something like:
|
||||
/// https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/before-reorg.txt
|
||||
struct DarksideBlocksURL {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
||||
var url: String = String()
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
init() {}
|
||||
}
|
||||
|
||||
/// DarksideTransactionsURL refers to an HTTP source that contains a list
|
||||
/// of hex-encoded transactions, one per line, that are to be associated
|
||||
/// with the given height (fake-mined into the block at that height)
|
||||
struct DarksideTransactionsURL {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
||||
var height: Int32 = 0
|
||||
|
||||
var url: String = String()
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
init() {}
|
||||
}
|
||||
|
||||
struct DarksideHeight {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
||||
var height: Int32 = 0
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
init() {}
|
||||
}
|
||||
|
||||
struct DarksideEmptyBlocks {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
||||
var height: Int32 = 0
|
||||
|
||||
var nonce: Int32 = 0
|
||||
|
||||
var count: Int32 = 0
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
init() {}
|
||||
}
|
||||
|
||||
// MARK: - Code below here is support for the SwiftProtobuf runtime.
|
||||
|
||||
fileprivate let _protobuf_package = "cash.z.wallet.sdk.rpc"
|
||||
|
||||
extension DarksideMetaState: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
static let protoMessageName: String = _protobuf_package + ".DarksideMetaState"
|
||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
1: .same(proto: "saplingActivation"),
|
||||
2: .same(proto: "branchID"),
|
||||
3: .same(proto: "chainName"),
|
||||
]
|
||||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
switch fieldNumber {
|
||||
case 1: try decoder.decodeSingularInt32Field(value: &self.saplingActivation)
|
||||
case 2: try decoder.decodeSingularStringField(value: &self.branchID)
|
||||
case 3: try decoder.decodeSingularStringField(value: &self.chainName)
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
||||
if self.saplingActivation != 0 {
|
||||
try visitor.visitSingularInt32Field(value: self.saplingActivation, fieldNumber: 1)
|
||||
}
|
||||
if !self.branchID.isEmpty {
|
||||
try visitor.visitSingularStringField(value: self.branchID, fieldNumber: 2)
|
||||
}
|
||||
if !self.chainName.isEmpty {
|
||||
try visitor.visitSingularStringField(value: self.chainName, fieldNumber: 3)
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
static func ==(lhs: DarksideMetaState, rhs: DarksideMetaState) -> Bool {
|
||||
if lhs.saplingActivation != rhs.saplingActivation {return false}
|
||||
if lhs.branchID != rhs.branchID {return false}
|
||||
if lhs.chainName != rhs.chainName {return false}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
extension DarksideBlock: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
static let protoMessageName: String = _protobuf_package + ".DarksideBlock"
|
||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
1: .same(proto: "block"),
|
||||
]
|
||||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
switch fieldNumber {
|
||||
case 1: try decoder.decodeSingularStringField(value: &self.block)
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
||||
if !self.block.isEmpty {
|
||||
try visitor.visitSingularStringField(value: self.block, fieldNumber: 1)
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
static func ==(lhs: DarksideBlock, rhs: DarksideBlock) -> Bool {
|
||||
if lhs.block != rhs.block {return false}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
extension DarksideBlocksURL: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
static let protoMessageName: String = _protobuf_package + ".DarksideBlocksURL"
|
||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
1: .same(proto: "url"),
|
||||
]
|
||||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
switch fieldNumber {
|
||||
case 1: try decoder.decodeSingularStringField(value: &self.url)
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
||||
if !self.url.isEmpty {
|
||||
try visitor.visitSingularStringField(value: self.url, fieldNumber: 1)
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
static func ==(lhs: DarksideBlocksURL, rhs: DarksideBlocksURL) -> Bool {
|
||||
if lhs.url != rhs.url {return false}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
extension DarksideTransactionsURL: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
static let protoMessageName: String = _protobuf_package + ".DarksideTransactionsURL"
|
||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
1: .same(proto: "height"),
|
||||
2: .same(proto: "url"),
|
||||
]
|
||||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
switch fieldNumber {
|
||||
case 1: try decoder.decodeSingularInt32Field(value: &self.height)
|
||||
case 2: try decoder.decodeSingularStringField(value: &self.url)
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
||||
if self.height != 0 {
|
||||
try visitor.visitSingularInt32Field(value: self.height, fieldNumber: 1)
|
||||
}
|
||||
if !self.url.isEmpty {
|
||||
try visitor.visitSingularStringField(value: self.url, fieldNumber: 2)
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
static func ==(lhs: DarksideTransactionsURL, rhs: DarksideTransactionsURL) -> Bool {
|
||||
if lhs.height != rhs.height {return false}
|
||||
if lhs.url != rhs.url {return false}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
extension DarksideHeight: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
static let protoMessageName: String = _protobuf_package + ".DarksideHeight"
|
||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
1: .same(proto: "height"),
|
||||
]
|
||||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
switch fieldNumber {
|
||||
case 1: try decoder.decodeSingularInt32Field(value: &self.height)
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
||||
if self.height != 0 {
|
||||
try visitor.visitSingularInt32Field(value: self.height, fieldNumber: 1)
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
static func ==(lhs: DarksideHeight, rhs: DarksideHeight) -> Bool {
|
||||
if lhs.height != rhs.height {return false}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
extension DarksideEmptyBlocks: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
static let protoMessageName: String = _protobuf_package + ".DarksideEmptyBlocks"
|
||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
1: .same(proto: "height"),
|
||||
2: .same(proto: "nonce"),
|
||||
3: .same(proto: "count"),
|
||||
]
|
||||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
switch fieldNumber {
|
||||
case 1: try decoder.decodeSingularInt32Field(value: &self.height)
|
||||
case 2: try decoder.decodeSingularInt32Field(value: &self.nonce)
|
||||
case 3: try decoder.decodeSingularInt32Field(value: &self.count)
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
||||
if self.height != 0 {
|
||||
try visitor.visitSingularInt32Field(value: self.height, fieldNumber: 1)
|
||||
}
|
||||
if self.nonce != 0 {
|
||||
try visitor.visitSingularInt32Field(value: self.nonce, fieldNumber: 2)
|
||||
}
|
||||
if self.count != 0 {
|
||||
try visitor.visitSingularInt32Field(value: self.count, fieldNumber: 3)
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
static func ==(lhs: DarksideEmptyBlocks, rhs: DarksideEmptyBlocks) -> Bool {
|
||||
if lhs.height != rhs.height {return false}
|
||||
if lhs.nonce != rhs.nonce {return false}
|
||||
if lhs.count != rhs.count {return false}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
// Copyright (c) 2019-2020 The Zcash developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
||||
|
||||
syntax = "proto3";
|
||||
package cash.z.wallet.sdk.rpc;
|
||||
option go_package = ".;walletrpc";
|
||||
option swift_prefix = "";
|
||||
import "service.proto";
|
||||
|
||||
message DarksideMetaState {
|
||||
int32 saplingActivation = 1;
|
||||
string branchID = 2;
|
||||
string chainName = 3;
|
||||
}
|
||||
|
||||
// A block is a hex-encoded string.
|
||||
message DarksideBlock {
|
||||
string block = 1;
|
||||
}
|
||||
|
||||
// DarksideBlocksURL is typically something like:
|
||||
// https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/before-reorg.txt
|
||||
message DarksideBlocksURL {
|
||||
string url = 1;
|
||||
}
|
||||
|
||||
// DarksideTransactionsURL refers to an HTTP source that contains a list
|
||||
// of hex-encoded transactions, one per line, that are to be associated
|
||||
// with the given height (fake-mined into the block at that height)
|
||||
message DarksideTransactionsURL {
|
||||
int32 height = 1;
|
||||
string url = 2;
|
||||
}
|
||||
|
||||
message DarksideHeight {
|
||||
int32 height = 1;
|
||||
}
|
||||
|
||||
message DarksideEmptyBlocks {
|
||||
int32 height = 1;
|
||||
int32 nonce = 2;
|
||||
int32 count = 3;
|
||||
}
|
||||
|
||||
// Darksidewalletd maintains two staging areas, blocks and transactions. The
|
||||
// Stage*() gRPCs add items to the staging area; ApplyStaged() "applies" everything
|
||||
// in the staging area to the working (operational) state that the mock zcashd
|
||||
// serves; transactions are placed into their corresponding blocks (by height).
|
||||
service DarksideStreamer {
|
||||
// Reset reverts all darksidewalletd state (active block range, latest height,
|
||||
// staged blocks and transactions) and lightwalletd state (cache) to empty,
|
||||
// the same as the initial state. This occurs synchronously and instantaneously;
|
||||
// no reorg happens in lightwalletd. This is good to do before each independent
|
||||
// test so that no state leaks from one test to another.
|
||||
// Also sets (some of) the values returned by GetLightdInfo().
|
||||
rpc Reset(DarksideMetaState) returns (Empty) {}
|
||||
|
||||
// StageBlocksStream accepts a list of blocks and saves them into the blocks
|
||||
// staging area until ApplyStaged() is called; there is no immediate effect on
|
||||
// the mock zcashd. Blocks are hex-encoded.
|
||||
rpc StageBlocksStream(stream DarksideBlock) returns (Empty) {}
|
||||
|
||||
// StageBlocks is the same as StageBlocksStream() except the blocks are fetched
|
||||
// from the given URL. Blocks are one per line, hex-encoded (not JSON).
|
||||
rpc StageBlocks(DarksideBlocksURL) returns (Empty) {}
|
||||
|
||||
// StageBlocksCreate is like the previous two, except it creates 'count'
|
||||
// empty blocks at consecutive heights starting at height 'height'. The
|
||||
// 'nonce' is part of the header, so it contributes to the block hash; this
|
||||
// lets you create two fake blocks with the same transactions (or no
|
||||
// transactions) and same height, with two different hashes.
|
||||
rpc StageBlocksCreate(DarksideEmptyBlocks) returns (Empty) {}
|
||||
|
||||
// StageTransactions stores the given transaction-height pairs in the
|
||||
// staging area until ApplyStaged() is called. Note that these transactions
|
||||
// are not returned by the production GetTransaction() gRPC until they
|
||||
// appear in a "mined" block (contained in the active blockchain presented
|
||||
// by the mock zcashd).
|
||||
rpc StageTransactionsStream(stream RawTransaction) returns (Empty) {}
|
||||
|
||||
rpc StageTransactions(DarksideTransactionsURL) returns (Empty) {}
|
||||
|
||||
|
||||
// ApplyStaged iterates the list of blocks that were staged by the
|
||||
// StageBlocks*() gRPCs, in the order they were staged, and "merges" each
|
||||
// into the active, working blocks list that the mock zcashd is presenting
|
||||
// to lightwalletd. The resulting working block list can't have gaps; if the
|
||||
// working block range is 1000-1006, and the staged block range is 1003-1004,
|
||||
// the resulting range is 1000-1004, with 1000-1002 unchanged, blocks
|
||||
// 1003-1004 from the new range, and 1005-1006 dropped. After merging all
|
||||
// blocks, ApplyStaged() appends staged transactions (in the order received)
|
||||
// into each one's corresponding block. The staging area is then cleared.
|
||||
//
|
||||
// The argument specifies the latest block height that mock zcashd reports
|
||||
// (i.e. what's returned by GetLatestBlock). Note that ApplyStaged() can
|
||||
// also be used to simply advance the latest block height presented by mock
|
||||
// zcashd. That is, there doesn't need to be anything in the staging area.
|
||||
rpc ApplyStaged(DarksideHeight) returns (Empty) {}
|
||||
|
||||
// Calls to the production gRPC SendTransaction() store the transaction in
|
||||
// a separate area (not the staging area); this method returns all transactions
|
||||
// in this separate area, which is then cleared. The height returned
|
||||
// with each transaction is -1 (invalid) since these transactions haven't
|
||||
// been mined yet. The intention is that the transactions returned here can
|
||||
// then, for example, be given to StageTransactions() to get them "mined"
|
||||
// into a specified block on the next ApplyStaged().
|
||||
rpc GetIncomingTransactions(Empty) returns (stream RawTransaction) {}
|
||||
|
||||
// Clear the incoming transaction pool.
|
||||
rpc ClearIncomingTransactions(Empty) returns (Empty) {}
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
//
|
||||
// DarkSideWalletService.swift
|
||||
// ZcashLightClientKit-Unit-Tests
|
||||
//
|
||||
// Created by Francisco Gindre on 3/23/20.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
@testable import ZcashLightClientKit
|
||||
import GRPC
|
||||
|
||||
enum DarksideDataset: String {
|
||||
case afterLargeReorg = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/after-large-large.txt"
|
||||
case afterSmallReorg = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/after-small-reorg.txt"
|
||||
case beforeReOrg = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/before-reorg.txt"
|
||||
|
||||
/**
|
||||
see
|
||||
https://github.com/zcash-hackworks/darksidewalletd-test-data/tree/master/tx-index-reorg
|
||||
*/
|
||||
case txIndexChangeBefore = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/tx-index-reorg/before-reorg.txt"
|
||||
|
||||
case txIndexChangeAfter = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/tx-index-reorg/after-reorg.txt"
|
||||
|
||||
/**
|
||||
See https://github.com/zcash-hackworks/darksidewalletd-test-data/tree/master/tx-height-reorg
|
||||
*/
|
||||
case txHeightReOrgBefore = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/tx-height-reorg/before-reorg.txt"
|
||||
|
||||
case txHeightReOrgAfter = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/tx-height-reorg/after-reorg.txt"
|
||||
|
||||
/*
|
||||
see: https://github.com/zcash-hackworks/darksidewalletd-test-data/tree/master/tx-remove-reorg
|
||||
*/
|
||||
case txReOrgRemovesInboundTxBefore = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/tx-remove-reorg/before-reorg.txt"
|
||||
|
||||
case txReOrgRemovesInboundTxAfter = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/tx-remove-reorg/after-reorg.txt"
|
||||
}
|
||||
|
||||
class DarksideWalletService: LightWalletService {
|
||||
|
||||
func fetchTransaction(txId: Data) throws -> TransactionEntity {
|
||||
try service.fetchTransaction(txId: txId)
|
||||
}
|
||||
|
||||
func fetchTransaction(txId: Data, result: @escaping (Result<TransactionEntity, LightWalletServiceError>) -> Void) {
|
||||
service.fetchTransaction(txId: txId, result: result)
|
||||
}
|
||||
|
||||
var channel: Channel
|
||||
init(channelProvider: ChannelProvider) {
|
||||
self.channel = ChannelProvider().channel()
|
||||
self.service = LightWalletGRPCService(channel: channel)
|
||||
self.darksideService = DarksideStreamerClient(channel: channel)
|
||||
}
|
||||
|
||||
convenience init() {
|
||||
self.init(channelProvider: ChannelProvider())
|
||||
}
|
||||
var service: LightWalletGRPCService
|
||||
var darksideService: DarksideStreamerClient
|
||||
|
||||
func latestBlockHeight(result: @escaping (Result<BlockHeight, LightWalletServiceError>) -> Void) {
|
||||
service.latestBlockHeight(result: result)
|
||||
}
|
||||
|
||||
func latestBlockHeight() throws -> BlockHeight {
|
||||
try service.latestBlockHeight()
|
||||
}
|
||||
|
||||
func blockRange(_ range: CompactBlockRange, result: @escaping (Result<[ZcashCompactBlock], LightWalletServiceError>) -> Void) {
|
||||
service.blockRange(range, result: result)
|
||||
}
|
||||
|
||||
func blockRange(_ range: CompactBlockRange) throws -> [ZcashCompactBlock] {
|
||||
try service.blockRange(range)
|
||||
}
|
||||
|
||||
/**
|
||||
Darskside lightwalletd should do a fake submission, by sending over the tx, retrieving it and including it in a new block
|
||||
*/
|
||||
func submit(spendTransaction: Data, result: @escaping (Result<LightWalletServiceResponse, LightWalletServiceError>) -> Void) {
|
||||
service.submit(spendTransaction: spendTransaction, result: result)
|
||||
}
|
||||
|
||||
func submit(spendTransaction: Data) throws -> LightWalletServiceResponse {
|
||||
try service.submit(spendTransaction: spendTransaction)
|
||||
}
|
||||
|
||||
func useDataset(_ datasetUrl: String) throws {
|
||||
try useDataset(from: datasetUrl)
|
||||
}
|
||||
|
||||
func useDataset(from urlString: String) throws {
|
||||
var blocksUrl = DarksideBlocksURL()
|
||||
blocksUrl.url = urlString
|
||||
_ = try darksideService.stageBlocks(blocksUrl, callOptions: nil).response.wait()
|
||||
}
|
||||
|
||||
func applyStaged(nextLatestHeight: BlockHeight) throws {
|
||||
var darksideHeight = DarksideHeight()
|
||||
darksideHeight.height = Int32(nextLatestHeight)
|
||||
_ = try darksideService.applyStaged(darksideHeight).response.wait()
|
||||
}
|
||||
|
||||
func clearIncomingTransactions() throws {
|
||||
_ = try darksideService.clearIncomingTransactions(Empty()).response.wait()
|
||||
}
|
||||
|
||||
func getIncomingTransactions() throws -> [RawTransaction]? {
|
||||
var txs = [RawTransaction]()
|
||||
let response = try darksideService.getIncomingTransactions(Empty(), handler: { txs.append($0) }).status.wait()
|
||||
switch response.code {
|
||||
case .ok:
|
||||
return txs.count > 0 ? txs : nil
|
||||
default:
|
||||
throw response
|
||||
}
|
||||
}
|
||||
|
||||
func reset(saplingActivation: BlockHeight) throws {
|
||||
var metaState = DarksideMetaState()
|
||||
metaState.saplingActivation = Int32(saplingActivation)
|
||||
metaState.branchID = "d3adb33f"
|
||||
metaState.chainName = "test"
|
||||
// TODO: complete meta state correctly
|
||||
_ = try darksideService.reset(metaState).response.wait()
|
||||
}
|
||||
|
||||
func stageBlocksCreate(from height: BlockHeight, count: Int = 1, nonce: Int = 0) throws {
|
||||
var emptyBlocks = DarksideEmptyBlocks()
|
||||
emptyBlocks.count = Int32(count)
|
||||
emptyBlocks.height = Int32(height)
|
||||
emptyBlocks.nonce = Int32(nonce)
|
||||
_ = try darksideService.stageBlocksCreate(emptyBlocks).response.wait()
|
||||
}
|
||||
|
||||
func stageTransaction(_ rawTransaction: RawTransaction, at height: BlockHeight) throws {
|
||||
var tx = rawTransaction
|
||||
tx.height = UInt64(height)
|
||||
_ = try darksideService.stageTransactionsStream().sendMessage(tx).wait()
|
||||
}
|
||||
|
||||
func stageTransaction(from url: String, at height: BlockHeight) throws {
|
||||
var txUrl = DarksideTransactionsURL()
|
||||
txUrl.height = Int32(height)
|
||||
txUrl.url = url
|
||||
_ = try darksideService.stageTransactions(txUrl, callOptions: nil).response.wait()
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
//
|
||||
// FakeChainBuilder.swift
|
||||
// ZcashLightClientKit-Unit-Tests
|
||||
//
|
||||
// Created by Francisco Gindre on 5/21/20.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
@testable import ZcashLightClientKit
|
||||
|
||||
enum FakeChainBuilderError: Error {
|
||||
case fakeHexDataConversionFailed
|
||||
}
|
||||
class FakeChainBuilder {
|
||||
static let someOtherTxUrl = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/t-shielded-spend.txt"
|
||||
static let txMainnetBlockUrl = "https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/basic-reorg/663150.txt"
|
||||
static func buildChain(darksideWallet: DarksideWalletService) throws {
|
||||
try darksideWallet.reset(saplingActivation: 663150)
|
||||
try darksideWallet.useDataset(from: txMainnetBlockUrl)
|
||||
|
||||
try darksideWallet.stageBlocksCreate(from: 663151, count: 100)
|
||||
|
||||
try darksideWallet.stageTransaction(from: txUrls[663174]!, at: 663174)
|
||||
|
||||
try darksideWallet.stageTransaction(from: txUrls[663188]!, at: 663188)
|
||||
|
||||
}
|
||||
|
||||
static func buildTxUrl(for id: String) -> String {
|
||||
"https://raw.githubusercontent.com/zcash-hackworks/darksidewalletd-test-data/master/transactions/recv/\(id).txt"
|
||||
}
|
||||
|
||||
static var txUrls = [
|
||||
663174 : buildTxUrl(for: "8f064d23c66dc36e32445e5f3b50e0f32ac3ddb78cff21fb521eb6c19c07c99a"),
|
||||
663188 : buildTxUrl(for: "15a677b6770c5505fb47439361d3d3a7c21238ee1a6874fdedad18ae96850590"),
|
||||
663202 : buildTxUrl(for: "d2e7be14bbb308f9d4d68de424d622cbf774226d01cd63cc6f155fafd5cd212c"),
|
||||
663218 : buildTxUrl(for: "e6566be3a4f9a80035dab8e1d97e40832a639e3ea938fb7972ea2f8482ff51ce"),
|
||||
663229 : buildTxUrl(for: "0821a89be7f2fc1311792c3fa1dd2171a8cdfb2effd98590cbd5ebcdcfcf491f"),
|
||||
663849 : buildTxUrl(for: "c9e35e6ff444b071d63bf9bab6480409d6361760445c8a28d24179adb35c2495"),
|
||||
663891 : buildTxUrl(for: "72a29d7db511025da969418880b749f7fc0fc910cdb06f52193b5fa5c0401d9d"),
|
||||
663922 : buildTxUrl(for: "ff6ea36765dc29793775c7aa71de19fca039c5b5b873a0497866e9c4bc48af01"),
|
||||
663938 : buildTxUrl(for: "34e507cab780546f980176f3ff2695cd404917508c7e5ee18cc1d2ff3858cb08"),
|
||||
663942 : buildTxUrl(for: "6edf869063eccff3345676b0fed9f1aa6988fb2524e3d9ca7420a13cfadcd76c"),
|
||||
663947 : buildTxUrl(for: "de97394ae220c28a33ba78b944e82dabec8cb404a4407650b134b3d5950358c0"),
|
||||
663949 : buildTxUrl(for: "4eaa902279f8380914baf5bcc470d8b7c11d84fda809f67f517a7cb48912b87b"),
|
||||
663953 : buildTxUrl(for: "e9527891b5d43d1ac72f2c0a3ac18a33dc5a0529aec04fa600616ed35f8123f8"),
|
||||
663956 : buildTxUrl(for: "73c5edf8ffba774d99155121ccf07e67fbcf14284458f7e732751fea60d3bcbc"),
|
||||
663974 : buildTxUrl(for: "4dcc95dd0a2f1f51bd64bb9f729b423c6de1690664a1b6614c75925e781662f7"),
|
||||
664003 : buildTxUrl(for: "d2e859e8ef8ab27355c7a6caf643065d2d7a720e334c4a84943f6d1ae3919b5d"),
|
||||
664012 : buildTxUrl(for: "547784f746eef2f164bbb1a56882723dde744157a21e4fdfeadee763f73fee84"),
|
||||
664022 : buildTxUrl(for: "981638bb7ac08e31ee6db5c70d98ad6b137a448716b19245f9454b450c07c911"),
|
||||
664037 : buildTxUrl(for: "36505ab3c78c62981c8111d143cd57dcfe6cafcb2c3cdc258b023ae5210d53f1"),
|
||||
664038 : buildTxUrl(for: "0ffc55af750bb10a9e6a7e425138cc5acb5f7ddca68bf9d0c4606437bd692622"),
|
||||
678828 : buildTxUrl(for: "cfd3bce9fdeeae12b99fdb977a997177e183c2312871f0454bdf61640cc03d93"),
|
||||
678836 : buildTxUrl(for: "5af3bc9818e5fabcc691f319d7354cc4194f17727f6303d59a94c3e5f0daf560"),
|
||||
682588 : buildTxUrl(for: "b1f566dec94048ff81306884b6ed92eb73cdb768b738d9c8cbd94babc1f0a9c9"),
|
||||
683689 : buildTxUrl(for: "3b568a1547832ac28bfcaf4c269f85fd68083735790f7949aa3a548ab53acf65"),
|
||||
683791 : buildTxUrl(for: "9e2eb538207ab47356a3723fd0e6f44b9349ea944d9c2d7be0d4e3a6a02c2c29"),
|
||||
683795 : buildTxUrl(for: "15d2f32494271f0a60f3928e4fc79c2cea337e06fbbbe7f6fb4a0d36002a0d42"),
|
||||
683809 : buildTxUrl(for: "76be7c244c37e1710bbb9f162baab265eebc8a379ad1843435ba5e7a2c21a600"),
|
||||
684374 : buildTxUrl(for: "3640a35c02cf4d9e0fa178380173b193873d8a0ef4bad57dd43e7d95db450c89"),
|
||||
685126 : buildTxUrl(for: "86f3457bdb8793a413c009a8a7e128b5a82723f41ebe557327bbe555fd47fbf3"),
|
||||
687746 : buildTxUrl(for: "edb32a55d5fa18fc5c6bf09f5f1de198b219b6780ca71bbc4fd321b655bbfe42"),
|
||||
687900 : buildTxUrl(for: "855af341c14b94fec67e5eb56bb801a59551df33e9d955982672f5f62e76f72e"),
|
||||
688059 : buildTxUrl(for: "57c226f77ad01ecf833515612e7cba7abe64500fa891144c2c89c59af8c36c22"),
|
||||
691540 : buildTxUrl(for: "9a74cd7f170f6c8cef04f3327fdcf63ec69dd1263f80c9bf0b3002c871950ddd"),
|
||||
691593 : buildTxUrl(for: "6b64134034ec282092501f85bf8955006894dbcac402fa5e6c85ee867334cd3d"),
|
||||
691632 : buildTxUrl(for: "75f2cdd2ff6a94535326abb5d9e663d53cbfa5f31ebb24b4d7e420e9440d41a2"),
|
||||
692981 : buildTxUrl(for: "f98f2c75785f110203930c7fd4115019ec70af6470db1be052985b469906fe98"),
|
||||
692984 : buildTxUrl(for: "67138ad7e5e97216124c2bbcda8edb7687c2cfbf5d644df2af2a86344437a661"),
|
||||
692992 : buildTxUrl(for: "6cf507ab4d3255fa51679c0256a1be1d668786bd3f558000f9e90ec442514212"),
|
||||
693248 : buildTxUrl(for: "d1278d74424807b830256ccbd4d7624dc9e68a50760f870a55c8e99715072ef1"),
|
||||
693268 : buildTxUrl(for: "e56c84718de5dee049b31c89832f4bf1694268e2664a04df182a8797cb00b52e"),
|
||||
693325 : buildTxUrl(for: "5635f48dc99adfebb0be105231b9383bd2d0df64e43a780d11620390640b8d3d"),
|
||||
693398 : buildTxUrl(for: "26c41d5dbbaaa3934b37109645b0aece9600248c5f51404d1f4ea7b711ac3312"),
|
||||
693460 : buildTxUrl(for: "8d381a3d993c8d424db0907bab3fef6000bc8de9efe7186846d44dd6d6a014b1"),
|
||||
693475 : buildTxUrl(for: "900f2a406c1546126e1dba0e4e6ca0e092ebe697a2f7b0abee4e9771e1038f0b"),
|
||||
693718 : buildTxUrl(for: "7690c8ec740c1be3c50e2aedae8bf907ac81141ae8b6a134c1811706c73f49a6"),
|
||||
693736 : buildTxUrl(for: "34a4d630f120e4c1e7d2b9844c69fd4d3be71532ade1aaf7147566f05162c316"),
|
||||
696005 : buildTxUrl(for: "076d30ca62082dda9a760e0d004393cd96830056c6dca643fccdbe500053e355"),
|
||||
696012 : buildTxUrl(for: "e2da49325057b2232e85b0228955234f4a3538df2ebf4cd121589bac9771f6f2"),
|
||||
696040 : buildTxUrl(for: "4f6ef63bd3be8338c902901daf77ab5aa23dd97c160ee91b00950accf7f0b194"),
|
||||
698361 : buildTxUrl(for: "d275a9e96e6c68dfb8fe6ec3fd39737ce5fa880f86552b3ed993048373d6e8ad"),
|
||||
710823 : buildTxUrl(for: "bac04ad7734628e70a57408c65403ec845bce575197e7984435976e1ac64ae4f"),
|
||||
710896 : buildTxUrl(for: "56c63ef496f633418f0576cc34a0730c74023d78003b95aff731e0448c8b9203"),
|
||||
711847 : buildTxUrl(for: "82439eade5d1deba7606f3db53bf33588677b1bd9765a5eb5f4d3f6980ecb3d4"),
|
||||
727486 : buildTxUrl(for: "b5877c7f7dd3856bae679f7ccb37ddf3fcd2fafe72a081878ee9069fc25934cd"),
|
||||
728159 : buildTxUrl(for: "5d6a0c4879a244d2c0a6e2d26c4d0d26dee5a5c1f3f13f42436253272d4b8a03"),
|
||||
736102 : buildTxUrl(for: "be3a3a3fe10b9a1976410e5aaf425b24695dcdd04df926a23d9f3f8ed43178c6"),
|
||||
736254 : buildTxUrl(for: "acc0685aee04f7b7c6a12c969c1646038ea4a3b940d00b28d1eaf7643602d49e"),
|
||||
736262 : buildTxUrl(for: "fed00ff5cb6ee057d00ec70f1f5f1b189d591903c1e1cbde654ad39c8477808c"),
|
||||
736301 : buildTxUrl(for: "f3ef9f3adedf2b66e438c9d7d878ed72886b62b70e68547bb47d5b6033519dcd"),
|
||||
736574 : buildTxUrl(for: "34574442629a2378eccd216385d8bc99859e214e79265941319599130de2c69a"),
|
||||
739582 : buildTxUrl(for: "71935e29127a7de0b96081f4c8a42a9c11584d83adedfaab414362a6f3d965cf"),
|
||||
741148 : buildTxUrl(for: "5eff7f15b39b9ab463767b768e23f90b4a23239ed873fdfbd4afa286027f7b57"),
|
||||
741154 : buildTxUrl(for: "b05c3df882ccff4f58acc1e3dbe2520213159d584bac01ff0199c37c25451430"),
|
||||
741156 : buildTxUrl(for: "1a3bb3d4fece0fcde1a47ef8271511cefcdb67f2698afc2c63297fbeab2003d8"),
|
||||
741158 : buildTxUrl(for: "a979dc83f55d9114dcab2eb5694bbf4fbb84602ceb27af6e287d6af8775d92c7"),
|
||||
741162 : buildTxUrl(for: "23278a3c1bf03f20f67299ed0b8dc4d577909d2344f1f02971c8890c6341d79d"),
|
||||
741170 : buildTxUrl(for: "db4101f3cccb1671dc1557670fa8b4e64c958008778b8ab1779a4a2969fe1153"),
|
||||
741171 : buildTxUrl(for: "74a94aceedb3a22eedb0b5d450487340b3783e1d22ef47af2359c45d0804d9ff"),
|
||||
741172 : buildTxUrl(for: "2899ccaea26e4c873a09965e0c268c96a86b1931d896b8622f36422d32c234c2"),
|
||||
741174 : buildTxUrl(for: "819009ec1d0cfb50d30c944a41bde545ee631663af39f8a17c31255ada12de13"),
|
||||
775018 : buildTxUrl(for: "85b3b64903b1873f5b7578eb2f167752b6a66ba64bb5c4cb8a4d75072219678b"),
|
||||
775021 : buildTxUrl(for: "6d69d23c8db7736efdd38090c3cd032f8e68431272964157c52a924315e1a3f5"),
|
||||
775267 : buildTxUrl(for: "daf24871749c8360028a19e4d82ddb0d573d7c765a894d601aa241f1e040ac5f"),
|
||||
776019 : buildTxUrl(for: "f64378feb08c30b28a90f31e8cd84a932ed064108fb17a3e0aee1585ff994138"),
|
||||
776158 : buildTxUrl(for: "9339a0a231f88b3067f3378c7ae70170fdf4246e0e70f442552a6e3961391b56"),
|
||||
776233 : buildTxUrl(for: "c9c33e44468c1fa0ee5f9d411b43748f8882915640b3b13c6e48c56e9cdde798"),
|
||||
776240 : buildTxUrl(for: "0e1c70fc67d3b9ae29a98996d4363b512d51d7b8422a6fa58f5803bebb247e7a"),
|
||||
820691 : buildTxUrl(for: "1948bc40226e53d2652f593ebe4f34c5d81550eeb16fe2ed797b7ef3c1083899"),
|
||||
822410 : buildTxUrl(for: "f3f8684be8d77367d099a38f30e3652410cdebe35c006d0599d86d8ec640867f"),
|
||||
828933 : buildTxUrl(for: "1fd394257d1c10c8a70fb760cf73f6d0e96e61edcf1ffca6da12d733a59221a4")
|
||||
]
|
||||
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
//
|
||||
// FakeService.swift
|
||||
// ZcashLightClientKitTests
|
||||
//
|
||||
// Created by Francisco Gindre on 10/23/19.
|
||||
// Copyright © 2019 Electric Coin Company. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftProtobuf
|
||||
@testable import ZcashLightClientKit
|
||||
|
||||
struct LightWalletServiceMockResponse: LightWalletServiceResponse {
|
||||
var errorCode: Int32
|
||||
var errorMessage: String
|
||||
var unknownFields: UnknownStorage
|
||||
|
||||
}
|
||||
|
||||
class MockLightWalletService: LightWalletService {
|
||||
|
||||
|
||||
private var service = LightWalletGRPCService(channel: ChannelProvider().channel())
|
||||
var latestHeight: BlockHeight
|
||||
|
||||
init(latestBlockHeight: BlockHeight) {
|
||||
self.latestHeight = latestBlockHeight
|
||||
}
|
||||
func latestBlockHeight(result: @escaping (Result<BlockHeight, LightWalletServiceError>) -> Void) {
|
||||
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
|
||||
result(.success(self.latestHeight))
|
||||
}
|
||||
}
|
||||
|
||||
func latestBlockHeight() throws -> BlockHeight {
|
||||
return self.latestHeight
|
||||
}
|
||||
|
||||
func blockRange(_ range: CompactBlockRange, result: @escaping (Result<[ZcashCompactBlock], LightWalletServiceError>) -> Void) {
|
||||
self.service.blockRange(range, result: result)
|
||||
}
|
||||
|
||||
func blockRange(_ range: CompactBlockRange) throws -> [ZcashCompactBlock] {
|
||||
try self.service.blockRange(range)
|
||||
}
|
||||
|
||||
func submit(spendTransaction: Data, result: @escaping (Result<LightWalletServiceResponse, LightWalletServiceError>) -> Void) {
|
||||
DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + 1) {
|
||||
result(.success(LightWalletServiceMockResponse(errorCode: 0, errorMessage: "", unknownFields: UnknownStorage())))
|
||||
}
|
||||
}
|
||||
|
||||
func submit(spendTransaction: Data) throws -> LightWalletServiceResponse {
|
||||
return LightWalletServiceMockResponse(errorCode: 0, errorMessage: "", unknownFields: UnknownStorage())
|
||||
}
|
||||
|
||||
func fetchTransaction(txId: Data) throws -> TransactionEntity {
|
||||
Transaction(id: 1, transactionId: Data(), created: "Today", transactionIndex: 1, expiryHeight: -1, minedHeight: -1, raw: nil)
|
||||
}
|
||||
|
||||
func fetchTransaction(txId: Data, result: @escaping (Result<TransactionEntity, LightWalletServiceError>) -> Void) {
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
//
|
||||
// FakeStorage.swift
|
||||
// ZcashLightClientKit
|
||||
//
|
||||
// Created by Francisco Gindre on 12/09/2019.
|
||||
// Copyright © 2019 Electric Coin Company. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
@testable import ZcashLightClientKit
|
||||
|
||||
class ZcashConsoleFakeStorage: CompactBlockRepository {
|
||||
func latestHeight() throws -> Int {
|
||||
return self.latestBlockHeight
|
||||
}
|
||||
|
||||
func write(blocks: [ZcashCompactBlock]) throws {
|
||||
fakeSave(blocks: blocks)
|
||||
}
|
||||
|
||||
func rewind(to height: BlockHeight) throws {
|
||||
fakeRewind(to: height)
|
||||
}
|
||||
|
||||
var latestBlockHeight: BlockHeight = 0
|
||||
var delay = DispatchTimeInterval.milliseconds(300)
|
||||
|
||||
init(latestBlockHeight: BlockHeight = 0) {
|
||||
self.latestBlockHeight = latestBlockHeight
|
||||
}
|
||||
|
||||
func latestHeight(result: @escaping (Result<BlockHeight, Error>) -> Void) {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
|
||||
result(.success(self.latestBlockHeight))
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func fakeSave(blocks: [ZcashCompactBlock]) {
|
||||
blocks.forEach {
|
||||
LoggerProxy.debug("saving block \($0)")
|
||||
self.latestBlockHeight = $0.height
|
||||
}
|
||||
}
|
||||
|
||||
func write(blocks: [ZcashCompactBlock], completion: ((Error?) -> Void)?) {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
|
||||
self.fakeSave(blocks: blocks)
|
||||
completion?(nil)
|
||||
}
|
||||
}
|
||||
|
||||
func rewind(to height: BlockHeight, completion: ((Error?) -> Void)?) {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
|
||||
self.fakeRewind(to: height)
|
||||
completion?(nil)
|
||||
}
|
||||
}
|
||||
|
||||
private func fakeRewind(to height: BlockHeight) {
|
||||
LoggerProxy.debug("rewind to \(height)")
|
||||
self.latestBlockHeight = min(self.latestBlockHeight, height)
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,168 @@
|
|||
//
|
||||
// MockTransactionRepository.swift
|
||||
// ZcashLightClientKit-Unit-Tests
|
||||
//
|
||||
// Created by Francisco Gindre on 12/6/19.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@testable import ZcashLightClientKit
|
||||
|
||||
class MockTransactionRepository: TransactionRepository {
|
||||
func findTransactions(in range: BlockRange, limit: Int) throws -> [TransactionEntity]? {
|
||||
nil
|
||||
}
|
||||
|
||||
// func findTransactions(in range: BlockRange, limit: Int) throws -> [TransactionEntity]? {
|
||||
// nil
|
||||
// }
|
||||
|
||||
|
||||
var unminedCount: Int
|
||||
var receivedCount: Int
|
||||
var sentCount: Int
|
||||
|
||||
var transactions: [ConfirmedTransactionEntity] = []
|
||||
var reference: [Kind] = []
|
||||
var sentTransactions: [ConfirmedTransaction] = []
|
||||
var receivedTransactions: [ConfirmedTransaction] = []
|
||||
|
||||
var allCount: Int {
|
||||
receivedCount + sentCount
|
||||
}
|
||||
|
||||
init(unminedCount: Int, receivedCount: Int, sentCount: Int) {
|
||||
self.unminedCount = unminedCount
|
||||
self.receivedCount = receivedCount
|
||||
self.sentCount = sentCount
|
||||
}
|
||||
|
||||
func generate() {
|
||||
|
||||
var txArray = [ConfirmedTransactionEntity]()
|
||||
reference = referenceArray()
|
||||
for i in 0 ..< reference.count {
|
||||
txArray.append(mockTx(index: i, kind: reference[i]))
|
||||
}
|
||||
transactions = txArray
|
||||
}
|
||||
|
||||
func countAll() throws -> Int {
|
||||
allCount
|
||||
}
|
||||
|
||||
func countUnmined() throws -> Int {
|
||||
unminedCount
|
||||
}
|
||||
|
||||
func findBy(id: Int) throws -> TransactionEntity? {
|
||||
transactions.first(where: {$0.id == id})?.transactionEntity
|
||||
}
|
||||
|
||||
func findBy(rawId: Data) throws -> TransactionEntity? {
|
||||
transactions.first(where: {$0.rawTransactionId == rawId})?.transactionEntity
|
||||
}
|
||||
|
||||
func findAllSentTransactions(offset: Int, limit: Int) throws -> [ConfirmedTransactionEntity]? {
|
||||
guard let indices = reference.indices(where: { $0 == .sent }) else { return nil }
|
||||
|
||||
let sentTxs = indices.map { (idx) -> ConfirmedTransactionEntity in
|
||||
transactions[idx]
|
||||
}
|
||||
return slice(txs: sentTxs, offset: offset, limit: limit)
|
||||
}
|
||||
|
||||
|
||||
func findAllReceivedTransactions(offset: Int, limit: Int) throws -> [ConfirmedTransactionEntity]? {
|
||||
guard let indices = reference.indices(where: { $0 == .received }) else { return nil }
|
||||
|
||||
let receivedTxs = indices.map { (idx) -> ConfirmedTransactionEntity in
|
||||
transactions[idx]
|
||||
}
|
||||
return slice(txs: receivedTxs, offset: offset, limit: limit)
|
||||
}
|
||||
|
||||
func findAll(offset: Int, limit: Int) throws -> [ConfirmedTransactionEntity]? {
|
||||
transactions
|
||||
}
|
||||
|
||||
func lastScannedHeight() throws -> BlockHeight {
|
||||
return 700000
|
||||
}
|
||||
|
||||
func isInitialized() throws -> Bool {
|
||||
true
|
||||
}
|
||||
|
||||
func findEncodedTransactionBy(txId: Int) -> EncodedTransaction? {
|
||||
nil
|
||||
}
|
||||
|
||||
enum Kind {
|
||||
case sent
|
||||
case received
|
||||
}
|
||||
|
||||
func referenceArray() -> [Kind] {
|
||||
var template = [Kind]()
|
||||
|
||||
for _ in 0 ..< sentCount {
|
||||
template.append(.sent)
|
||||
}
|
||||
for _ in 0 ..< receivedCount {
|
||||
template.append(.received)
|
||||
}
|
||||
return template.shuffled()
|
||||
}
|
||||
|
||||
|
||||
func mockTx(index: Int, kind: Kind) -> ConfirmedTransactionEntity {
|
||||
switch kind {
|
||||
case .received:
|
||||
return mockReceived(index)
|
||||
case .sent:
|
||||
return mockSent(index)
|
||||
}
|
||||
}
|
||||
|
||||
func mockSent(_ index: Int) -> ConfirmedTransactionEntity {
|
||||
ConfirmedTransaction(toAddress: "some_address", expiryHeight: BlockHeight.max, minedHeight: randomBlockHeight(), noteId: index, blockTimeInSeconds: randomTimeInterval(), transactionIndex: index, raw: Data(), id: index, value: Int.random(in: 1 ... ZcashSDK.ZATOSHI_PER_ZEC), memo: nil, rawTransactionId: Data())
|
||||
}
|
||||
|
||||
|
||||
func mockReceived(_ index: Int) -> ConfirmedTransactionEntity {
|
||||
ConfirmedTransaction(toAddress: nil, expiryHeight: BlockHeight.max, minedHeight: randomBlockHeight(), noteId: index, blockTimeInSeconds: randomTimeInterval(), transactionIndex: index, raw: Data(), id: index, value: Int.random(in: 1 ... ZcashSDK.ZATOSHI_PER_ZEC), memo: nil, rawTransactionId: Data())
|
||||
}
|
||||
|
||||
func randomBlockHeight() -> BlockHeight {
|
||||
BlockHeight.random(in: ZcashSDK.SAPLING_ACTIVATION_HEIGHT ... 1_000_000)
|
||||
}
|
||||
func randomTimeInterval() -> TimeInterval {
|
||||
Double.random(in: Date().timeIntervalSince1970 - 1000000.0 ... Date().timeIntervalSince1970)
|
||||
}
|
||||
|
||||
func slice(txs: [ConfirmedTransactionEntity], offset: Int, limit: Int) -> [ConfirmedTransactionEntity] {
|
||||
guard offset < txs.count else { return [] }
|
||||
|
||||
return Array(txs[offset ..< min(offset + limit, txs.count - offset)])
|
||||
}
|
||||
}
|
||||
|
||||
extension MockTransactionRepository.Kind: Equatable {}
|
||||
|
||||
extension Array {
|
||||
func indices(where f: (_ element: Element) -> Bool) -> [Int]? {
|
||||
guard self.count > 0 else { return nil }
|
||||
var idx = [Int]()
|
||||
for i in 0 ..< self.count {
|
||||
if f(self[i]) {
|
||||
idx.append(i)
|
||||
}
|
||||
}
|
||||
|
||||
guard idx.count > 0 else { return nil }
|
||||
return idx
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
//
|
||||
// SampleLogger.swift
|
||||
// ZcashLightClientSample
|
||||
//
|
||||
// Created by Francisco Gindre on 3/9/20.
|
||||
// Copyright © 2020 Electric Coin Company. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os
|
||||
import ZcashLightClientKit
|
||||
class SampleLogger: ZcashLightClientKit.Logger {
|
||||
enum LogLevel: Int {
|
||||
case debug
|
||||
case error
|
||||
case warning
|
||||
case event
|
||||
case info
|
||||
}
|
||||
|
||||
var level: LogLevel
|
||||
init(logLevel: LogLevel) {
|
||||
self.level = logLevel
|
||||
}
|
||||
|
||||
private static let subsystem = Bundle.main.bundleIdentifier!
|
||||
static let oslog = OSLog(subsystem: subsystem, category: "test-logs")
|
||||
|
||||
func debug(_ message: String, file: String = #file, function: String = #function, line: Int = #line) {
|
||||
guard level.rawValue == LogLevel.debug.rawValue else { return }
|
||||
log(level: "DEBUG 🐞", message: message, file: file, function: function, line: line)
|
||||
}
|
||||
|
||||
func error(_ message: String, file: String = #file, function: String = #function, line: Int = #line) {
|
||||
guard level.rawValue <= LogLevel.error.rawValue else { return }
|
||||
log(level: "ERROR 💥", message: message, file: file, function: function, line: line)
|
||||
}
|
||||
|
||||
func warn(_ message: String, file: String = #file, function: String = #function, line: Int = #line) {
|
||||
guard level.rawValue <= LogLevel.warning.rawValue else { return }
|
||||
log(level: "WARNING ⚠️", message: message, file: file, function: function, line: line)
|
||||
}
|
||||
|
||||
func event(_ message: String, file: String = #file, function: String = #function, line: Int = #line) {
|
||||
guard level.rawValue <= LogLevel.event.rawValue else { return }
|
||||
log(level: "EVENT ⏱", message: message, file: file, function: function, line: line)
|
||||
}
|
||||
|
||||
func info(_ message: String, file: String = #file, function: String = #function, line: Int = #line) {
|
||||
guard level.rawValue <= LogLevel.info.rawValue else { return }
|
||||
log(level: "INFO ℹ️", message: message, file: file, function: function, line: line)
|
||||
}
|
||||
|
||||
private func log(level: String, message: String, file: String, function: String, line: Int) {
|
||||
let fileName = file as NSString
|
||||
|
||||
os_log("[%@] %@ - %@ - Line: %d -> %@", log: Self.oslog, type: .default, level, fileName.lastPathComponent, function, line, message)
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,235 @@
|
|||
//
|
||||
// Stubs.swift
|
||||
// ZcashLightClientKitTests
|
||||
//
|
||||
// Created by Francisco Gindre on 18/09/2019.
|
||||
// Copyright © 2019 Electric Coin Company. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import GRPC
|
||||
import SwiftProtobuf
|
||||
@testable import ZcashLightClientKit
|
||||
|
||||
class AwfulLightWalletService: MockLightWalletService {
|
||||
override func latestBlockHeight() throws -> BlockHeight {
|
||||
throw LightWalletServiceError.generalError
|
||||
}
|
||||
|
||||
override func blockRange(_ range: CompactBlockRange) throws -> [ZcashCompactBlock] {
|
||||
throw LightWalletServiceError.invalidBlock
|
||||
}
|
||||
|
||||
override func latestBlockHeight(result: @escaping (Result<BlockHeight, LightWalletServiceError>) -> Void) {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
result(.failure(LightWalletServiceError.generalError))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override func blockRange(_ range: CompactBlockRange, result: @escaping (Result<[ZcashCompactBlock], LightWalletServiceError>) -> Void) {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
result(.failure(LightWalletServiceError.generalError))
|
||||
}
|
||||
}
|
||||
|
||||
override func submit(spendTransaction: Data, result: @escaping(Result<LightWalletServiceResponse,LightWalletServiceError>) -> Void) {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
result(.failure(LightWalletServiceError.generalError))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Submits a raw transaction over lightwalletd. Blocking
|
||||
*/
|
||||
|
||||
override func submit(spendTransaction: Data) throws -> LightWalletServiceResponse {
|
||||
throw LightWalletServiceError.generalError
|
||||
}
|
||||
}
|
||||
|
||||
class SlightlyBadLightWalletService: MockLightWalletService {
|
||||
|
||||
|
||||
override func submit(spendTransaction: Data, result: @escaping(Result<LightWalletServiceResponse,LightWalletServiceError>) -> Void) {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
result(.success(LightWalletServiceMockResponse.error))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Submits a raw transaction over lightwalletd. Blocking
|
||||
*/
|
||||
|
||||
override func submit(spendTransaction: Data) throws -> LightWalletServiceResponse {
|
||||
LightWalletServiceMockResponse.error
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension LightWalletServiceMockResponse {
|
||||
static var error: LightWalletServiceMockResponse {
|
||||
LightWalletServiceMockResponse(errorCode: -100, errorMessage: "Ohhh this is bad dude, really bad, you lost all your internet money", unknownFields: UnknownStorage())
|
||||
}
|
||||
static var success: LightWalletServiceMockResponse {
|
||||
LightWalletServiceMockResponse(errorCode: 0, errorMessage: "", unknownFields: UnknownStorage())
|
||||
}
|
||||
}
|
||||
|
||||
class MockRustBackend: ZcashRustBackendWelding {
|
||||
|
||||
static var mockDataDb = false
|
||||
static var mockAcounts = false
|
||||
static var mockError: RustWeldingError?
|
||||
static var mockLastError: String?
|
||||
static var mockAccounts: [String]?
|
||||
static var mockAddresses: [String]?
|
||||
static var mockBalance: Int64?
|
||||
static var mockVerifiedBalance: Int64?
|
||||
static var mockMemo: String?
|
||||
static var mockSentMemo: String?
|
||||
static var mockValidateCombinedChainSuccessRate: Float?
|
||||
static var mockValidateCombinedChainFailAfterAttempts: Int?
|
||||
static var mockValidateCombinedChainKeepFailing = false
|
||||
static var mockValidateCombinedChainFailureHeight: BlockHeight = 0
|
||||
static var mockScanblocksSuccessRate: Float?
|
||||
static var mockCreateToAddress: Int64?
|
||||
static var rustBackend = ZcashRustBackend.self
|
||||
|
||||
|
||||
static func lastError() -> RustWeldingError? {
|
||||
mockError ?? rustBackend.lastError()
|
||||
}
|
||||
|
||||
static func getLastError() -> String? {
|
||||
mockLastError ?? rustBackend.getLastError()
|
||||
}
|
||||
|
||||
static func isValidShieldedAddress(_ address: String) throws -> Bool {
|
||||
true
|
||||
}
|
||||
|
||||
static func isValidTransparentAddress(_ address: String) throws -> Bool {
|
||||
true
|
||||
}
|
||||
|
||||
static func initDataDb(dbData: URL) throws {
|
||||
if !mockDataDb {
|
||||
try rustBackend.initDataDb(dbData: dbData)
|
||||
}
|
||||
}
|
||||
|
||||
static func initAccountsTable(dbData: URL, seed: [UInt8], accounts: Int32) -> [String]? {
|
||||
mockAccounts ?? rustBackend.initAccountsTable(dbData: dbData, seed: seed, accounts: accounts)
|
||||
}
|
||||
|
||||
static func initBlocksTable(dbData: URL, height: Int32, hash: String, time: UInt32, saplingTree: String) throws {
|
||||
if !mockDataDb {
|
||||
try rustBackend.initBlocksTable(dbData: dbData, height: height, hash: hash, time: time, saplingTree: saplingTree)
|
||||
}
|
||||
}
|
||||
|
||||
static func getAddress(dbData: URL, account: Int32) -> String? {
|
||||
mockAddresses?[Int(account)] ?? rustBackend.getAddress(dbData: dbData, account: account)
|
||||
}
|
||||
|
||||
static func getBalance(dbData: URL, account: Int32) -> Int64 {
|
||||
mockBalance ?? rustBackend.getBalance(dbData: dbData, account: account)
|
||||
}
|
||||
|
||||
static func getVerifiedBalance(dbData: URL, account: Int32) -> Int64 {
|
||||
mockVerifiedBalance ?? rustBackend.getVerifiedBalance(dbData: dbData, account: account)
|
||||
}
|
||||
|
||||
static func getReceivedMemoAsUTF8(dbData: URL, idNote: Int64) -> String? {
|
||||
mockMemo ?? rustBackend.getReceivedMemoAsUTF8(dbData: dbData, idNote: idNote)
|
||||
}
|
||||
|
||||
static func getSentMemoAsUTF8(dbData: URL, idNote: Int64) -> String? {
|
||||
mockSentMemo ?? getSentMemoAsUTF8(dbData: dbData, idNote: idNote)
|
||||
}
|
||||
|
||||
static func validateCombinedChain(dbCache: URL, dbData: URL) -> Int32 {
|
||||
if let rate = self.mockValidateCombinedChainSuccessRate {
|
||||
if shouldSucceed(successRate: rate) {
|
||||
return validationResult(dbCache: dbCache, dbData: dbData)
|
||||
} else {
|
||||
return Int32(mockValidateCombinedChainFailureHeight)
|
||||
}
|
||||
} else if let attempts = self.mockValidateCombinedChainFailAfterAttempts {
|
||||
self.mockValidateCombinedChainFailAfterAttempts = attempts - 1
|
||||
if attempts > 0 {
|
||||
return validationResult(dbCache: dbCache, dbData: dbData)
|
||||
} else {
|
||||
|
||||
if attempts == 0 {
|
||||
return Int32(mockValidateCombinedChainFailureHeight)
|
||||
} else if attempts < 0 && mockValidateCombinedChainKeepFailing {
|
||||
return Int32(mockValidateCombinedChainFailureHeight)
|
||||
} else {
|
||||
return validationResult(dbCache: dbCache, dbData: dbData)
|
||||
}
|
||||
}
|
||||
}
|
||||
return rustBackend.validateCombinedChain(dbCache: dbCache, dbData: dbData)
|
||||
}
|
||||
|
||||
private static func validationResult(dbCache: URL, dbData: URL) -> Int32{
|
||||
if mockDataDb {
|
||||
return -1
|
||||
} else {
|
||||
return rustBackend.validateCombinedChain(dbCache: dbCache, dbData: dbData)
|
||||
}
|
||||
}
|
||||
|
||||
static func rewindToHeight(dbData: URL, height: Int32) -> Bool {
|
||||
mockDataDb ? true : rustBackend.rewindToHeight(dbData: dbData, height: height)
|
||||
}
|
||||
|
||||
static func scanBlocks(dbCache: URL, dbData: URL) -> Bool {
|
||||
if let rate = mockScanblocksSuccessRate {
|
||||
|
||||
if shouldSucceed(successRate: rate) {
|
||||
return mockDataDb ? true : rustBackend.scanBlocks(dbCache: dbCache, dbData: dbData)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return rustBackend.scanBlocks(dbCache: dbCache, dbData: dbData)
|
||||
}
|
||||
|
||||
static func createToAddress(dbData: URL, account: Int32, extsk: String, consensusBranchId: Int32, to: String, value: Int64, memo: String?, spendParamsPath: String, outputParamsPath: String) -> Int64 {
|
||||
mockCreateToAddress ?? rustBackend.createToAddress(dbData: dbData, account: account, extsk: extsk, consensusBranchId: consensusBranchId, to: to, value: value, memo: memo, spendParamsPath: spendParamsPath, outputParamsPath: outputParamsPath)
|
||||
}
|
||||
|
||||
static func shouldSucceed(successRate: Float) -> Bool {
|
||||
let random = Float.random(in: 0.0...1.0)
|
||||
return random <= successRate
|
||||
}
|
||||
|
||||
static func deriveExtendedFullViewingKey(_ spendingKey: String) throws -> String? {
|
||||
nil
|
||||
}
|
||||
|
||||
static func deriveExtendedFullViewingKeys(seed: [UInt8], accounts: Int32) throws -> [String]? {
|
||||
nil
|
||||
}
|
||||
|
||||
static func deriveExtendedSpendingKeys(seed: [UInt8], accounts: Int32) throws -> [String]? {
|
||||
nil
|
||||
}
|
||||
|
||||
static func decryptAndStoreTransaction(dbData: URL, tx: [UInt8]) -> Bool {
|
||||
false
|
||||
}
|
||||
|
||||
static func importExtendedFullViewingKey(dbData: URL, extfvk: String) throws -> Int32 {
|
||||
-1
|
||||
}
|
||||
|
||||
|
||||
static func consensusBranchIdFor(height: Int32) throws -> Int32 {
|
||||
-1
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,260 @@
|
|||
//
|
||||
// TestCoordinator.swift
|
||||
// ZcashLightClientKit-Unit-Tests
|
||||
//
|
||||
// Created by Francisco Gindre on 4/29/20.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
@testable import ZcashLightClientKit
|
||||
@testable import Zircles
|
||||
/**
|
||||
This is the TestCoordinator
|
||||
What does it do? quite a lot.
|
||||
Is it a nice "SOLID" "Clean Code" piece of source code?
|
||||
Hell no. It's your testing overlord and you will be grateful it is.
|
||||
*/
|
||||
class TestCoordinator {
|
||||
|
||||
enum CoordinatorError: Error {
|
||||
case notDarksideWallet
|
||||
case notificationFromUnknownSynchronizer
|
||||
case notMockLightWalletService
|
||||
}
|
||||
|
||||
enum SyncThreshold {
|
||||
case upTo(height: BlockHeight)
|
||||
case latestHeight
|
||||
}
|
||||
|
||||
enum DarksideData {
|
||||
case `default`
|
||||
case predefined(dataset: DarksideDataset)
|
||||
case url(urlString: String, startHeigth: BlockHeight)
|
||||
}
|
||||
|
||||
var completionHandler: ((SDKSynchronizer) -> Void)?
|
||||
var errorHandler: ((Error?) -> Void)?
|
||||
var seed: String
|
||||
var birthday: BlockHeight
|
||||
var channelProvider: ChannelProvider
|
||||
var synchronizer: SDKSynchronizer
|
||||
var service: DarksideWalletService
|
||||
var combineSynchronizer: CombineSynchronizer
|
||||
var spendingKeys: [String]?
|
||||
var databases: TemporaryTestDatabases
|
||||
|
||||
init(seed: String,
|
||||
walletBirthday: BlockHeight,
|
||||
channelProvider: ChannelProvider) throws {
|
||||
self.seed = seed
|
||||
self.birthday = walletBirthday
|
||||
self.channelProvider = channelProvider
|
||||
self.databases = TemporaryDbBuilder.build()
|
||||
self.service = DarksideWalletService()
|
||||
let storage = CompactBlockStorage(url: databases.cacheDB, readonly: false)
|
||||
try storage.createTable()
|
||||
|
||||
let downloader = CompactBlockDownloader(service: self.service, storage: storage)
|
||||
|
||||
|
||||
let buildResult = try TestSynchronizerBuilder.build(
|
||||
rustBackend: ZcashRustBackend.self,
|
||||
lowerBoundHeight: self.birthday,
|
||||
cacheDbURL: databases.cacheDB,
|
||||
dataDbURL: databases.dataDB,
|
||||
pendingDbURL: databases.pendingDB,
|
||||
service: self.service,
|
||||
repository: TransactionSQLDAO(dbProvider: SimpleConnectionProvider(path: databases.dataDB.absoluteString)),
|
||||
downloader: downloader,
|
||||
spendParamsURL: try __spendParamsURL(),
|
||||
outputParamsURL: try __outputParamsURL(),
|
||||
seedBytes: TestSeed().seed(),
|
||||
walletBirthday: WalletBirthday.birthday(with: birthday),
|
||||
loggerProxy: SampleLogger(logLevel: .debug))
|
||||
|
||||
self.synchronizer = buildResult.synchronizer.synchronizer
|
||||
self.combineSynchronizer = buildResult.synchronizer
|
||||
self.spendingKeys = buildResult.spendingKeys
|
||||
subscribeToNotifications(synchronizer: self.synchronizer)
|
||||
}
|
||||
|
||||
func stop() throws {
|
||||
try synchronizer.stop()
|
||||
self.completionHandler = nil
|
||||
self.errorHandler = nil
|
||||
|
||||
}
|
||||
|
||||
func setDarksideWalletState(_ state: DarksideData) throws {
|
||||
|
||||
switch state {
|
||||
case .default:
|
||||
try service.useDataset(DarksideDataset.beforeReOrg.rawValue)
|
||||
case .predefined(let dataset):
|
||||
try service.useDataset(dataset.rawValue)
|
||||
case .url(let urlString,_):
|
||||
try service.useDataset(from: urlString)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func setLatestHeight(height: BlockHeight) throws {
|
||||
try service.applyStaged(nextLatestHeight: height)
|
||||
}
|
||||
|
||||
func sync(completion: @escaping (SDKSynchronizer) -> Void, error: @escaping (Error?) -> Void) throws {
|
||||
self.completionHandler = completion
|
||||
self.errorHandler = error
|
||||
|
||||
try synchronizer.start()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Notifications
|
||||
*/
|
||||
func subscribeToNotifications(synchronizer: Synchronizer) {
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(synchronizerFailed(_:)), name: .synchronizerFailed, object: synchronizer)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(synchronizerSynced(_:)), name: .synchronizerSynced, object: synchronizer)
|
||||
|
||||
}
|
||||
|
||||
@objc func synchronizerFailed(_ notification: Notification) {
|
||||
self.errorHandler?(notification.userInfo?[SDKSynchronizer.NotificationKeys.error] as? Error)
|
||||
}
|
||||
|
||||
@objc func synchronizerSynced(_ notification: Notification) {
|
||||
if case .stopped = self.synchronizer.status {
|
||||
LoggerProxy.debug("WARNING: notification received after synchronizer was stopped")
|
||||
return
|
||||
}
|
||||
self.completionHandler?(self.synchronizer)
|
||||
}
|
||||
|
||||
@objc func synchronizerDisconnected(_ notification: Notification) {
|
||||
/// TODO: See if we need hooks for this
|
||||
}
|
||||
|
||||
@objc func synchronizerStarted(_ notification: Notification) {
|
||||
/// TODO: See if we need hooks for this
|
||||
}
|
||||
|
||||
@objc func synchronizerStopped(_ notification: Notification) {
|
||||
/// TODO: See if we need hooks for this
|
||||
}
|
||||
|
||||
@objc func synchronizerSyncing(_ notification: Notification) {
|
||||
/// TODO: See if we need hooks for this
|
||||
}
|
||||
}
|
||||
|
||||
extension TestCoordinator {
|
||||
func resetBlocks(dataset: DarksideData) throws {
|
||||
|
||||
switch dataset {
|
||||
case .default:
|
||||
try service.useDataset(DarksideDataset.beforeReOrg.rawValue)
|
||||
case .predefined(let blocks):
|
||||
try service.useDataset(blocks.rawValue)
|
||||
case .url(let urlString,_):
|
||||
try service.useDataset(urlString)
|
||||
}
|
||||
}
|
||||
|
||||
func stageBlockCreate(height: BlockHeight, count: Int = 1, nonce: Int = 0) throws {
|
||||
try service.stageBlocksCreate(from: height, count: count, nonce: 0)
|
||||
}
|
||||
|
||||
func applyStaged(blockheight: BlockHeight) throws {
|
||||
try service.applyStaged(nextLatestHeight: blockheight)
|
||||
}
|
||||
|
||||
func stageTransaction(_ tx: RawTransaction, at height: BlockHeight) throws {
|
||||
try service.stageTransaction(tx, at: height)
|
||||
}
|
||||
|
||||
func stageTransaction(url: String, at height: BlockHeight) throws {
|
||||
try service.stageTransaction(from: url, at: height)
|
||||
}
|
||||
|
||||
func latestHeight() throws -> BlockHeight {
|
||||
try service.latestBlockHeight()
|
||||
}
|
||||
|
||||
func reset(saplingActivation: BlockHeight) throws {
|
||||
try service.reset(saplingActivation: saplingActivation)
|
||||
}
|
||||
|
||||
func getIncomingTransactions() throws -> [RawTransaction]? {
|
||||
return try service.getIncomingTransactions()
|
||||
}
|
||||
}
|
||||
|
||||
struct TemporaryTestDatabases {
|
||||
var cacheDB: URL
|
||||
var dataDB: URL
|
||||
var pendingDB: URL
|
||||
}
|
||||
|
||||
class TemporaryDbBuilder {
|
||||
|
||||
static func build() -> TemporaryTestDatabases {
|
||||
let tempUrl = try! __documentsDirectory()
|
||||
let timestamp = String(Int(Date().timeIntervalSince1970))
|
||||
|
||||
return TemporaryTestDatabases(cacheDB: tempUrl.appendingPathComponent("cache_db_\(timestamp).db"),
|
||||
dataDB: tempUrl.appendingPathComponent("data_db_\(timestamp).db"),
|
||||
pendingDB: tempUrl.appendingPathComponent("pending_db_\(timestamp).db"))
|
||||
}
|
||||
}
|
||||
|
||||
class TestSynchronizerBuilder {
|
||||
|
||||
static func build(
|
||||
rustBackend: ZcashRustBackendWelding.Type,
|
||||
lowerBoundHeight: BlockHeight,
|
||||
cacheDbURL: URL,
|
||||
dataDbURL: URL,
|
||||
pendingDbURL: URL,
|
||||
service: LightWalletService,
|
||||
repository: TransactionRepository,
|
||||
downloader: CompactBlockDownloader,
|
||||
spendParamsURL: URL,
|
||||
outputParamsURL: URL,
|
||||
seedBytes: [UInt8],
|
||||
walletBirthday: WalletBirthday,
|
||||
loggerProxy: Logger? = nil
|
||||
) throws -> (spendingKeys: [String]?, synchronizer: CombineSynchronizer) {
|
||||
|
||||
let initializer = Initializer(
|
||||
rustBackend: rustBackend,
|
||||
lowerBoundHeight: lowerBoundHeight,
|
||||
cacheDbURL: cacheDbURL,
|
||||
dataDbURL: dataDbURL,
|
||||
pendingDbURL: pendingDbURL,
|
||||
service: service,
|
||||
repository: repository,
|
||||
downloader: downloader,
|
||||
spendParamsURL: spendParamsURL,
|
||||
outputParamsURL: outputParamsURL,
|
||||
loggerProxy: loggerProxy
|
||||
)
|
||||
let credentials = try initializer.initialize(seedProvider: StubSeedProvider(bytes: seedBytes), walletBirthdayHeight: walletBirthday.height)
|
||||
|
||||
return (credentials, try CombineSynchronizer(initializer: initializer)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class StubSeedProvider: SeedProvider {
|
||||
|
||||
let bytes: [UInt8]
|
||||
init(bytes: [UInt8]) {
|
||||
self.bytes = bytes
|
||||
}
|
||||
func seed() -> [UInt8] {
|
||||
self.bytes
|
||||
}
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
//
|
||||
// TestDbBuilder.swift
|
||||
// ZcashLightClientKitTests
|
||||
//
|
||||
// Created by Francisco Gindre on 10/14/19.
|
||||
// Copyright © 2019 Electric Coin Company. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SQLite
|
||||
@testable import ZcashLightClientKit
|
||||
|
||||
struct TestDbHandle {
|
||||
var originalDb: URL
|
||||
var readWriteDb: URL
|
||||
|
||||
init(originalDb: URL) {
|
||||
self.originalDb = originalDb
|
||||
self.readWriteDb = FileManager.default.temporaryDirectory.appendingPathComponent(self.originalDb.lastPathComponent.appending("_\(Date().timeIntervalSince1970)")) // avoid files clashing because crashing tests failed to remove previous ones by incrementally changing the filename
|
||||
}
|
||||
|
||||
func setUp() throws {
|
||||
try FileManager.default.copyItem(at: originalDb, to: readWriteDb)
|
||||
}
|
||||
|
||||
func dispose() {
|
||||
try? FileManager.default.removeItem(at: readWriteDb)
|
||||
}
|
||||
|
||||
func connectionProvider(readwrite: Bool = true) -> ConnectionProvider {
|
||||
SimpleConnectionProvider(path: self.readWriteDb.absoluteString, readonly: !readwrite)
|
||||
}
|
||||
}
|
||||
|
||||
class TestDbBuilder {
|
||||
|
||||
enum TestBuilderError: Error {
|
||||
case generalError
|
||||
}
|
||||
|
||||
static func inMemoryCompactBlockStorage() throws -> CompactBlockStorage {
|
||||
let compactBlockDao = CompactBlockStorage(connectionProvider: try InMemoryDbProvider())
|
||||
try compactBlockDao.createTable()
|
||||
return compactBlockDao
|
||||
}
|
||||
|
||||
static func diskCompactBlockStorage(at url: URL) throws -> CompactBlockStorage {
|
||||
let compactBlockDao = CompactBlockStorage(connectionProvider: SimpleConnectionProvider(path: url.absoluteString))
|
||||
try compactBlockDao.createTable()
|
||||
return compactBlockDao
|
||||
}
|
||||
|
||||
static func pendingTransactionsDbURL() throws -> URL {
|
||||
try __documentsDirectory().appendingPathComponent("pending.db")
|
||||
}
|
||||
static func prePopulatedCacheDbURL() -> URL? {
|
||||
Bundle(for: TestDbBuilder.self).url(forResource: "cache", withExtension: "db")
|
||||
}
|
||||
|
||||
static func prePopulatedDataDbURL() -> URL? {
|
||||
Bundle(for: TestDbBuilder.self).url(forResource: "test_data", withExtension: "db")
|
||||
}
|
||||
|
||||
static func prepopulatedDataDbProvider() -> ConnectionProvider? {
|
||||
let bundle = Bundle(for: TestDbBuilder.self)
|
||||
guard let url = bundle.url(forResource: "ZcashSdk_Data", withExtension: "db") else { return nil }
|
||||
let provider = SimpleConnectionProvider(path: url.absoluteString, readonly: true)
|
||||
return provider
|
||||
}
|
||||
|
||||
static func transactionRepository() -> TransactionRepository? {
|
||||
guard let provider = prepopulatedDataDbProvider() else { return nil }
|
||||
|
||||
return TransactionSQLDAO(dbProvider: provider)
|
||||
}
|
||||
|
||||
static func sentNotesRepository() -> SentNotesRepository? {
|
||||
guard let provider = prepopulatedDataDbProvider() else { return nil }
|
||||
return SentNotesSQLDAO(dbProvider: provider)
|
||||
}
|
||||
|
||||
static func receivedNotesRepository() -> ReceivedNoteRepository? {
|
||||
guard let provider = prepopulatedDataDbProvider() else { return nil }
|
||||
return ReceivedNotesSQLDAO(dbProvider: provider)
|
||||
}
|
||||
|
||||
static func seed(db: CompactBlockRepository, with blockRange: CompactBlockRange) throws {
|
||||
|
||||
guard let blocks = StubBlockCreator.createBlockRange(blockRange) else {
|
||||
throw TestBuilderError.generalError
|
||||
}
|
||||
|
||||
try db.write(blocks: blocks)
|
||||
}
|
||||
}
|
||||
|
||||
struct InMemoryDbProvider: ConnectionProvider {
|
||||
var readonly: Bool
|
||||
|
||||
var conn: Connection
|
||||
init(readonly: Bool = false) throws {
|
||||
self.readonly = readonly
|
||||
self.conn = try Connection(.inMemory, readonly: readonly)
|
||||
}
|
||||
|
||||
func connection() throws -> Connection {
|
||||
self.conn
|
||||
}
|
||||
}
|
||||
|
||||
struct StubBlockCreator {
|
||||
static func createRandomDataBlock(with height: BlockHeight) -> ZcashCompactBlock? {
|
||||
guard let data = randomData(ofLength: 100) else {
|
||||
LoggerProxy.debug("error creating stub block")
|
||||
return nil
|
||||
}
|
||||
return ZcashCompactBlock(height: height, data: data)
|
||||
}
|
||||
static func createBlockRange(_ range: CompactBlockRange) -> [ZcashCompactBlock]? {
|
||||
|
||||
var blocks = [ZcashCompactBlock]()
|
||||
for height in range {
|
||||
guard let block = createRandomDataBlock(with: height) else {
|
||||
return nil
|
||||
}
|
||||
blocks.append(block)
|
||||
}
|
||||
|
||||
return blocks
|
||||
}
|
||||
|
||||
static func randomData(ofLength length: Int) -> Data? {
|
||||
var bytes = [UInt8](repeating: 0, count: length)
|
||||
let status = SecRandomCopyBytes(kSecRandomDefault, length, &bytes)
|
||||
if status == errSecSuccess {
|
||||
return Data(bytes: &bytes, count: bytes.count)
|
||||
}
|
||||
LoggerProxy.debug("Status \(status)")
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
//
|
||||
// Tests+Utils.swift
|
||||
// ZcashLightClientKitTests
|
||||
//
|
||||
// Created by Francisco Gindre on 18/09/2019.
|
||||
// Copyright © 2019 Electric Coin Company. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import GRPC
|
||||
import ZcashLightClientKit
|
||||
import XCTest
|
||||
import NIO
|
||||
class LightWalletEndpointBuilder {
|
||||
static var `default`: LightWalletEndpoint {
|
||||
LightWalletEndpoint(address: "localhost", port: 9067, secure: false)
|
||||
}
|
||||
}
|
||||
|
||||
class ChannelProvider {
|
||||
func channel(secure: Bool = false) -> GRPCChannel {
|
||||
let endpoint = LightWalletEndpointBuilder.default
|
||||
|
||||
let configuration = ClientConnection.Configuration(target: .hostAndPort(endpoint.host, endpoint.port), eventLoopGroup: MultiThreadedEventLoopGroup(numberOfThreads: 1), tls: secure ? .init() : nil)
|
||||
return ClientConnection(configuration: configuration)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
struct MockDbInit {
|
||||
@discardableResult static func emptyFile(at path: String) -> Bool {
|
||||
|
||||
FileManager.default.createFile(atPath: path, contents: Data("".utf8), attributes: nil)
|
||||
|
||||
}
|
||||
|
||||
static func destroy(at path: String) throws {
|
||||
try FileManager.default.removeItem(atPath: path)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension XCTestExpectation {
|
||||
func subscribe(to notification: Notification.Name, object: Any?) {
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(fulfill), name: notification, object: object)
|
||||
}
|
||||
|
||||
func unsubscribe(from notification: Notification.Name) {
|
||||
NotificationCenter.default.removeObserver(self, name: notification, object: nil)
|
||||
}
|
||||
|
||||
func unsubscribeFromNotifications() {
|
||||
NotificationCenter.default.removeObserver(self)
|
||||
}
|
||||
}
|
||||
|
||||
func __documentsDirectory() throws -> URL {
|
||||
try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
|
||||
}
|
||||
|
||||
func __cacheDbURL() throws -> URL {
|
||||
try __documentsDirectory().appendingPathComponent("cache.db", isDirectory: false)
|
||||
}
|
||||
|
||||
func __dataDbURL() throws -> URL {
|
||||
try __documentsDirectory().appendingPathComponent("data.db", isDirectory: false)
|
||||
}
|
||||
|
||||
func __spendParamsURL() throws -> URL {
|
||||
URL(string: Bundle.testBundle.url(forResource: "sapling-spend", withExtension: "params")!.path)!
|
||||
}
|
||||
|
||||
func __outputParamsURL() throws -> URL {
|
||||
URL(string: Bundle.testBundle.url(forResource: "sapling-output", withExtension: "params")!.path)!
|
||||
}
|
||||
|
||||
func copyParametersToDocuments() throws -> (spend: URL, output: URL) {
|
||||
|
||||
let spendURL = try __documentsDirectory().appendingPathComponent("sapling-spend.params", isDirectory: false)
|
||||
let outputURL = try __documentsDirectory().appendingPathComponent("sapling-output.params", isDirectory: false)
|
||||
try FileManager.default.copyItem(at: try __spendParamsURL(), to: spendURL)
|
||||
try FileManager.default.copyItem(at: try __outputParamsURL(), to: outputURL)
|
||||
|
||||
return (spendURL, outputURL)
|
||||
}
|
||||
|
||||
func deleteParametersFromDocuments() throws {
|
||||
let documents = try __documentsDirectory()
|
||||
deleteParamsFrom(spend: documents.appendingPathComponent("sapling-spend.params"), output: documents.appendingPathComponent("sapling-output.params"))
|
||||
}
|
||||
func deleteParamsFrom(spend: URL, output: URL) {
|
||||
try? FileManager.default.removeItem(at: spend)
|
||||
try? FileManager.default.removeItem(at: output)
|
||||
}
|
||||
|
||||
func parametersReady() -> Bool {
|
||||
|
||||
guard let output = try? __outputParamsURL(),
|
||||
let spend = try? __spendParamsURL(),
|
||||
FileManager.default.isReadableFile(atPath: output.absoluteString),
|
||||
FileManager.default.isReadableFile(atPath: spend.absoluteString) else {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
class StubTest: XCTestCase {}
|
||||
extension Bundle {
|
||||
static var testBundle: Bundle {
|
||||
Bundle(for: StubTest.self)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class TestSeed: SeedProvider {
|
||||
|
||||
/**
|
||||
test account: "still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread"
|
||||
*/
|
||||
let seedString = Data(base64Encoded: "9VDVOZZZOWWHpZtq1Ebridp3Qeux5C+HwiRR0g7Oi7HgnMs8Gfln83+/Q1NnvClcaSwM4ADFL1uZHxypEWlWXg==")!
|
||||
|
||||
func seed() -> [UInt8] {
|
||||
[UInt8](seedString)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue