createNewZircle darksidelightwalletd tests

This commit is contained in:
Francisco Gindre 2020-07-01 20:01:12 -03:00
parent 74e1da5cdd
commit c870646780
17 changed files with 2255 additions and 31 deletions

View File

@ -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;
};

View File

@ -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>

View File

@ -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
}

View File

@ -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)")
}
}

View File

@ -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 {}

View File

@ -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
}
}

View File

@ -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) {}
}

View File

@ -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()
}
}

View File

@ -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")
]
}

View File

@ -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) {
}
}

View File

@ -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)
}
}

View File

@ -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
}
}

View File

@ -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)
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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)
}
}