[#746] Modularize GRPC related code

Closes #746

- `LightWalletGRPCService` is no longer public. `LightWalletService` is no longer public.
- `LightWalletGRPCService` shouldn't be used dicrectly. Use `LightWalletServiceFactory` to create instance of the service.
- Moved sending of `blockProcessorConnectivityStateChanged` notification to `Initilizer`.
- All the errors from GRPC are mapped to `LightWalletServiceError`. Handling of `GRPCStatus` in the SDK is no longer required.
- `Service` directory (that contains GRPC code) is moved to `Modules/Service`. We can put more code to `Modules/` if we decide
to modularize more code.
This commit is contained in:
Michal Fousek 2023-02-03 11:23:35 +01:00
parent 62437c5c7d
commit 385c0a7195
49 changed files with 309 additions and 280 deletions

View File

@ -2,15 +2,16 @@
# https://github.com/raywenderlich/swift-style-guide
included:
- Sources/ZcashLightClientKit/
- Tests/
- Example/ZcashLightClientSample/ZcashLightClientSample
- Example/ZcashLightClientSample/ZcashLightClientSampleTests
- Sources/ZcashLightClientKit/
- Tests/
- Example/ZcashLightClientSample/ZcashLightClientSample
- Example/ZcashLightClientSample/ZcashLightClientSampleTests
excluded:
- xctemplates/
- Sources/ZcashLightClientKit/Service/ProtoBuf/compact_formats.pb.swift
- Sources/ZcashLightClientKit/Service/ProtoBuf/service.pb.swift
- Sources/ZcashLightClientKit/Service/ProtoBuf/service.grpc.swift
- Sources/ZcashLightClientKit/Modules/Service/GRPC/ProtoBuf/compact_formats.pb.swift
- Sources/ZcashLightClientKit/Modules/Service/GRPC/ProtoBuf/service.pb.swift
- Sources/ZcashLightClientKit/Modules/Service/GRPC/ProtoBuf/service.grpc.swift
- Tests/TestUtils/proto
- Tests/TestUtils/TestVector.swift
- Tests/OfflineTests/BirthdayTests.swift

View File

@ -1,5 +1,9 @@
# Unreleased
- [#726] Modularize GRPC layer
This is mostly internal change. `LightWalletService` is no longer public. If it
is used in your code replace it by using `SDKSynchronizer` API.
## File system backed block cache
File system based block cache. Compact blocks will now be stored

View File

@ -5,7 +5,6 @@
// Created by Francisco Gindre on 06/09/2019.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
// swiftlint:disable force_cast force_try force_unwrapping
import UIKit
import ZcashLightClientKit
import NotificationBubbles

View File

@ -11,8 +11,8 @@ import ZcashLightClientKit
class LatestHeightViewController: UIViewController {
@IBOutlet weak var blockHeightLabel: UILabel!
var service: LightWalletService = LightWalletGRPCService(endpoint: DemoAppConfig.endpoint)
let synchronizer = AppDelegate.shared.sharedSynchronizer
var model: BlockHeight? {
didSet {
if viewIfLoaded != nil {
@ -32,9 +32,9 @@ class LatestHeightViewController: UIViewController {
/// Note: It's safe to modify model or call fail() because all methods of a UIViewController are MainActor methods by default.
Task {
do {
model = try await service.latestBlockHeightAsync()
model = try await synchronizer.latestHeight()
} catch {
fail(error as? LightWalletServiceError ?? .unknown)
fail(error)
}
}
}
@ -47,7 +47,7 @@ class LatestHeightViewController: UIViewController {
blockHeightLabel.text = String(model)
}
func fail(_ error: LightWalletServiceError) {
func fail(_ error: Error) {
self.blockHeightLabel.text = "Error"
let alert = UIAlertController(title: "Error", message: String(describing: error), preferredStyle: .alert)

View File

@ -27,8 +27,8 @@ let package = Package(
.product(name: "libzcashlc", package: "zcash-light-client-ffi")
],
exclude: [
"Service/ProtoBuf/proto/compact_formats.proto",
"Service/ProtoBuf/proto/service.proto"
"Modules/Service/GRPC/ProtoBuf/proto/compact_formats.proto",
"Modules/Service/GRPC/ProtoBuf/proto/service.proto"
],
resources: [
.copy("Resources/checkpoints")

View File

@ -8,7 +8,6 @@
// swiftlint:disable file_length type_body_length
import Foundation
import GRPC
public typealias RefreshedUTXOs = (inserted: [UnspentTransactionOutputEntity], skipped: [UnspentTransactionOutputEntity])
@ -831,13 +830,13 @@ actor CompactBlockProcessor {
)
}
func notifyTransactions(_ txs: [ZcashTransaction.Overview], in range: BlockRange) {
func notifyTransactions(_ txs: [ZcashTransaction.Overview], in range: CompactBlockRange) {
NotificationSender.default.post(
name: .blockProcessorFoundTransactions,
object: self,
userInfo: [
CompactBlockProcessorNotificationKey.foundTransactions: txs,
CompactBlockProcessorNotificationKey.foundTransactionsRange: ClosedRange(uncheckedBounds: (range.start.height, range.end.height))
CompactBlockProcessorNotificationKey.foundTransactionsRange: range
]
)
}
@ -885,17 +884,6 @@ actor CompactBlockProcessor {
}
if let lwdError = error as? LightWalletServiceError {
return lwdError.mapToProcessorError()
} else if let rpcError = error as? GRPC.GRPCStatus {
switch rpcError {
case .ok:
LoggerProxy.warn("Error Raised when status is OK")
return CompactBlockProcessorError.grpcError(
statusCode: rpcError.code.rawValue,
message: rpcError.message ?? "Error Raised when status is OK"
)
default:
return CompactBlockProcessorError.grpcError(statusCode: rpcError.code.rawValue, message: rpcError.message ?? "No message")
}
}
return .unspecifiedError(underlyingError: error)
}

View File

@ -57,18 +57,17 @@ extension CompactBlockProcessor {
LoggerProxy.debug("Started Enhancing range: \(range)")
state = .enhancing
let blockRange = range.blockRange()
var retries = 0
let maxRetries = 5
// fetch transactions
do {
let startTime = Date()
let transactions = try transactionRepository.find(in: blockRange, limit: Int.max, kind: .all)
let transactions = try transactionRepository.find(in: range, limit: Int.max, kind: .all)
guard !transactions.isEmpty else {
await internalSyncProgress.set(range.upperBound, .latestEnhancedHeight)
LoggerProxy.debug("no transactions detected on range: \(blockRange.printRange)")
LoggerProxy.debug("no transactions detected on range: \(range.lowerBound)...\(range.upperBound)")
return
}
@ -121,8 +120,8 @@ extension CompactBlockProcessor {
throw error
}
if let foundTxs = try? transactionRepository.find(in: blockRange, limit: Int.max, kind: .all) {
notifyTransactions(foundTxs, in: blockRange)
if let foundTxs = try? transactionRepository.find(in: range, limit: Int.max, kind: .all) {
notifyTransactions(foundTxs, in: range)
}
await internalSyncProgress.set(range.upperBound, .latestEnhancedHeight)
@ -132,9 +131,3 @@ extension CompactBlockProcessor {
}
}
}
private extension BlockRange {
var printRange: String {
"\(self.start.height) ... \(self.end.height)"
}
}

View File

@ -78,12 +78,12 @@ class TransactionSQLDAO: TransactionRepository {
return try execute(query) { try ZcashTransaction.Overview(row: $0) }
}
func find(in range: BlockRange, limit: Int, kind: TransactionKind) throws -> [ZcashTransaction.Overview] {
func find(in range: CompactBlockRange, limit: Int, kind: TransactionKind) throws -> [ZcashTransaction.Overview] {
let query = transactionsView
.order((ZcashTransaction.Overview.Column.minedHeight ?? BlockHeight.max).desc, ZcashTransaction.Overview.Column.id.desc)
.filter(
ZcashTransaction.Overview.Column.minedHeight >= BlockHeight(range.start.height) &&
ZcashTransaction.Overview.Column.minedHeight <= BlockHeight(range.end.height)
ZcashTransaction.Overview.Column.minedHeight >= BlockHeight(range.lowerBound) &&
ZcashTransaction.Overview.Column.minedHeight <= BlockHeight(range.upperBound)
)
.filterQueryFor(kind: kind)
.limit(limit)

View File

@ -0,0 +1,24 @@
//
// BlockProgress.swift
//
//
// Created by Michal Fousek on 03.02.2023.
//
import Foundation
public struct BlockProgress: Equatable {
public var startHeight: BlockHeight
public var targetHeight: BlockHeight
public var progressHeight: BlockHeight
public var progress: Float {
let overall = self.targetHeight - self.startHeight
return overall > 0 ? Float((self.progressHeight - self.startHeight)) / Float(overall) : 0
}
}
public extension BlockProgress {
static let nullProgress = BlockProgress(startHeight: 0, targetHeight: 0, progressHeight: 0)
}

View File

@ -55,7 +55,8 @@ public enum ZcashTransaction {
public let value: Zatoshi
}
public struct Fetched {
/// Used when fetching blocks from the lightwalletd
struct Fetched {
public let rawID: Data
public let minedHeight: BlockHeight
public let raw: Data

View File

@ -21,6 +21,37 @@ extension BlockHeight {
}
}
/**
A Zcash compact block to store on cache DB
*/
public struct ZcashCompactBlock {
struct Meta {
var hash: Data
var time: UInt32
var saplingOutputs: UInt32
var orchardOutputs: UInt32
}
public var height: BlockHeight
public var data: Data
var meta: Meta
}
extension ZcashCompactBlock: Encodable {
enum CodingKeys: CodingKey {
case height
case data
case meta
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(data, forKey: .data)
try container.encode(height, forKey: .height)
}
}
extension ZcashCompactBlock {
init(compactBlock: CompactBlock) {
self.height = Int(compactBlock.height)

View File

@ -53,12 +53,17 @@ public struct LightWalletEndpoint {
}
}
extension Notification.Name {
static let connectionStatusChanged = Notification.Name("LightWalletServiceConnectivityStatusChanged")
}
/**
Wrapper for all the Rust backend functionality that does not involve processing blocks. This
class initializes the Rust backend and the supporting data required to exercise those abilities.
The [cash.z.wallet.sdk.block.CompactBlockProcessor] handles all the remaining Rust backend
functionality, related to processing blocks.
*/
// swiftlint:disable:next type_body_length
public class Initializer {
public enum InitializationResult {
case success
@ -119,7 +124,7 @@ public class Initializer {
dataDbURL: dataDbURL,
pendingDbURL: pendingDbURL,
endpoint: endpoint,
service: LightWalletGRPCService(endpoint: endpoint),
service: Self.makeLightWalletServiceFactory(endpoint: endpoint).make(),
repository: TransactionRepositoryBuilder.build(dataDbURL: dataDbURL),
accountRepository: AccountRepositoryBuilder.build(
dataDbURL: dataDbURL,
@ -171,8 +176,6 @@ public class Initializer {
alias: String = "",
loggerProxy: Logger? = nil
) {
let lwdService = LightWalletGRPCService(endpoint: endpoint)
self.init(
rustBackend: ZcashRustBackend.self,
lowerBoundHeight: walletBirthday,
@ -182,7 +185,7 @@ public class Initializer {
dataDbURL: dataDbURL,
pendingDbURL: pendingDbURL,
endpoint: endpoint,
service: lwdService,
service: Self.makeLightWalletServiceFactory(endpoint: endpoint).make(),
repository: TransactionRepositoryBuilder.build(dataDbURL: dataDbURL),
accountRepository: AccountRepositoryBuilder.build(
dataDbURL: dataDbURL,
@ -251,6 +254,22 @@ public class Initializer {
self.network = network
}
private static func makeLightWalletServiceFactory(endpoint: LightWalletEndpoint) -> LightWalletServiceFactory {
return LightWalletServiceFactory(
endpoint: endpoint,
connectionStateChange: { oldState, newState in
NotificationSender.default.post(
name: .blockProcessorConnectivityStateChanged,
object: nil,
userInfo: [
CompactBlockProcessorNotificationKey.currentConnectivityStatus: newState,
CompactBlockProcessorNotificationKey.previousConnectivityStatus: oldState
]
)
}
)
}
/// Initialize the wallet. The ZIP-32 seed bytes can optionally be passed to perform
/// database migrations. most of the times the seed won't be needed. If they do and are
/// not provided this will fail with `InitializationResult.seedRequired`. It could

View File

@ -12,96 +12,47 @@ import NIO
import NIOHPACK
import NIOTransportServices
public typealias Channel = GRPC.GRPCChannel
public protocol LightWalletdInfo {
var version: String { get }
var vendor: String { get }
/// true
var taddrSupport: Bool { get }
/// either "main" or "test"
var chainName: String { get }
/// depends on mainnet or testnet
var saplingActivationHeight: UInt64 { get }
/// protocol identifier, see consensus/upgrades.cpp
var consensusBranchID: String { get }
/// latest block on the best chain
var blockHeight: UInt64 { get }
var gitCommit: String { get }
var branch: String { get }
var buildDate: String { get }
var buildUser: String { get }
/// less than tip height if zcashd is syncing
var estimatedHeight: UInt64 { get }
/// example: "v4.1.1-877212414"
var zcashdBuild: String { get }
/// example: "/MagicBean:4.1.1/"
var zcashdSubversion: String { get }
}
typealias Channel = GRPC.GRPCChannel
extension LightdInfo: LightWalletdInfo {}
extension SendResponse: LightWalletServiceResponse {}
/**
Swift GRPC implementation of Lightwalletd service
*/
public enum GRPCResult: Equatable {
enum GRPCResult: Equatable {
case success
case error(_ error: LightWalletServiceError)
}
public protocol CancellableCall {
protocol CancellableCall {
func cancel()
}
extension ServerStreamingCall: CancellableCall {
public func cancel() {
func cancel() {
self.cancel(promise: self.eventLoop.makePromise(of: Void.self))
}
}
public struct BlockProgress: Equatable {
public var startHeight: BlockHeight
public var targetHeight: BlockHeight
public var progressHeight: BlockHeight
public var progress: Float {
let overall = self.targetHeight - self.startHeight
return overall > 0 ? Float((self.progressHeight - self.startHeight)) / Float(overall) : 0
}
}
public extension BlockProgress {
static let nullProgress = BlockProgress(startHeight: 0, targetHeight: 0, progressHeight: 0)
}
protocol LatestBlockHeightProvider {
func latestBlockHeight(streamer: CompactTxStreamerNIOClient?) throws -> BlockHeight
}
class LiveLatestBlockHeightProvider: LatestBlockHeightProvider {
func latestBlockHeight(streamer: CompactTxStreamerNIOClient?) throws -> BlockHeight {
guard let height = try? streamer?.getLatestBlock(ChainSpec()).response.wait().compactBlockHeight() else {
throw LightWalletServiceError.timeOut
do {
guard let height = try? streamer?.getLatestBlock(ChainSpec()).response.wait().compactBlockHeight() else {
throw LightWalletServiceError.timeOut
}
return height
} catch {
throw error.mapToServiceError()
}
return height
}
}
public class LightWalletGRPCService {
class LightWalletGRPCService {
let channel: Channel
let connectionManager: ConnectionStatusManager
let compactTxStreamer: CompactTxStreamerNIOClient
@ -112,13 +63,14 @@ public class LightWalletGRPCService {
var queue: DispatchQueue
public convenience init(endpoint: LightWalletEndpoint) {
convenience init(endpoint: LightWalletEndpoint, connectionStateChange: @escaping (_ from: ConnectionState, _ to: ConnectionState) -> Void) {
self.init(
host: endpoint.host,
port: endpoint.port,
secure: endpoint.secure,
singleCallTimeout: endpoint.singleCallTimeoutInMillis,
streamingCallTimeout: endpoint.streamingCallTimeoutInMillis
streamingCallTimeout: endpoint.streamingCallTimeoutInMillis,
connectionStateChange: connectionStateChange
)
}
@ -129,14 +81,15 @@ public class LightWalletGRPCService {
/// - secure: whether this server is TLS or plaintext. default True (TLS)
/// - singleCallTimeout: Timeout for unary calls in milliseconds.
/// - streamingCallTimeout: Timeout for streaming calls in milliseconds.
public init(
init(
host: String,
port: Int = 9067,
secure: Bool = true,
singleCallTimeout: Int64,
streamingCallTimeout: Int64
streamingCallTimeout: Int64,
connectionStateChange: @escaping (_ from: ConnectionState, _ to: ConnectionState) -> Void
) {
self.connectionManager = ConnectionStatusManager()
self.connectionManager = ConnectionStatusManager(connectionStateChange: connectionStateChange)
self.queue = DispatchQueue.init(label: "LightWalletGRPCService")
self.streamingCallTimeout = TimeLimit.timeout(.milliseconds(streamingCallTimeout))
self.singleCallTimeout = TimeLimit.timeout(.milliseconds(singleCallTimeout))
@ -176,19 +129,27 @@ public class LightWalletGRPCService {
_ = channel.close()
}
func blockRange(startHeight: BlockHeight, endHeight: BlockHeight? = nil, result: @escaping (CompactBlock) -> Void) throws -> ServerStreamingCall<BlockRange, CompactBlock> {
func blockRange(startHeight: BlockHeight, endHeight: BlockHeight? = nil, result: @escaping (CompactBlock) -> Void) -> ServerStreamingCall<BlockRange, CompactBlock> {
compactTxStreamer.getBlockRange(BlockRange(startHeight: startHeight, endHeight: endHeight), handler: result)
}
func latestBlock() throws -> BlockID {
try compactTxStreamer.getLatestBlock(ChainSpec()).response.wait()
do {
return try compactTxStreamer.getLatestBlock(ChainSpec()).response.wait()
} catch {
throw error.mapToServiceError()
}
}
func getTx(hash: String) throws -> RawTransaction {
var filter = TxFilter()
filter.hash = Data(hash.utf8)
return try compactTxStreamer.getTransaction(filter).response.wait()
do {
return try compactTxStreamer.getTransaction(filter).response.wait()
} catch {
throw error.mapToServiceError()
}
}
static func callOptions(timeLimit: TimeLimit) -> CallOptions {
@ -206,23 +167,35 @@ public class LightWalletGRPCService {
// MARK: - LightWalletService
extension LightWalletGRPCService: LightWalletService {
public func getInfo() async throws -> LightWalletdInfo {
try await compactTxStreamerAsync.getLightdInfo(Empty())
func getInfo() async throws -> LightWalletdInfo {
do {
return try await compactTxStreamerAsync.getLightdInfo(Empty())
} catch {
throw error.mapToServiceError()
}
}
public func latestBlockHeight() throws -> BlockHeight {
try latestBlockHeightProvider.latestBlockHeight(streamer: compactTxStreamer)
func latestBlockHeight() throws -> BlockHeight {
do {
return try latestBlockHeightProvider.latestBlockHeight(streamer: compactTxStreamer)
} catch {
throw error.mapToServiceError()
}
}
public func latestBlockHeightAsync() async throws -> BlockHeight {
let blockID = try await compactTxStreamerAsync.getLatestBlock(ChainSpec())
guard let blockHeight = Int(exactly: blockID.height) else {
throw LightWalletServiceError.generalError(message: "error creating blockheight from BlockID \(blockID)")
func latestBlockHeightAsync() async throws -> BlockHeight {
do {
let blockID = try await compactTxStreamerAsync.getLatestBlock(ChainSpec())
guard let blockHeight = Int(exactly: blockID.height) else {
throw LightWalletServiceError.generalError(message: "error creating blockheight from BlockID \(blockID)")
}
return blockHeight
} catch {
throw error.mapToServiceError()
}
return blockHeight
}
public func blockRange(_ range: CompactBlockRange) -> AsyncThrowingStream<ZcashCompactBlock, Error> {
func blockRange(_ range: CompactBlockRange) -> AsyncThrowingStream<ZcashCompactBlock, Error> {
let stream = compactTxStreamerAsync.getBlockRange(range.blockRange())
return AsyncThrowingStream { continuation in
@ -239,7 +212,7 @@ extension LightWalletGRPCService: LightWalletService {
}
}
public func submit(spendTransaction: Data) async throws -> LightWalletServiceResponse {
func submit(spendTransaction: Data) async throws -> LightWalletServiceResponse {
do {
let transaction = RawTransaction.with { $0.data = spendTransaction }
return try await compactTxStreamerAsync.sendTransaction(transaction)
@ -248,15 +221,19 @@ extension LightWalletGRPCService: LightWalletService {
}
}
public func fetchTransaction(txId: Data) async throws -> ZcashTransaction.Fetched {
func fetchTransaction(txId: Data) async throws -> ZcashTransaction.Fetched {
var txFilter = TxFilter()
txFilter.hash = txId
let rawTx = try await compactTxStreamerAsync.getTransaction(txFilter)
return ZcashTransaction.Fetched(rawID: txId, minedHeight: BlockHeight(rawTx.height), raw: rawTx.data)
do {
let rawTx = try await compactTxStreamerAsync.getTransaction(txFilter)
return ZcashTransaction.Fetched(rawID: txId, minedHeight: BlockHeight(rawTx.height), raw: rawTx.data)
} catch {
throw error.mapToServiceError()
}
}
public func fetchUTXOs(
func fetchUTXOs(
for tAddress: String,
height: BlockHeight,
result: @escaping (Result<[UnspentTransactionOutputEntity], LightWalletServiceError>
@ -298,14 +275,14 @@ extension LightWalletGRPCService: LightWalletService {
}
}
public func fetchUTXOs(
func fetchUTXOs(
for tAddress: String,
height: BlockHeight
) -> AsyncThrowingStream<UnspentTransactionOutputEntity, Error> {
return fetchUTXOs(for: [tAddress], height: height)
}
public func fetchUTXOs(
func fetchUTXOs(
for tAddresses: [String],
height: BlockHeight
) -> AsyncThrowingStream<UnspentTransactionOutputEntity, Error> {
@ -344,7 +321,7 @@ extension LightWalletGRPCService: LightWalletService {
}
}
public func blockStream(
func blockStream(
startHeight: BlockHeight,
endHeight: BlockHeight
) -> AsyncThrowingStream<ZcashCompactBlock, Error> {
@ -370,15 +347,28 @@ extension LightWalletGRPCService: LightWalletService {
}
}
public func closeConnection() {
func closeConnection() {
_ = channel.close()
}
}
// MARK: - Extensions
extension Notification.Name {
static let connectionStatusChanged = Notification.Name("LightWalletServiceConnectivityStatusChanged")
extension ConnectivityState {
func toConnectionState() -> ConnectionState {
switch self {
case .connecting:
return .connecting
case .idle:
return .idle
case .ready:
return .online
case .shutdown:
return .shutdown
case .transientFailure:
return .reconnecting
}
}
}
extension TimeAmount {
@ -427,15 +417,12 @@ extension LightWalletServiceError {
}
class ConnectionStatusManager: ConnectivityStateDelegate {
let connectionStateChange: (_ from: ConnectionState, _ to: ConnectionState) -> Void
init(connectionStateChange: @escaping (_ from: ConnectionState, _ to: ConnectionState) -> Void) {
self.connectionStateChange = connectionStateChange
}
func connectivityStateDidChange(from oldState: ConnectivityState, to newState: ConnectivityState) {
LoggerProxy.event("Connection Changed from \(oldState) to \(newState)")
NotificationSender.default.post(
name: .blockProcessorConnectivityStateChanged,
object: self,
userInfo: [
CompactBlockProcessorNotificationKey.currentConnectivityStatus: newState,
CompactBlockProcessorNotificationKey.previousConnectivityStatus: oldState
]
)
connectionStateChange(oldState.toConnectionState(), newState.toConnectionState())
}
}

View File

@ -7,13 +7,11 @@
//
import Foundation
import GRPC
import SwiftProtobuf
/**
Wrapper for errors received from a Lightwalletd endpoint
*/
public enum LightWalletServiceError: Error {
enum LightWalletServiceError: Error {
case generalError(message: String)
case failed(statusCode: Int, message: String)
case invalidBlock
@ -27,7 +25,7 @@ public enum LightWalletServiceError: Error {
extension LightWalletServiceError: Equatable {
// swiftlint:disable cyclomatic_complexity
public static func == (lhs: Self, rhs: Self) -> Bool {
static func == (lhs: Self, rhs: Self) -> Bool {
switch lhs {
case .generalError(let message):
switch rhs {
@ -93,15 +91,64 @@ extension LightWalletServiceError: Equatable {
}
}
public protocol LightWalletServiceResponse {
var errorCode: Int32 { get }
var errorMessage: String { get }
var unknownFields: SwiftProtobuf.UnknownStorage { get }
protocol LightWalletdInfo {
var version: String { get }
var vendor: String { get }
/// true
var taddrSupport: Bool { get }
/// either "main" or "test"
var chainName: String { get }
/// depends on mainnet or testnet
var saplingActivationHeight: UInt64 { get }
/// protocol identifier, see consensus/upgrades.cpp
var consensusBranchID: String { get }
/// latest block on the best chain
var blockHeight: UInt64 { get }
var gitCommit: String { get }
var branch: String { get }
var buildDate: String { get }
var buildUser: String { get }
/// less than tip height if zcashd is syncing
var estimatedHeight: UInt64 { get }
/// example: "v4.1.1-877212414"
var zcashdBuild: String { get }
/// example: "/MagicBean:4.1.1/"
var zcashdSubversion: String { get }
}
extension SendResponse: LightWalletServiceResponse {}
protocol LightWalletServiceResponse {
var errorCode: Int32 { get }
var errorMessage: String { get }
}
public protocol LightWalletService {
struct LightWalletServiceFactory {
let endpoint: LightWalletEndpoint
let connectionStateChange: (_ from: ConnectionState, _ to: ConnectionState) -> Void
init(endpoint: LightWalletEndpoint, connectionStateChange: @escaping (_ from: ConnectionState, _ to: ConnectionState) -> Void) {
self.endpoint = endpoint
self.connectionStateChange = connectionStateChange
}
func make() -> LightWalletService {
return LightWalletGRPCService(endpoint: endpoint, connectionStateChange: connectionStateChange)
}
}
protocol LightWalletService {
/// Returns the info for this lightwalletd server
func getInfo() async throws -> LightWalletdInfo

View File

@ -8,37 +8,6 @@
import Foundation
/**
A Zcash compact block to store on cache DB
*/
public struct ZcashCompactBlock {
struct Meta {
var hash: Data
var time: UInt32
var saplingOutputs: UInt32
var orchardOutputs: UInt32
}
public var height: BlockHeight
public var data: Data
var meta: Meta
}
extension ZcashCompactBlock: Encodable {
enum CodingKeys: CodingKey {
case height
case data
case meta
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(data, forKey: .data)
try container.encode(height, forKey: .height)
}
}
enum CompactBlockRepositoryError: Error, Equatable {
/// cache is empty
case cacheEmpty

View File

@ -23,7 +23,7 @@ protocol TransactionRepository {
func find(id: Int) throws -> ZcashTransaction.Overview
func find(rawID: Data) throws -> ZcashTransaction.Overview
func find(offset: Int, limit: Int, kind: TransactionKind) throws -> [ZcashTransaction.Overview]
func find(in range: BlockRange, limit: Int, kind: TransactionKind) throws -> [ZcashTransaction.Overview]
func find(in range: CompactBlockRange, limit: Int, kind: TransactionKind) throws -> [ZcashTransaction.Overview]
func find(from: ZcashTransaction.Overview, limit: Int, kind: TransactionKind) throws -> [ZcashTransaction.Overview]
func findReceived(offset: Int, limit: Int) throws -> [ZcashTransaction.Received]
func findSent(offset: Int, limit: Int) throws -> [ZcashTransaction.Sent]

View File

@ -305,8 +305,8 @@ public class SDKSynchronizer: Synchronizer {
@objc func connectivityStateChanged(_ notification: Notification) {
guard
let userInfo = notification.userInfo,
let previous = userInfo[CompactBlockProcessorNotificationKey.previousConnectivityStatus] as? ConnectivityState,
let current = userInfo[CompactBlockProcessorNotificationKey.currentConnectivityStatus] as? ConnectivityState
let previous = userInfo[CompactBlockProcessorNotificationKey.previousConnectivityStatus] as? ConnectionState,
let current = userInfo[CompactBlockProcessorNotificationKey.currentConnectivityStatus] as? ConnectionState
else {
LoggerProxy.error(
"Found \(Notification.Name.blockProcessorConnectivityStateChanged) but lacks dictionary information." +
@ -315,17 +315,16 @@ public class SDKSynchronizer: Synchronizer {
return
}
let currentState = ConnectionState(current)
NotificationSender.default.post(
name: .synchronizerConnectionStateChanged,
object: self,
userInfo: [
NotificationKeys.previousConnectionState: ConnectionState(previous),
NotificationKeys.currentConnectionState: currentState
NotificationKeys.previousConnectionState: previous,
NotificationKeys.currentConnectionState: current
]
)
connectionState = currentState
connectionState = current
}
@objc func transactionsFound(_ notification: Notification) {
@ -890,24 +889,6 @@ extension SDKSynchronizer {
}
}
import GRPC
extension ConnectionState {
init(_ connectivityState: ConnectivityState) {
switch connectivityState {
case .connecting:
self = .connecting
case .idle:
self = .idle
case .ready:
self = .online
case .shutdown:
self = .shutdown
case .transientFailure:
self = .reconnecting
}
}
}
private struct NullEnhancementProgress: EnhancementProgress {
var totalTransactions: Int { 0 }
var enhancedTransactions: Int { 0 }

View File

@ -36,7 +36,6 @@ class AdvancedReOrgTests: XCTestCase {
self.coordinator = try TestCoordinator(
seed: seedPhrase,
walletBirthday: birthday + 50, // don't use an exact birthday, users never do.
channelProvider: ChannelProvider(),
network: network
)
try coordinator.reset(saplingActivation: 663150, branchID: self.branchID, chainName: self.chainName)

View File

@ -33,7 +33,6 @@ class BalanceTests: XCTestCase {
self.coordinator = try TestCoordinator(
seed: self.seedPhrase,
walletBirthday: self.birthday,
channelProvider: ChannelProvider(),
network: self.network
)

View File

@ -28,7 +28,7 @@ class BlockDownloaderTests: XCTestCase {
override func setUpWithError() throws {
try super.setUpWithError()
service = LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.default)
service = LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.default, connectionStateChange: { _, _ in }).make()
try self.testFileManager.createDirectory(at: self.testTempDirectory, withIntermediateDirectories: false)
storage = FSCompactBlockRepository(
cacheDirectory: testTempDirectory,

View File

@ -36,7 +36,6 @@ class DarksideSanityCheckTests: XCTestCase {
self.coordinator = try TestCoordinator(
seed: self.seedPhrase,
walletBirthday: self.birthday,
channelProvider: ChannelProvider(),
network: self.network
)

View File

@ -33,7 +33,6 @@ final class InternalStateConsistencyTests: XCTestCase {
self.coordinator = try TestCoordinator(
seed: seedPhrase,
walletBirthday: birthday + 50, // don't use an exact birthday, users never do.
channelProvider: ChannelProvider(),
network: network
)
try coordinator.reset(saplingActivation: 663150, branchID: self.branchID, chainName: self.chainName)

View File

@ -34,7 +34,6 @@ class PendingTransactionUpdatesTest: XCTestCase {
self.coordinator = try TestCoordinator(
seed: self.seedPhrase,
walletBirthday: self.birthday,
channelProvider: ChannelProvider(),
network: self.network
)

View File

@ -57,7 +57,6 @@ class ReOrgTests: XCTestCase {
self.coordinator = try TestCoordinator(
seed: self.seedPhrase,
walletBirthday: self.birthday,
channelProvider: ChannelProvider(),
network: self.network
)

View File

@ -37,7 +37,6 @@ class RewindRescanTests: XCTestCase {
self.coordinator = try TestCoordinator(
seed: self.seedPhrase,
walletBirthday: self.birthday,
channelProvider: ChannelProvider(),
network: self.network
)

View File

@ -35,7 +35,6 @@ class ShieldFundsTests: XCTestCase {
self.coordinator = try TestCoordinator(
seed: seedPhrase,
walletBirthday: birthday,
channelProvider: ChannelProvider(),
network: network
)
try coordinator.reset(saplingActivation: birthday, branchID: self.branchID, chainName: self.chainName)

View File

@ -38,7 +38,6 @@ class SychronizerDarksideTests: XCTestCase {
self.coordinator = try TestCoordinator(
seed: self.seedPhrase,
walletBirthday: self.birthday,
channelProvider: ChannelProvider(),
network: self.network
)

View File

@ -36,7 +36,6 @@ final class SynchronizerTests: XCTestCase {
self.coordinator = try TestCoordinator(
seed: self.seedPhrase,
walletBirthday: self.birthday + 50, // don't use an exact birthday, users never do.
channelProvider: ChannelProvider(),
network: self.network
)

View File

@ -35,7 +35,6 @@ class Z2TReceiveTests: XCTestCase {
self.coordinator = try TestCoordinator(
seed: self.seedPhrase,
walletBirthday: self.birthday,
channelProvider: ChannelProvider(),
network: self.network
)

View File

@ -67,12 +67,8 @@ class BlockScanTests: XCTestCase {
XCTAssertNoThrow(try rustWelding.initDataDb(dbData: dataDbURL, seed: nil, networkType: network.networkType))
let service = LightWalletGRPCService(
endpoint: LightWalletEndpoint(
address: "lightwalletd.testnet.electriccoin.co",
port: 9067
)
)
let endpoint = LightWalletEndpoint(address: "lightwalletd.testnet.electriccoin.co", port: 9067)
let service = LightWalletServiceFactory(endpoint: endpoint, connectionStateChange: { _, _ in }).make()
let blockCount = 100
let range = network.constants.saplingActivationHeight ... network.constants.saplingActivationHeight + blockCount
@ -90,6 +86,7 @@ class BlockScanTests: XCTestCase {
)
try fsBlockRepository.create()
let processorConfig = CompactBlockProcessor.Configuration(
fsBlockCacheRoot: fsDbRootURL,
dataDb: dataDbURL,
@ -169,7 +166,7 @@ class BlockScanTests: XCTestCase {
networkType: network.networkType
)
let service = LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.eccTestnet)
let service = LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.eccTestnet, connectionStateChange: { _, _ in }).make()
let fsDbRootURL = self.testTempDirectory

View File

@ -31,14 +31,15 @@ class BlockStreamingTest: XCTestCase {
}
func testStream() async throws {
let service = LightWalletGRPCService(
host: LightWalletEndpointBuilder.eccTestnet.host,
let endpoint = LightWalletEndpoint(
address: LightWalletEndpointBuilder.eccTestnet.host,
port: 9067,
secure: true,
singleCallTimeout: 1000,
streamingCallTimeout: 100000
singleCallTimeoutInMillis: 1000,
streamingCallTimeoutInMillis: 100000
)
let service = LightWalletServiceFactory(endpoint: endpoint, connectionStateChange: { _, _ in }).make()
let latestHeight = try service.latestBlockHeight()
let startHeight = latestHeight - 100_000
@ -59,13 +60,14 @@ class BlockStreamingTest: XCTestCase {
}
func testStreamCancellation() async throws {
let service = LightWalletGRPCService(
host: LightWalletEndpointBuilder.eccTestnet.host,
let endpoint = LightWalletEndpoint(
address: LightWalletEndpointBuilder.eccTestnet.host,
port: 9067,
secure: true,
singleCallTimeout: 10000,
streamingCallTimeout: 10000
singleCallTimeoutInMillis: 10000,
streamingCallTimeoutInMillis: 10000
)
let service = LightWalletServiceFactory(endpoint: endpoint, connectionStateChange: { _, _ in }).make()
let realRustBackend = ZcashRustBackend.self
@ -118,13 +120,14 @@ class BlockStreamingTest: XCTestCase {
}
func testStreamTimeout() async throws {
let service = LightWalletGRPCService(
host: LightWalletEndpointBuilder.eccTestnet.host,
let endpoint = LightWalletEndpoint(
address: LightWalletEndpointBuilder.eccTestnet.host,
port: 9067,
secure: true,
singleCallTimeout: 1000,
streamingCallTimeout: 1000
singleCallTimeoutInMillis: 1000,
streamingCallTimeoutInMillis: 1000
)
let service = LightWalletServiceFactory(endpoint: endpoint, connectionStateChange: { _, _ in }).make()
let realRustBackend = ZcashRustBackend.self

View File

@ -34,9 +34,10 @@ class CompactBlockProcessorTests: XCTestCase {
try self.testFileManager.createDirectory(at: self.testTempDirectory, withIntermediateDirectories: false)
logger = OSLogger(logLevel: .debug)
let liveService = LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.eccTestnet, connectionStateChange: { _, _ in }).make()
let service = MockLightWalletService(
latestBlockHeight: mockLatestHeight,
service: LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.eccTestnet)
service: liveService
)
let branchID = try ZcashRustBackend.consensusBranchIdFor(height: Int32(mockLatestHeight), networkType: network.networkType)
service.mockLightDInfo = LightdInfo.with({ info in

View File

@ -37,10 +37,12 @@ class CompactBlockReorgTests: XCTestCase {
try self.testFileManager.createDirectory(at: self.testTempDirectory, withIntermediateDirectories: false)
logger = OSLogger(logLevel: .debug)
let liveService = LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.eccTestnet, connectionStateChange: { _, _ in }).make()
let service = MockLightWalletService(
latestBlockHeight: mockLatestHeight,
service: LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.eccTestnet)
service: liveService
)
let branchID = try ZcashRustBackend.consensusBranchIdFor(height: Int32(mockLatestHeight), networkType: network.networkType)
service.mockLightDInfo = LightdInfo.with { info in
info.blockHeight = UInt64(mockLatestHeight)

View File

@ -33,7 +33,7 @@ class DownloadTests: XCTestCase {
}
func testSingleDownload() async throws {
let service = LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.eccTestnet)
let service = LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.eccTestnet, connectionStateChange: { _, _ in }).make()
let realRustBackend = ZcashRustBackend.self

View File

@ -16,13 +16,11 @@ class LightWalletServiceTests: XCTestCase {
let network: ZcashNetwork = ZcashNetworkBuilder.network(for: .testnet)
var service: LightWalletService!
var channel: Channel!
override func setUp() {
// Put setup code here. This method is called before the invocation of each test method in the class.
super.setUp()
channel = ChannelProvider().channel()
service = LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.eccTestnet)
service = LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.eccTestnet, connectionStateChange: { _, _ in }).make()
}
// FIXME: [#721] check whether this test is still valid on in memory lwd implementation, https://github.com/zcash/ZcashLightClientKit/issues/721

View File

@ -32,7 +32,7 @@ class BlockBatchValidationTests: XCTestCase {
let network = ZcashNetworkBuilder.network(for: .mainnet)
let service = MockLightWalletService(
latestBlockHeight: 1210000,
service: LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.default)
service: LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.default, connectionStateChange: { _, _ in }).make()
)
let realRustBackend = ZcashRustBackend.self
@ -101,7 +101,7 @@ class BlockBatchValidationTests: XCTestCase {
let network = ZcashNetworkBuilder.network(for: .mainnet)
let service = MockLightWalletService(
latestBlockHeight: 1210000,
service: LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.default)
service: LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.default, connectionStateChange: { _, _ in }).make()
)
let realRustBackend = ZcashRustBackend.self
@ -170,7 +170,7 @@ class BlockBatchValidationTests: XCTestCase {
let network = ZcashNetworkBuilder.network(for: .testnet)
let service = MockLightWalletService(
latestBlockHeight: 1210000,
service: LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.default)
service: LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.default, connectionStateChange: { _, _ in }).make()
)
let realRustBackend = ZcashRustBackend.self
@ -239,7 +239,7 @@ class BlockBatchValidationTests: XCTestCase {
let network = ZcashNetworkBuilder.network(for: .mainnet)
let service = MockLightWalletService(
latestBlockHeight: 1210000,
service: LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.default)
service: LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.default, connectionStateChange: { _, _ in }).make()
)
let realRustBackend = ZcashRustBackend.self
@ -314,7 +314,7 @@ class BlockBatchValidationTests: XCTestCase {
let expectedLatestHeight = BlockHeight(1210000)
let service = MockLightWalletService(
latestBlockHeight: expectedLatestHeight,
service: LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.default)
service: LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.default, connectionStateChange: { _, _ in }).make()
)
let expectedStoredLatestHeight = BlockHeight(1220000)
let expectedResult = CompactBlockProcessor.NextState.wait(
@ -398,7 +398,7 @@ class BlockBatchValidationTests: XCTestCase {
let expectedLatestHeight = BlockHeight(1230000)
let service = MockLightWalletService(
latestBlockHeight: expectedLatestHeight,
service: LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.default)
service: LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.default, connectionStateChange: { _, _ in }).make()
)
let expectedStoreLatestHeight = BlockHeight(1220000)
let walletBirthday = BlockHeight(1210000)
@ -487,7 +487,7 @@ class BlockBatchValidationTests: XCTestCase {
let expectedLatestHeight = BlockHeight(1230000)
let service = MockLightWalletService(
latestBlockHeight: expectedLatestHeight,
service: LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.default)
service: LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.default, connectionStateChange: { _, _ in }).make()
)
let expectedStoreLatestHeight = BlockHeight(1230000)
let walletBirthday = BlockHeight(1210000)

View File

@ -37,7 +37,7 @@ class CompactBlockProcessorOfflineTests: XCTestCase {
let service = MockLightWalletService(
latestBlockHeight: 690000,
service: LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.eccTestnet)
service: LightWalletServiceFactory(endpoint: LightWalletEndpointBuilder.eccTestnet, connectionStateChange: { _, _ in }).make()
)
let storage = FSCompactBlockRepository(

View File

@ -54,7 +54,7 @@ class TransactionRepositoryTests: XCTestCase {
var transactions: [ZcashTransaction.Overview]!
XCTAssertNoThrow(
try {
transactions = try self.transactionRepository.find(in: BlockRange(startHeight: 663218, endHeight: 663974), limit: 3, kind: .received)
transactions = try self.transactionRepository.find(in: 663218...663974, limit: 3, kind: .received)
}()
)

View File

@ -39,16 +39,16 @@ enum DarksideDataset: String {
class DarksideWalletService: LightWalletService {
var channel: Channel
var service: LightWalletGRPCService
var service: LightWalletService
var darksideService: DarksideStreamerClient
init(endpoint: LightWalletEndpoint) {
self.channel = ChannelProvider().channel()
self.service = LightWalletGRPCService(endpoint: endpoint)
self.service = LightWalletServiceFactory(endpoint: endpoint, connectionStateChange: { _, _ in }).make()
self.darksideService = DarksideStreamerClient(channel: channel)
}
init(service: LightWalletGRPCService) {
init(service: LightWalletService) {
self.channel = ChannelProvider().channel()
self.darksideService = DarksideStreamerClient(channel: channel)
self.service = service

View File

@ -67,7 +67,6 @@ class ZcashConsoleFakeStorage: CompactBlockRepository {
}
}
import GRPC
struct SandblastSimulator {
/// Creates an array of Zcash CompactBlock from a mainnet sandblasted block of 500K bytes
/// this is not good for syncing but for performance benchmarking of block storage.

View File

@ -185,7 +185,7 @@ extension MockTransactionRepository: TransactionRepository {
throw MockTransactionRepositoryError.notImplemented
}
func find(in range: BlockRange, limit: Int, kind: TransactionKind) throws -> [ZcashTransaction.Overview] {
func find(in range: CompactBlockRange, limit: Int, kind: TransactionKind) throws -> [ZcashTransaction.Overview] {
throw MockTransactionRepositoryError.notImplemented
}

View File

@ -11,7 +11,7 @@ import GRPC
import SwiftProtobuf
@testable import ZcashLightClientKit
// swiftlint:disable function_parameter_count identifier_name type_body_length
// swiftlint:disable function_parameter_count identifier_name
class AwfulLightWalletService: MockLightWalletService {
override func latestBlockHeight() throws -> BlockHeight {
throw LightWalletServiceError.criticalError

View File

@ -39,7 +39,6 @@ class TestCoordinator {
var errorHandler: ((Error?) -> Void)?
var spendingKey: UnifiedSpendingKey
var birthday: BlockHeight
var channelProvider: ChannelProvider
var synchronizer: SDKSynchronizer
var service: DarksideWalletService
var spendingKeys: [UnifiedSpendingKey]?
@ -48,7 +47,6 @@ class TestCoordinator {
convenience init(
seed: String,
walletBirthday: BlockHeight,
channelProvider: ChannelProvider,
network: ZcashNetwork
) throws {
let derivationTool = DerivationTool(networkType: network.networkType)
@ -64,7 +62,6 @@ class TestCoordinator {
spendingKey: spendingKey,
unifiedFullViewingKey: ufvk,
walletBirthday: walletBirthday,
channelProvider: channelProvider,
network: network
)
}
@ -73,25 +70,24 @@ class TestCoordinator {
spendingKey: UnifiedSpendingKey,
unifiedFullViewingKey: UnifiedFullViewingKey,
walletBirthday: BlockHeight,
channelProvider: ChannelProvider,
network: ZcashNetwork
) throws {
XCTestCase.wait { await InternalSyncProgress(storage: UserDefaults.standard).rewind(to: 0) }
self.spendingKey = spendingKey
self.birthday = walletBirthday
self.channelProvider = channelProvider
self.databases = TemporaryDbBuilder.build()
self.network = network
self.service = DarksideWalletService(
service: LightWalletGRPCService(
host: Constants.address,
port: 9067,
secure: false,
singleCallTimeout: 10000,
streamingCallTimeout: 1000000
)
let endpoint = LightWalletEndpoint(
address: Constants.address,
port: 9067,
secure: false,
singleCallTimeoutInMillis: 10000,
streamingCallTimeoutInMillis: 1000000
)
let liveService = LightWalletServiceFactory(endpoint: endpoint, connectionStateChange: { _, _ in }).make()
self.service = DarksideWalletService(service: liveService)
let realRustBackend = ZcashRustBackend.self