From 312a169911f5425420a284c12eac3865183eb5e0 Mon Sep 17 00:00:00 2001 From: Francisco Gindre Date: Mon, 6 Apr 2020 12:54:31 -0300 Subject: [PATCH] Feature/reorg testing (#104) * Add DarksideWalletD class * darkside walletd reorg tests * Removed hardcoded stuff from tests * add parameters to support other kinds of reorgs * Basic reorg test in place * Integration to DarksideWalletD and tests * tests improvements * Implement reorg testing. fix error throwing --- Example/ZcashLightClientSample/Podfile.lock | 6 +- .../Processor/CompactBlockProcessor.swift | 6 +- .../Rust/ZcashRustBackend.swift | 6 +- .../Rust/ZcashRustBackendWelding.swift | 4 +- .../Service/ProtoBuf/compact_formats.pb.swift | 14 +- .../ProtoBuf/proto/compact_formats.proto | 10 +- .../Service/ProtoBuf/proto/service.proto | 67 ++- .../Service/ProtoBuf/service.grpc.swift | 102 ++++ .../Service/ProtoBuf/service.pb.swift | 512 ++++++++++++++++-- .../WalletBirthday+saplingtree.stencil | 9 +- .../UIKit/Synchronizer/SDKSynchronizer.swift | 3 +- .../BlockDownloaderTests.swift | 2 +- .../CompactBlockProcessorTests.swift | 15 +- .../CompactBlockReorgTests.swift | 12 +- ZcashLightClientKitTests/ReOrgTests.swift | 243 +++++++++ .../proto/darkside.grpc.swift | 106 ++++ .../proto/darkside.pb.swift | 77 +++ ZcashLightClientKitTests/proto/darkside.proto | 25 + .../utils/DarkSideWalletService.swift | 62 +++ .../utils/SampleLogger.swift | 61 +++ ZcashLightClientKitTests/utils/Stubs.swift | 4 + .../utils/Tests+Utils.swift | 4 +- 22 files changed, 1248 insertions(+), 102 deletions(-) create mode 100644 ZcashLightClientKitTests/ReOrgTests.swift create mode 100644 ZcashLightClientKitTests/proto/darkside.grpc.swift create mode 100644 ZcashLightClientKitTests/proto/darkside.pb.swift create mode 100644 ZcashLightClientKitTests/proto/darkside.proto create mode 100644 ZcashLightClientKitTests/utils/DarkSideWalletService.swift create mode 100644 ZcashLightClientKitTests/utils/SampleLogger.swift diff --git a/Example/ZcashLightClientSample/Podfile.lock b/Example/ZcashLightClientSample/Podfile.lock index a90d8797..71094e27 100644 --- a/Example/ZcashLightClientSample/Podfile.lock +++ b/Example/ZcashLightClientSample/Podfile.lock @@ -30,10 +30,10 @@ PODS: - gRPC-Core (~> 1.23.0) - SwiftProtobuf (~> 1.7.0) - SwiftProtobuf (1.7.0) - - ZcashLightClientKit (0.2.1): + - ZcashLightClientKit (0.3.1): - SQLite.swift (~> 0.12.2) - SwiftGRPC (~> 0.10.0) - - ZcashLightClientKit/Tests (0.2.1): + - ZcashLightClientKit/Tests (0.3.1): - SQLite.swift (~> 0.12.2) - SwiftGRPC (~> 0.10.0) @@ -72,7 +72,7 @@ SPEC CHECKSUMS: SQLite.swift: d2b4642190917051ce6bd1d49aab565fe794eea3 SwiftGRPC: f8fcfecb547c96cc6913de619f95fa3cd09838ee SwiftProtobuf: 4fd9645e69b72cbae6ec8da5be0cdd20ca6565dd - ZcashLightClientKit: 8f195725d2ec3d579c78b228a0d3a384e21f56b9 + ZcashLightClientKit: 24b01a37977bddfaadc7746acef40ad209bae84a PODFILE CHECKSUM: 5a1fb98512fa179a4e83d67d14dd402f6d129a4d diff --git a/ZcashLightClientKit/Block/Processor/CompactBlockProcessor.swift b/ZcashLightClientKit/Block/Processor/CompactBlockProcessor.swift index 3edbcc27..1c1b901c 100644 --- a/ZcashLightClientKit/Block/Processor/CompactBlockProcessor.swift +++ b/ZcashLightClientKit/Block/Processor/CompactBlockProcessor.swift @@ -204,7 +204,7 @@ public class CompactBlockProcessor { } var shouldStart: Bool { switch self.state { - case .stopped, .synced, .error(_): + case .stopped, .synced, .error: return maxAttemptsReached default: return false @@ -276,7 +276,6 @@ public class CompactBlockProcessor { } - /** Stops the CompactBlockProcessor @@ -408,7 +407,6 @@ public class CompactBlockProcessor { self?.state = .scanning } - scanBlocksOperation.completionHandler = { [weak self] (finished, cancelled) in guard !cancelled else { LoggerProxy.debug("Warning: scanBlocksOperation operation cancelled") @@ -571,7 +569,7 @@ public class CompactBlockProcessor { self.retryAttempts = self.retryAttempts + 1 self.processingError = error switch self.state { - case .error(_): + case .error: notifyError(error) default: break diff --git a/ZcashLightClientKit/Rust/ZcashRustBackend.swift b/ZcashLightClientKit/Rust/ZcashRustBackend.swift index 54bb67d0..0837397b 100644 --- a/ZcashLightClientKit/Rust/ZcashRustBackend.swift +++ b/ZcashLightClientKit/Rust/ZcashRustBackend.swift @@ -15,6 +15,8 @@ class ZcashRustBackend: ZcashRustBackendWelding { zcashlc_clear_last_error() if message.contains("couldn't load Sapling spend parameters") { return RustWeldingError.saplingSpendParametersNotFound + } else if message.contains("is not empty") { + return RustWeldingError.dataDbNotEmpty } return RustWeldingError.genericError(message: message) } @@ -83,7 +85,7 @@ class ZcashRustBackend: ZcashRustBackendWelding { let dbData = dbData.osStr() guard zcashlc_init_blocks_table(dbData.0, dbData.1, height, [CChar](hash.utf8CString), time, [CChar](saplingTree.utf8CString)) != 0 else { if let error = lastError() { - throw throwDataDbError(error) + throw error } throw RustWeldingError.dataDbInitFailed(message: "Unknown Error") } @@ -167,7 +169,7 @@ class ZcashRustBackend: ZcashRustBackendWelding { return nil } - let derived = String(validatingUTF8: extsk) + let derived = String(validatingUTF8: extsk) zcashlc_string_free(extsk) return derived diff --git a/ZcashLightClientKit/Rust/ZcashRustBackendWelding.swift b/ZcashLightClientKit/Rust/ZcashRustBackendWelding.swift index cfc48067..14a6be45 100644 --- a/ZcashLightClientKit/Rust/ZcashRustBackendWelding.swift +++ b/ZcashLightClientKit/Rust/ZcashRustBackendWelding.swift @@ -21,11 +21,11 @@ public struct ZcashRustBackendWeldingConstants { public protocol ZcashRustBackendWelding { /** - gets the latest error if available + gets the latest error if available. Clear the existing error */ static func lastError() -> RustWeldingError? /** - gets the latest error message from librustzcash + gets the latest error message from librustzcash. Does not clear existing error */ static func getLastError() -> String? /** diff --git a/ZcashLightClientKit/Service/ProtoBuf/compact_formats.pb.swift b/ZcashLightClientKit/Service/ProtoBuf/compact_formats.pb.swift index 104edc98..dea79fe2 100644 --- a/ZcashLightClientKit/Service/ProtoBuf/compact_formats.pb.swift +++ b/ZcashLightClientKit/Service/ProtoBuf/compact_formats.pb.swift @@ -3,16 +3,20 @@ // Generated by the Swift generator plugin for the protocol buffer compiler. // Source: compact_formats.proto // -// For information on using the generated types, please see the documentation: +// For information on using the generated types, please see the documenation: // 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 +// Please ensure that your 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 {} @@ -51,14 +55,14 @@ struct CompactBlock { init() {} } +/// Index and hash will allow the receiver to call out to chain +/// explorers or other data structures to retrieve more information +/// about this transaction. struct CompactTx { // 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. - /// Index and hash will allow the receiver to call out to chain - /// explorers or other data structures to retrieve more information - /// about this transaction. var index: UInt64 = 0 var hash: Data = SwiftProtobuf.Internal.emptyData diff --git a/ZcashLightClientKit/Service/ProtoBuf/proto/compact_formats.proto b/ZcashLightClientKit/Service/ProtoBuf/proto/compact_formats.proto index e20c05a5..69a404ca 100644 --- a/ZcashLightClientKit/Service/ProtoBuf/proto/compact_formats.proto +++ b/ZcashLightClientKit/Service/ProtoBuf/proto/compact_formats.proto @@ -1,3 +1,7 @@ +// 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"; @@ -19,10 +23,10 @@ message CompactBlock { repeated CompactTx vtx = 7; // compact transactions from this block } +// Index and hash will allow the receiver to call out to chain +// explorers or other data structures to retrieve more information +// about this transaction. message CompactTx { - // Index and hash will allow the receiver to call out to chain - // explorers or other data structures to retrieve more information - // about this transaction. uint64 index = 1; bytes hash = 2; diff --git a/ZcashLightClientKit/Service/ProtoBuf/proto/service.proto b/ZcashLightClientKit/Service/ProtoBuf/proto/service.proto index d4c5135d..6a77c844 100644 --- a/ZcashLightClientKit/Service/ProtoBuf/proto/service.proto +++ b/ZcashLightClientKit/Service/ProtoBuf/proto/service.proto @@ -1,3 +1,7 @@ +// 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"; @@ -5,15 +9,14 @@ option swift_prefix = ""; import "compact_formats.proto"; // A BlockID message contains identifiers to select a block: a height or a -// hash. If the hash is present it takes precedence. +// hash. Specification by hash is not implemented, but may be in the future. message BlockID { uint64 height = 1; bytes hash = 2; } -// BlockRange technically allows ranging from hash to hash etc but this is not -// currently intended for support, though there is no reason you couldn't do -// it. Further permutations are left as an exercise. +// BlockRange specifies a series of blocks from start to end inclusive. +// Both BlockIDs must be heights; specification by hash is not yet supported. message BlockRange { BlockID start = 1; BlockID end = 2; @@ -21,29 +24,81 @@ message BlockRange { // A TxFilter contains the information needed to identify a particular // transaction: either a block and an index, or a direct transaction hash. +// Currently, only specification by hash is supported. message TxFilter { BlockID block = 1; uint64 index = 2; bytes hash = 3; } -// RawTransaction contains the complete transaction data. +// RawTransaction contains the complete transaction data. It also optionally includes +// the block height in which the transaction was included message RawTransaction { bytes data = 1; + uint64 height = 2; } +// A SendResponse encodes an error code and a string. It is currently used +// only by SendTransaction(). If error code is zero, the operation was +// successful; if non-zero, it and the message specify the failure. message SendResponse { int32 errorCode = 1; string errorMessage = 2; } -// Empty placeholder. Someday we may want to specify e.g. a particular chain fork. +// Chainspec is a placeholder to allow specification of a particular chain fork. message ChainSpec {} +// Empty is for gRPCs that take no arguments, currently only GetLightdInfo. +message Empty {} + +// LightdInfo returns various information about this lightwalletd instance +// and the state of the blockchain. +message LightdInfo { + string version = 1; + string vendor = 2; + bool taddrSupport = 3; + string chainName = 4; + uint64 saplingActivationHeight = 5; + string consensusBranchId = 6; + uint64 blockHeight = 7; +} + +// TransparentAddressBlockFilter restricts the results to the given address +// or block range. +message TransparentAddressBlockFilter { + string address = 1; + BlockRange range = 2; +} + +// Duration is currently used only for testing, so that the Ping rpc +// can simulate a delay, to create many simultaneous connections. Units +// are microseconds. +message Duration { + int64 intervalUs = 1; +} + +// PingResponse is used to indicate concurrency, how many Ping rpcs +// are executing upon entry and upon exit (after the delay). +message PingResponse { + int64 entry = 1; + int64 exit = 2; +} + service CompactTxStreamer { + // Compact Blocks rpc GetLatestBlock(ChainSpec) returns (BlockID) {} rpc GetBlock(BlockID) returns (CompactBlock) {} rpc GetBlockRange(BlockRange) returns (stream CompactBlock) {} + + // Transactions rpc GetTransaction(TxFilter) returns (RawTransaction) {} rpc SendTransaction(RawTransaction) returns (SendResponse) {} + + // t-Address support + rpc GetAddressTxids(TransparentAddressBlockFilter) returns (stream RawTransaction) {} + + // Misc + rpc GetLightdInfo(Empty) returns (LightdInfo) {} + rpc Ping(Duration) returns (PingResponse) {} } diff --git a/ZcashLightClientKit/Service/ProtoBuf/service.grpc.swift b/ZcashLightClientKit/Service/ProtoBuf/service.grpc.swift index 81172e27..269e8b03 100644 --- a/ZcashLightClientKit/Service/ProtoBuf/service.grpc.swift +++ b/ZcashLightClientKit/Service/ProtoBuf/service.grpc.swift @@ -65,6 +65,34 @@ fileprivate final class CompactTxStreamerSendTransactionCallBase: ClientCallUnar override class var method: String { return "/cash.z.wallet.sdk.rpc.CompactTxStreamer/SendTransaction" } } +internal protocol CompactTxStreamerGetAddressTxidsCall: ClientCallServerStreaming { + /// Do not call this directly, call `receive()` in the protocol extension below instead. + func _receive(timeout: DispatchTime) throws -> RawTransaction? + /// Call this to wait for a result. Nonblocking. + func receive(completion: @escaping (ResultOrRPCError) -> Void) throws +} + +internal extension CompactTxStreamerGetAddressTxidsCall { + /// Call this to wait for a result. Blocking. + func receive(timeout: DispatchTime = .distantFuture) throws -> RawTransaction? { return try self._receive(timeout: timeout) } +} + +fileprivate final class CompactTxStreamerGetAddressTxidsCallBase: ClientCallServerStreamingBase, CompactTxStreamerGetAddressTxidsCall { + override class var method: String { return "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetAddressTxids" } +} + +internal protocol CompactTxStreamerGetLightdInfoCall: ClientCallUnary {} + +fileprivate final class CompactTxStreamerGetLightdInfoCallBase: ClientCallUnaryBase, CompactTxStreamerGetLightdInfoCall { + override class var method: String { return "/cash.z.wallet.sdk.rpc.CompactTxStreamer/GetLightdInfo" } +} + +internal protocol CompactTxStreamerPingCall: ClientCallUnary {} + +fileprivate final class CompactTxStreamerPingCallBase: ClientCallUnaryBase, CompactTxStreamerPingCall { + override class var method: String { return "/cash.z.wallet.sdk.rpc.CompactTxStreamer/Ping" } +} + /// Instantiate CompactTxStreamerServiceClient, then call methods of this protocol to make API calls. internal protocol CompactTxStreamerService: ServiceClient { @@ -97,6 +125,23 @@ internal protocol CompactTxStreamerService: ServiceClient { @discardableResult func sendTransaction(_ request: RawTransaction, metadata customMetadata: Metadata, completion: @escaping (SendResponse?, CallResult) -> Void) throws -> CompactTxStreamerSendTransactionCall + /// Asynchronous. Server-streaming. + /// Send the initial message. + /// Use methods on the returned object to get streamed responses. + func getAddressTxids(_ request: TransparentAddressBlockFilter, metadata customMetadata: Metadata, completion: ((CallResult) -> Void)?) throws -> CompactTxStreamerGetAddressTxidsCall + + /// Synchronous. Unary. + func getLightdInfo(_ request: Empty, metadata customMetadata: Metadata) throws -> LightdInfo + /// Asynchronous. Unary. + @discardableResult + func getLightdInfo(_ request: Empty, metadata customMetadata: Metadata, completion: @escaping (LightdInfo?, CallResult) -> Void) throws -> CompactTxStreamerGetLightdInfoCall + + /// Synchronous. Unary. + func ping(_ request: Duration, metadata customMetadata: Metadata) throws -> PingResponse + /// Asynchronous. Unary. + @discardableResult + func ping(_ request: Duration, metadata customMetadata: Metadata, completion: @escaping (PingResponse?, CallResult) -> Void) throws -> CompactTxStreamerPingCall + } internal extension CompactTxStreamerService { @@ -145,6 +190,31 @@ internal extension CompactTxStreamerService { return try self.sendTransaction(request, metadata: self.metadata, completion: completion) } + /// Asynchronous. Server-streaming. + func getAddressTxids(_ request: TransparentAddressBlockFilter, completion: ((CallResult) -> Void)?) throws -> CompactTxStreamerGetAddressTxidsCall { + return try self.getAddressTxids(request, metadata: self.metadata, completion: completion) + } + + /// Synchronous. Unary. + func getLightdInfo(_ request: Empty) throws -> LightdInfo { + return try self.getLightdInfo(request, metadata: self.metadata) + } + /// Asynchronous. Unary. + @discardableResult + func getLightdInfo(_ request: Empty, completion: @escaping (LightdInfo?, CallResult) -> Void) throws -> CompactTxStreamerGetLightdInfoCall { + return try self.getLightdInfo(request, metadata: self.metadata, completion: completion) + } + + /// Synchronous. Unary. + func ping(_ request: Duration) throws -> PingResponse { + return try self.ping(request, metadata: self.metadata) + } + /// Asynchronous. Unary. + @discardableResult + func ping(_ request: Duration, completion: @escaping (PingResponse?, CallResult) -> Void) throws -> CompactTxStreamerPingCall { + return try self.ping(request, metadata: self.metadata, completion: completion) + } + } internal final class CompactTxStreamerServiceClient: ServiceClientBase, CompactTxStreamerService { @@ -204,5 +274,37 @@ internal final class CompactTxStreamerServiceClient: ServiceClientBase, CompactT .start(request: request, metadata: customMetadata, completion: completion) } + /// Asynchronous. Server-streaming. + /// Send the initial message. + /// Use methods on the returned object to get streamed responses. + internal func getAddressTxids(_ request: TransparentAddressBlockFilter, metadata customMetadata: Metadata, completion: ((CallResult) -> Void)?) throws -> CompactTxStreamerGetAddressTxidsCall { + return try CompactTxStreamerGetAddressTxidsCallBase(channel) + .start(request: request, metadata: customMetadata, completion: completion) + } + + /// Synchronous. Unary. + internal func getLightdInfo(_ request: Empty, metadata customMetadata: Metadata) throws -> LightdInfo { + return try CompactTxStreamerGetLightdInfoCallBase(channel) + .run(request: request, metadata: customMetadata) + } + /// Asynchronous. Unary. + @discardableResult + internal func getLightdInfo(_ request: Empty, metadata customMetadata: Metadata, completion: @escaping (LightdInfo?, CallResult) -> Void) throws -> CompactTxStreamerGetLightdInfoCall { + return try CompactTxStreamerGetLightdInfoCallBase(channel) + .start(request: request, metadata: customMetadata, completion: completion) + } + + /// Synchronous. Unary. + internal func ping(_ request: Duration, metadata customMetadata: Metadata) throws -> PingResponse { + return try CompactTxStreamerPingCallBase(channel) + .run(request: request, metadata: customMetadata) + } + /// Asynchronous. Unary. + @discardableResult + internal func ping(_ request: Duration, metadata customMetadata: Metadata, completion: @escaping (PingResponse?, CallResult) -> Void) throws -> CompactTxStreamerPingCall { + return try CompactTxStreamerPingCallBase(channel) + .start(request: request, metadata: customMetadata, completion: completion) + } + } diff --git a/ZcashLightClientKit/Service/ProtoBuf/service.pb.swift b/ZcashLightClientKit/Service/ProtoBuf/service.pb.swift index 2282bfde..b67a1c6d 100644 --- a/ZcashLightClientKit/Service/ProtoBuf/service.pb.swift +++ b/ZcashLightClientKit/Service/ProtoBuf/service.pb.swift @@ -3,16 +3,20 @@ // Generated by the Swift generator plugin for the protocol buffer compiler. // Source: service.proto // -// For information on using the generated types, please see the documentation: +// For information on using the generated types, please see the documenation: // 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 +// Please ensure that your 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 {} @@ -20,7 +24,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP } /// A BlockID message contains identifiers to select a block: a height or a -/// hash. If the hash is present it takes precedence. +/// hash. Specification by hash is not implemented, but may be in the future. struct BlockID { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for @@ -35,68 +39,74 @@ struct BlockID { init() {} } -/// BlockRange technically allows ranging from hash to hash etc but this is not -/// currently intended for support, though there is no reason you couldn't do -/// it. Further permutations are left as an exercise. +/// BlockRange specifies a series of blocks from start to end inclusive. +/// Both BlockIDs must be heights; specification by hash is not yet supported. struct BlockRange { // 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 start: BlockID { - get {return _start ?? BlockID()} - set {_start = newValue} + get {return _storage._start ?? BlockID()} + set {_uniqueStorage()._start = newValue} } /// Returns true if `start` has been explicitly set. - var hasStart: Bool {return self._start != nil} + var hasStart: Bool {return _storage._start != nil} /// Clears the value of `start`. Subsequent reads from it will return its default value. - mutating func clearStart() {self._start = nil} + mutating func clearStart() {_uniqueStorage()._start = nil} var end: BlockID { - get {return _end ?? BlockID()} - set {_end = newValue} + get {return _storage._end ?? BlockID()} + set {_uniqueStorage()._end = newValue} } /// Returns true if `end` has been explicitly set. - var hasEnd: Bool {return self._end != nil} + var hasEnd: Bool {return _storage._end != nil} /// Clears the value of `end`. Subsequent reads from it will return its default value. - mutating func clearEnd() {self._end = nil} + mutating func clearEnd() {_uniqueStorage()._end = nil} var unknownFields = SwiftProtobuf.UnknownStorage() init() {} - fileprivate var _start: BlockID? = nil - fileprivate var _end: BlockID? = nil + fileprivate var _storage = _StorageClass.defaultInstance } /// A TxFilter contains the information needed to identify a particular /// transaction: either a block and an index, or a direct transaction hash. +/// Currently, only specification by hash is supported. struct TxFilter { // 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: BlockID { - get {return _block ?? BlockID()} - set {_block = newValue} + get {return _storage._block ?? BlockID()} + set {_uniqueStorage()._block = newValue} } /// Returns true if `block` has been explicitly set. - var hasBlock: Bool {return self._block != nil} + var hasBlock: Bool {return _storage._block != nil} /// Clears the value of `block`. Subsequent reads from it will return its default value. - mutating func clearBlock() {self._block = nil} + mutating func clearBlock() {_uniqueStorage()._block = nil} - var index: UInt64 = 0 + var index: UInt64 { + get {return _storage._index} + set {_uniqueStorage()._index = newValue} + } - var hash: Data = SwiftProtobuf.Internal.emptyData + var hash: Data { + get {return _storage._hash} + set {_uniqueStorage()._hash = newValue} + } var unknownFields = SwiftProtobuf.UnknownStorage() init() {} - fileprivate var _block: BlockID? = nil + fileprivate var _storage = _StorageClass.defaultInstance } -/// RawTransaction contains the complete transaction data. +/// RawTransaction contains the complete transaction data. It also optionally includes +/// the block height in which the transaction was included struct RawTransaction { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for @@ -104,11 +114,16 @@ struct RawTransaction { var data: Data = SwiftProtobuf.Internal.emptyData + var height: UInt64 = 0 + var unknownFields = SwiftProtobuf.UnknownStorage() init() {} } +/// A SendResponse encodes an error code and a string. It is currently used +/// only by SendTransaction(). If error code is zero, the operation was +/// successful; if non-zero, it and the message specify the failure. struct SendResponse { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for @@ -123,7 +138,7 @@ struct SendResponse { init() {} } -/// Empty placeholder. Someday we may want to specify e.g. a particular chain fork. +/// Chainspec is a placeholder to allow specification of a particular chain fork. struct ChainSpec { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for @@ -134,6 +149,102 @@ struct ChainSpec { init() {} } +/// Empty is for gRPCs that take no arguments, currently only GetLightdInfo. +struct Empty { + // 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 unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +/// LightdInfo returns various information about this lightwalletd instance +/// and the state of the blockchain. +struct LightdInfo { + // 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 version: String = String() + + var vendor: String = String() + + var taddrSupport: Bool = false + + var chainName: String = String() + + var saplingActivationHeight: UInt64 = 0 + + var consensusBranchID: String = String() + + var blockHeight: UInt64 = 0 + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +/// TransparentAddressBlockFilter restricts the results to the given address +/// or block range. +struct TransparentAddressBlockFilter { + // 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 address: String { + get {return _storage._address} + set {_uniqueStorage()._address = newValue} + } + + var range: BlockRange { + get {return _storage._range ?? BlockRange()} + set {_uniqueStorage()._range = newValue} + } + /// Returns true if `range` has been explicitly set. + var hasRange: Bool {return _storage._range != nil} + /// Clears the value of `range`. Subsequent reads from it will return its default value. + mutating func clearRange() {_uniqueStorage()._range = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _storage = _StorageClass.defaultInstance +} + +/// Duration is currently used only for testing, so that the Ping rpc +/// can simulate a delay, to create many simultaneous connections. Units +/// are microseconds. +struct Duration { + // 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 intervalUs: Int64 = 0 + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +/// PingResponse is used to indicate concurrency, how many Ping rpcs +/// are executing upon entry and upon exit (after the delay). +struct PingResponse { + // 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 entry: Int64 = 0 + + var exit: Int64 = 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" @@ -180,29 +291,63 @@ extension BlockRange: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio 2: .same(proto: "end"), ] + fileprivate class _StorageClass { + var _start: BlockID? = nil + var _end: BlockID? = nil + + static let defaultInstance = _StorageClass() + + private init() {} + + init(copying source: _StorageClass) { + _start = source._start + _end = source._end + } + } + + fileprivate mutating func _uniqueStorage() -> _StorageClass { + if !isKnownUniquelyReferenced(&_storage) { + _storage = _StorageClass(copying: _storage) + } + return _storage + } + mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularMessageField(value: &self._start) - case 2: try decoder.decodeSingularMessageField(value: &self._end) - default: break + _ = _uniqueStorage() + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + while let fieldNumber = try decoder.nextFieldNumber() { + switch fieldNumber { + case 1: try decoder.decodeSingularMessageField(value: &_storage._start) + case 2: try decoder.decodeSingularMessageField(value: &_storage._end) + default: break + } } } } func traverse(visitor: inout V) throws { - if let v = self._start { - try visitor.visitSingularMessageField(value: v, fieldNumber: 1) - } - if let v = self._end { - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + if let v = _storage._start { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } + if let v = _storage._end { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } } try unknownFields.traverse(visitor: &visitor) } static func ==(lhs: BlockRange, rhs: BlockRange) -> Bool { - if lhs._start != rhs._start {return false} - if lhs._end != rhs._end {return false} + if lhs._storage !== rhs._storage { + let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in + let _storage = _args.0 + let rhs_storage = _args.1 + if _storage._start != rhs_storage._start {return false} + if _storage._end != rhs_storage._end {return false} + return true + } + if !storagesAreEqual {return false} + } if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -216,34 +361,70 @@ extension TxFilter: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB 3: .same(proto: "hash"), ] + fileprivate class _StorageClass { + var _block: BlockID? = nil + var _index: UInt64 = 0 + var _hash: Data = SwiftProtobuf.Internal.emptyData + + static let defaultInstance = _StorageClass() + + private init() {} + + init(copying source: _StorageClass) { + _block = source._block + _index = source._index + _hash = source._hash + } + } + + fileprivate mutating func _uniqueStorage() -> _StorageClass { + if !isKnownUniquelyReferenced(&_storage) { + _storage = _StorageClass(copying: _storage) + } + return _storage + } + mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - switch fieldNumber { - case 1: try decoder.decodeSingularMessageField(value: &self._block) - case 2: try decoder.decodeSingularUInt64Field(value: &self.index) - case 3: try decoder.decodeSingularBytesField(value: &self.hash) - default: break + _ = _uniqueStorage() + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + while let fieldNumber = try decoder.nextFieldNumber() { + switch fieldNumber { + case 1: try decoder.decodeSingularMessageField(value: &_storage._block) + case 2: try decoder.decodeSingularUInt64Field(value: &_storage._index) + case 3: try decoder.decodeSingularBytesField(value: &_storage._hash) + default: break + } } } } func traverse(visitor: inout V) throws { - if let v = self._block { - try visitor.visitSingularMessageField(value: v, fieldNumber: 1) - } - if self.index != 0 { - try visitor.visitSingularUInt64Field(value: self.index, fieldNumber: 2) - } - if !self.hash.isEmpty { - try visitor.visitSingularBytesField(value: self.hash, fieldNumber: 3) + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + if let v = _storage._block { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) + } + if _storage._index != 0 { + try visitor.visitSingularUInt64Field(value: _storage._index, fieldNumber: 2) + } + if !_storage._hash.isEmpty { + try visitor.visitSingularBytesField(value: _storage._hash, fieldNumber: 3) + } } try unknownFields.traverse(visitor: &visitor) } static func ==(lhs: TxFilter, rhs: TxFilter) -> Bool { - if lhs._block != rhs._block {return false} - if lhs.index != rhs.index {return false} - if lhs.hash != rhs.hash {return false} + if lhs._storage !== rhs._storage { + let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in + let _storage = _args.0 + let rhs_storage = _args.1 + if _storage._block != rhs_storage._block {return false} + if _storage._index != rhs_storage._index {return false} + if _storage._hash != rhs_storage._hash {return false} + return true + } + if !storagesAreEqual {return false} + } if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -253,12 +434,14 @@ extension RawTransaction: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement static let protoMessageName: String = _protobuf_package + ".RawTransaction" static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .same(proto: "data"), + 2: .same(proto: "height"), ] mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { switch fieldNumber { case 1: try decoder.decodeSingularBytesField(value: &self.data) + case 2: try decoder.decodeSingularUInt64Field(value: &self.height) default: break } } @@ -268,11 +451,15 @@ extension RawTransaction: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement if !self.data.isEmpty { try visitor.visitSingularBytesField(value: self.data, fieldNumber: 1) } + if self.height != 0 { + try visitor.visitSingularUInt64Field(value: self.height, fieldNumber: 2) + } try unknownFields.traverse(visitor: &visitor) } static func ==(lhs: RawTransaction, rhs: RawTransaction) -> Bool { if lhs.data != rhs.data {return false} + if lhs.height != rhs.height {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -331,3 +518,220 @@ extension ChainSpec: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation return true } } + +extension Empty: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".Empty" + static let _protobuf_nameMap = SwiftProtobuf._NameMap() + + mutating func decodeMessage(decoder: inout D) throws { + while let _ = try decoder.nextFieldNumber() { + } + } + + func traverse(visitor: inout V) throws { + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: Empty, rhs: Empty) -> Bool { + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension LightdInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".LightdInfo" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "version"), + 2: .same(proto: "vendor"), + 3: .same(proto: "taddrSupport"), + 4: .same(proto: "chainName"), + 5: .same(proto: "saplingActivationHeight"), + 6: .same(proto: "consensusBranchId"), + 7: .same(proto: "blockHeight"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + switch fieldNumber { + case 1: try decoder.decodeSingularStringField(value: &self.version) + case 2: try decoder.decodeSingularStringField(value: &self.vendor) + case 3: try decoder.decodeSingularBoolField(value: &self.taddrSupport) + case 4: try decoder.decodeSingularStringField(value: &self.chainName) + case 5: try decoder.decodeSingularUInt64Field(value: &self.saplingActivationHeight) + case 6: try decoder.decodeSingularStringField(value: &self.consensusBranchID) + case 7: try decoder.decodeSingularUInt64Field(value: &self.blockHeight) + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if !self.version.isEmpty { + try visitor.visitSingularStringField(value: self.version, fieldNumber: 1) + } + if !self.vendor.isEmpty { + try visitor.visitSingularStringField(value: self.vendor, fieldNumber: 2) + } + if self.taddrSupport != false { + try visitor.visitSingularBoolField(value: self.taddrSupport, fieldNumber: 3) + } + if !self.chainName.isEmpty { + try visitor.visitSingularStringField(value: self.chainName, fieldNumber: 4) + } + if self.saplingActivationHeight != 0 { + try visitor.visitSingularUInt64Field(value: self.saplingActivationHeight, fieldNumber: 5) + } + if !self.consensusBranchID.isEmpty { + try visitor.visitSingularStringField(value: self.consensusBranchID, fieldNumber: 6) + } + if self.blockHeight != 0 { + try visitor.visitSingularUInt64Field(value: self.blockHeight, fieldNumber: 7) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: LightdInfo, rhs: LightdInfo) -> Bool { + if lhs.version != rhs.version {return false} + if lhs.vendor != rhs.vendor {return false} + if lhs.taddrSupport != rhs.taddrSupport {return false} + if lhs.chainName != rhs.chainName {return false} + if lhs.saplingActivationHeight != rhs.saplingActivationHeight {return false} + if lhs.consensusBranchID != rhs.consensusBranchID {return false} + if lhs.blockHeight != rhs.blockHeight {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension TransparentAddressBlockFilter: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".TransparentAddressBlockFilter" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "address"), + 2: .same(proto: "range"), + ] + + fileprivate class _StorageClass { + var _address: String = String() + var _range: BlockRange? = nil + + static let defaultInstance = _StorageClass() + + private init() {} + + init(copying source: _StorageClass) { + _address = source._address + _range = source._range + } + } + + fileprivate mutating func _uniqueStorage() -> _StorageClass { + if !isKnownUniquelyReferenced(&_storage) { + _storage = _StorageClass(copying: _storage) + } + return _storage + } + + mutating func decodeMessage(decoder: inout D) throws { + _ = _uniqueStorage() + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + while let fieldNumber = try decoder.nextFieldNumber() { + switch fieldNumber { + case 1: try decoder.decodeSingularStringField(value: &_storage._address) + case 2: try decoder.decodeSingularMessageField(value: &_storage._range) + default: break + } + } + } + } + + func traverse(visitor: inout V) throws { + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + if !_storage._address.isEmpty { + try visitor.visitSingularStringField(value: _storage._address, fieldNumber: 1) + } + if let v = _storage._range { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: TransparentAddressBlockFilter, rhs: TransparentAddressBlockFilter) -> Bool { + if lhs._storage !== rhs._storage { + let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in + let _storage = _args.0 + let rhs_storage = _args.1 + if _storage._address != rhs_storage._address {return false} + if _storage._range != rhs_storage._range {return false} + return true + } + if !storagesAreEqual {return false} + } + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Duration: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".Duration" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "intervalUs"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + switch fieldNumber { + case 1: try decoder.decodeSingularInt64Field(value: &self.intervalUs) + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.intervalUs != 0 { + try visitor.visitSingularInt64Field(value: self.intervalUs, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: Duration, rhs: Duration) -> Bool { + if lhs.intervalUs != rhs.intervalUs {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension PingResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".PingResponse" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "entry"), + 2: .same(proto: "exit"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + switch fieldNumber { + case 1: try decoder.decodeSingularInt64Field(value: &self.entry) + case 2: try decoder.decodeSingularInt64Field(value: &self.exit) + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.entry != 0 { + try visitor.visitSingularInt64Field(value: self.entry, fieldNumber: 1) + } + if self.exit != 0 { + try visitor.visitSingularInt64Field(value: self.exit, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: PingResponse, rhs: PingResponse) -> Bool { + if lhs.entry != rhs.entry {return false} + if lhs.exit != rhs.exit {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} diff --git a/ZcashLightClientKit/Stencil/WalletBirthday+saplingtree.stencil b/ZcashLightClientKit/Stencil/WalletBirthday+saplingtree.stencil index c4e2d76d..95f9ee5c 100644 --- a/ZcashLightClientKit/Stencil/WalletBirthday+saplingtree.stencil +++ b/ZcashLightClientKit/Stencil/WalletBirthday+saplingtree.stencil @@ -26,13 +26,20 @@ public extension WalletBirthday { time: 1574579149, tree: "01999fc372390699b15f71d41745abe6a2ea0db4ffa8894d3c5fe30b9261a1a43a01585112668685bd6783cb01b72d17dc86c6d740c27cccf66b75e959e4e4f5ea3710019b7f6b4457a97eadbe1a39bfcc6ba0a56d37010d0d799e1e652fc29733103e04016a0b4d2705e1feb2021d80e5785608536dde05aea5ef676a5427244228b19e2d00010973d03ad5f79fcac64ab3ffbdaaac1a24b74a3617770bf960fb004cbd422439000001984bfce9361025cc38574f944a3ed7b074b3bf88cfce6f14c4a9be4d91d6dc730105871ec1e3737a39bceb00b0c2d253ff36f472e92c361e7ef360d49ea8dc4c4200000001c145105e1bf401668a8f23ca70c47ee92d23bd366072020c83d26b855eeafd6d0001fa6980c053d84f809b6abcf35690f03a11f87b28e3240828e32e3f57af41e54e01319312241b0031e3a255b0d708750b4cb3f3fe79e3503fe488cc8db1dd00753801754bb593ea42d231a7ddf367640f09bbf59dc00f2c1d2003cc340e0c016b5b13" ) - case 663000 ..< 663700: + case 663000 ..< 663150: return WalletBirthday( height: 663000, hash: "0000000000bd422264b700bb33cab167ab42392c89db0e7c8ce30d57f346fe69", time: 1576810013, tree: "0102f02a8cd23e35502f8efa55893c7a145168ac3fa00d4bd032b55097bbe0335a01b57c362e3c834f2216c72ae0d8a335fd2397cc80073d0f5b29c419028bc6c94c100000000157bfd70afa37c8bf0c60c9e160d2145bdfbcf07837b0ff90bf8a5108722cc85400000000011bc9521263584de20822f9483e7edb5af54150c4823c775b2efc6a1eded9625501a6030f8d4b588681eddb66cad63f09c5c7519db49500fc56ebd481ce5e903c22000163f4eec5a2fe00a5f45e71e1542ff01e937d2210c99f03addcce5314a5278b2d0163ab01f46a3bb6ea46f5a19d5bdd59eb3f81e19cfa6d10ab0fd5566c7a16992601fa6980c053d84f809b6abcf35690f03a11f87b28e3240828e32e3f57af41e54e01319312241b0031e3a255b0d708750b4cb3f3fe79e3503fe488cc8db1dd00753801754bb593ea42d231a7ddf367640f09bbf59dc00f2c1d2003cc340e0c016b5b13" ) + case 663150 ..< 663700: + return WalletBirthday( + height: 663150, + hash: "0000000002fd3be4c24c437bd22620901617125ec2a3a6c902ec9a6c06f734fc", + time: 1576821833, + tree: "01ec6278a1bed9e1b080fd60ef50eb17411645e3746ff129283712bc4757ecc833001001b4e1d4a26ac4a2810b57a14f4ffb69395f55dde5674ecd2462af96f9126e054701a36afb68534f640938bdffd80dfcb3f4d5e232488abbf67d049b33a761e7ed6901a16e35205fb7fe626a9b13fc43e1d2b98a9c241f99f93d5e93a735454073025401f5b9bcbf3d0e3c83f95ee79299e8aeadf30af07717bda15ffb7a3d00243b58570001fa6d4c2390e205f81d86b85ace0b48f3ce0afb78eeef3e14c70bcfd7c5f0191c0000011bc9521263584de20822f9483e7edb5af54150c4823c775b2efc6a1eded9625501a6030f8d4b588681eddb66cad63f09c5c7519db49500fc56ebd481ce5e903c22000163f4eec5a2fe00a5f45e71e1542ff01e937d2210c99f03addcce5314a5278b2d0163ab01f46a3bb6ea46f5a19d5bdd59eb3f81e19cfa6d10ab0fd5566c7a16992601fa6980c053d84f809b6abcf35690f03a11f87b28e3240828e32e3f57af41e54e01319312241b0031e3a255b0d708750b4cb3f3fe79e3503fe488cc8db1dd00753801754bb593ea42d231a7ddf367640f09bbf59dc00f2c1d2003cc340e0c016b5b13" + ) case 663700 ..< 670000: return WalletBirthday( height: 663700, diff --git a/ZcashLightClientKit/UIKit/Synchronizer/SDKSynchronizer.swift b/ZcashLightClientKit/UIKit/Synchronizer/SDKSynchronizer.swift index d0f33af3..4ec25526 100644 --- a/ZcashLightClientKit/UIKit/Synchronizer/SDKSynchronizer.swift +++ b/ZcashLightClientKit/UIKit/Synchronizer/SDKSynchronizer.swift @@ -64,7 +64,6 @@ public extension Notification.Name { */ public class SDKSynchronizer: Synchronizer { - public struct NotificationKeys { public static let progress = "SDKSynchronizer.progress" public static let blockHeight = "SDKSynchronizer.blockHeight" @@ -516,7 +515,7 @@ public class SDKSynchronizer: Synchronizer { } private func mapError(_ error: Error) -> Error { - if let compactBlockProcessorError = error as? CompactBlockProcessorError { + if let compactBlockProcessorError = error as? CompactBlockProcessorError { switch compactBlockProcessorError { case .dataDbInitFailed(let path): return SynchronizerError.initFailed(message: "DataDb init failed at path: \(path)") diff --git a/ZcashLightClientKitTests/BlockDownloaderTests.swift b/ZcashLightClientKitTests/BlockDownloaderTests.swift index 41566962..9acdb0c7 100644 --- a/ZcashLightClientKitTests/BlockDownloaderTests.swift +++ b/ZcashLightClientKitTests/BlockDownloaderTests.swift @@ -86,7 +86,7 @@ class BlockDownloaderTests: XCTestCase { } func testFailure() { - let awfulDownloader = CompactBlockDownloader(service: AwfulLightWalletService(latestBlockHeight: 281_000), storage: ZcashConsoleFakeStorage()) + let awfulDownloader = CompactBlockDownloader(service: AwfulLightWalletService(latestBlockHeight: ZcashSDK.SAPLING_ACTIVATION_HEIGHT + 1000), storage: ZcashConsoleFakeStorage()) let expect = XCTestExpectation(description: self.description) expect.expectedFulfillmentCount = 1 diff --git a/ZcashLightClientKitTests/CompactBlockProcessorTests.swift b/ZcashLightClientKitTests/CompactBlockProcessorTests.swift index 4c8ee5b1..6357c816 100644 --- a/ZcashLightClientKitTests/CompactBlockProcessorTests.swift +++ b/ZcashLightClientKitTests/CompactBlockProcessorTests.swift @@ -18,7 +18,7 @@ class CompactBlockProcessorTests: XCTestCase { var startedScanningNotificationExpectation: XCTestExpectation! var startedValidatingNotificationExpectation: XCTestExpectation! var idleNotificationExpectation: XCTestExpectation! - let mockLatestHeight = 282_000 + let mockLatestHeight = ZcashSDK.SAPLING_ACTIVATION_HEIGHT + 2000 override func setUp() { // Put setup code here. This method is called before the invocation of each test method in the class. @@ -108,27 +108,24 @@ class CompactBlockProcessorTests: XCTestCase { // test first range var latestDownloadedHeight = processorConfig.walletBirthday // this can be either this or Wallet Birthday. - var latestBlockchainHeight = BlockHeight(281_000) + var latestBlockchainHeight = BlockHeight(ZcashSDK.SAPLING_ACTIVATION_HEIGHT + 1000) var expectedBatchRange = CompactBlockRange(uncheckedBounds: (lower: latestDownloadedHeight, upper:latestDownloadedHeight + processorConfig.downloadBatchSize - 1)) XCTAssertEqual(expectedBatchRange, processor.nextBatchBlockRange(latestHeight: latestBlockchainHeight, latestDownloadedHeight: latestDownloadedHeight)) // Test mid-range - latestDownloadedHeight = BlockHeight(280_100) - latestBlockchainHeight = BlockHeight(281_000) + latestDownloadedHeight = BlockHeight(ZcashSDK.SAPLING_ACTIVATION_HEIGHT + ZcashSDK.DEFAULT_BATCH_SIZE) + latestBlockchainHeight = BlockHeight(ZcashSDK.SAPLING_ACTIVATION_HEIGHT + 1000) expectedBatchRange = CompactBlockRange(uncheckedBounds: (lower: latestDownloadedHeight + 1, upper:latestDownloadedHeight + processorConfig.downloadBatchSize)) XCTAssertEqual(expectedBatchRange, processor.nextBatchBlockRange(latestHeight: latestBlockchainHeight, latestDownloadedHeight: latestDownloadedHeight)) - latestDownloadedHeight = BlockHeight(280_950) - latestBlockchainHeight = BlockHeight(281_000) - // Test last batch range - latestDownloadedHeight = BlockHeight(280_950) - latestBlockchainHeight = BlockHeight(281_000) + latestDownloadedHeight = BlockHeight(ZcashSDK.SAPLING_ACTIVATION_HEIGHT + 950) + latestBlockchainHeight = BlockHeight(ZcashSDK.SAPLING_ACTIVATION_HEIGHT + 1000) expectedBatchRange = CompactBlockRange(uncheckedBounds: (lower: latestDownloadedHeight + 1, upper: latestBlockchainHeight)) diff --git a/ZcashLightClientKitTests/CompactBlockReorgTests.swift b/ZcashLightClientKitTests/CompactBlockReorgTests.swift index 5f1b0934..1b892c80 100644 --- a/ZcashLightClientKitTests/CompactBlockReorgTests.swift +++ b/ZcashLightClientKitTests/CompactBlockReorgTests.swift @@ -20,7 +20,7 @@ class CompactBlockReorgTests: XCTestCase { var startedValidatingNotificationExpectation: XCTestExpectation! var idleNotificationExpectation: XCTestExpectation! var reorgNotificationExpectation: XCTestExpectation! - let mockLatestHeight = 282_000 + let mockLatestHeight = ZcashSDK.SAPLING_ACTIVATION_HEIGHT + 2000 override func setUp() { // Put setup code here. This method is called before the invocation of each test method in the class. @@ -33,7 +33,7 @@ class CompactBlockReorgTests: XCTestCase { let mockBackend = MockRustBackend.self mockBackend.mockValidateCombinedChainFailAfterAttempts = 3 mockBackend.mockValidateCombinedChainKeepFailing = false - mockBackend.mockValidateCombinedChainFailureHeight = 280_320 + mockBackend.mockValidateCombinedChainFailureHeight = ZcashSDK.SAPLING_ACTIVATION_HEIGHT + 320 processor = CompactBlockProcessor(downloader: downloader, backend: mockBackend, @@ -65,7 +65,7 @@ class CompactBlockReorgTests: XCTestCase { } @objc func processorHandledReorg(_ notification: Notification) { -// DispatchQueue.main.sync { + XCTAssertNotNil(notification.userInfo) if let reorg = notification.userInfo?[CompactBlockProcessorNotificationKey.reorgHeight] as? BlockHeight, let rewind = notification.userInfo?[CompactBlockProcessorNotificationKey.rewindHeight] as? BlockHeight { @@ -76,8 +76,6 @@ class CompactBlockReorgTests: XCTestCase { } else { XCTFail("CompactBlockProcessor reorg notification is malformed") } -// } - } @objc func processorFailed(_ notification: Notification) { @@ -88,7 +86,6 @@ class CompactBlockReorgTests: XCTestCase { } else { XCTFail("CompactBlockProcessor failed") } - } } @@ -115,10 +112,9 @@ class CompactBlockReorgTests: XCTestCase { downloadStartedExpect, startedValidatingNotificationExpectation, startedScanningNotificationExpectation, - reorgNotificationExpectation, idleNotificationExpectation, - ], timeout: 3000,enforceOrder: true) + ], timeout: 300,enforceOrder: true) } private func expectedBatches(currentHeight: BlockHeight, targetHeight: BlockHeight, batchSize: Int) -> Int { diff --git a/ZcashLightClientKitTests/ReOrgTests.swift b/ZcashLightClientKitTests/ReOrgTests.swift new file mode 100644 index 00000000..ddced6bf --- /dev/null +++ b/ZcashLightClientKitTests/ReOrgTests.swift @@ -0,0 +1,243 @@ +// +// ReOrgTests.swift +// ZcashLightClientKit-Unit-Tests +// +// Created by Francisco Gindre on 3/23/20. +// + +import XCTest +@testable import ZcashLightClientKit +/** + basic reorg test. Scan, get a reorg and then reach latest height. + + * connect to dLWD + * request latest height -> receive 663250 + * download and sync blocks from 663150 to 663250 + * trigger reorg by calling API (no need to pass params)** + * request latest height -> receive 663251! + * download that block + * observe that the prev hash of that block does not match the hash that we have for 663250 + * rewind 10 blocks and request blocks 663241 to 663251 + */ +class ReOrgTests: XCTestCase { + + var processorConfig: CompactBlockProcessor.Configuration! + var processor: CompactBlockProcessor! + var darksideWalletService: DarksideWalletService! + var downloader: CompactBlockDownloader! + var downloadStartedExpect: XCTestExpectation! + var updatedNotificationExpectation: XCTestExpectation! + var stopNotificationExpectation: XCTestExpectation! + var startedScanningNotificationExpectation: XCTestExpectation! + var startedValidatingNotificationExpectation: XCTestExpectation! + var idleNotificationExpectation: XCTestExpectation! + var reorgNotificationExpectation: XCTestExpectation! + var afterReorgIdleNotification: XCTestExpectation! + var waitExpectation: XCTestExpectation! + let mockLatestHeight = BlockHeight(663250) + let targetLatestHeight = BlockHeight(663251) + let walletBirthday = BlockHeight(663150) + + override func setUpWithError() throws { + logger = SampleLogger(logLevel: .debug) + var config = CompactBlockProcessor.Configuration.standard + + let birthday = WalletBirthday.birthday(with: walletBirthday) + config.walletBirthday = birthday.height + processorConfig = config + + try? FileManager.default.removeItem(at: processorConfig.cacheDb) + try? FileManager.default.removeItem(at: processorConfig.dataDb) + let service = DarksideWalletService() + darksideWalletService = service + let storage = CompactBlockStorage.init(connectionProvider: SimpleConnectionProvider(path: processorConfig.cacheDb.absoluteString)) + try! storage.createTable() + downloader = CompactBlockDownloader(service: service, storage: storage) + processor = CompactBlockProcessor(downloader: downloader, + backend: ZcashRustBackend.self, + config: processorConfig) + + downloadStartedExpect = XCTestExpectation(description: self.description + " downloadStartedExpect") + stopNotificationExpectation = XCTestExpectation(description: self.description + " stopNotificationExpectation") + updatedNotificationExpectation = XCTestExpectation(description: self.description + " updatedNotificationExpectation") + startedValidatingNotificationExpectation = XCTestExpectation(description: self.description + " startedValidatingNotificationExpectation") + startedScanningNotificationExpectation = XCTestExpectation(description: self.description + " startedScanningNotificationExpectation") + idleNotificationExpectation = XCTestExpectation(description: self.description + " idleNotificationExpectation") + afterReorgIdleNotification = XCTestExpectation(description: self.description + " afterReorgIdleNotification") + reorgNotificationExpectation = XCTestExpectation(description: self.description + " reorgNotificationExpectation") + + waitExpectation = XCTestExpectation(description: self.description + "waitExpectation") + NotificationCenter.default.addObserver(self, selector: #selector(processorHandledReorg(_:)), name: Notification.Name.blockProcessorHandledReOrg, object: processor) + NotificationCenter.default.addObserver(self, selector: #selector(processorFailed(_:)), name: Notification.Name.blockProcessorFailed, object: processor) + } + + override func tearDownWithError() throws { + try! FileManager.default.removeItem(at: processorConfig.cacheDb) + try? FileManager.default.removeItem(at: processorConfig.dataDb) + downloadStartedExpect.unsubscribeFromNotifications() + stopNotificationExpectation.unsubscribeFromNotifications() + updatedNotificationExpectation.unsubscribeFromNotifications() + startedScanningNotificationExpectation.unsubscribeFromNotifications() + startedValidatingNotificationExpectation.unsubscribeFromNotifications() + idleNotificationExpectation.unsubscribeFromNotifications() + reorgNotificationExpectation.unsubscribeFromNotifications() + afterReorgIdleNotification.unsubscribeFromNotifications() + NotificationCenter.default.removeObserver(self) + } + + fileprivate func startProcessing() throws { + XCTAssertNotNil(processor) + + // Subscribe to notifications + downloadStartedExpect.subscribe(to: Notification.Name.blockProcessorStartedDownloading, object: processor) + stopNotificationExpectation.subscribe(to: Notification.Name.blockProcessorStopped, object: processor) + updatedNotificationExpectation.subscribe(to: Notification.Name.blockProcessorUpdated, object: processor) + startedValidatingNotificationExpectation.subscribe(to: Notification.Name.blockProcessorStartedValidating, object: processor) + startedScanningNotificationExpectation.subscribe(to: Notification.Name.blockProcessorStartedScanning, object: processor) + + NotificationCenter.default.addObserver(self, selector: #selector(firstIdleNotification(_:)), name: Notification.Name.blockProcessorIdle, object: processor) + + NotificationCenter.default.addObserver(self, selector: #selector(handleReOrgNotification(_:)), name: Notification.Name.blockProcessorHandledReOrg, object: processor) + + try processor.start() + } + + @objc func firstIdleNotification(_ notification: Notification) { + idleNotificationExpectation.fulfill() + } + + @objc func reOrgIdleNotification(_ notification: Notification) { + afterReorgIdleNotification.fulfill() + } + + @objc func handleReOrgNotification(_ notification: Notification) { + + reorgNotificationExpectation.fulfill() + guard let reorgHeight = notification.userInfo?[CompactBlockProcessorNotificationKey.reorgHeight] as? BlockHeight, + let rewindHeight = notification.userInfo?[CompactBlockProcessorNotificationKey.rewindHeight] as? BlockHeight else { + XCTFail("malformed reorg userInfo") + return + } + print("reorgHeight: \(reorgHeight)") + print("rewindHeight: \(rewindHeight)") + + XCTAssertTrue(reorgHeight > 0) + XCTAssertNoThrow(rewindHeight > 0) + } + + func testBasicReOrg() throws { + let mockLatestHeight = BlockHeight(663200) + let targetLatestHeight = BlockHeight(663250) + let reOrgHeight = BlockHeight(663195) + let walletBirthday = WalletBirthday.birthday(with: 663151).height + + try basicReOrgTest(firstLatestHeight: mockLatestHeight, reorgHeight: reOrgHeight, walletBirthday: walletBirthday, targetHeight: targetLatestHeight) + } + + func testTenPlusBlockReOrg() throws { + let mockLatestHeight = BlockHeight(663200) + let targetLatestHeight = BlockHeight(663250) + let reOrgHeight = BlockHeight(663180) + let walletBirthday = WalletBirthday.birthday(with: BlockHeight(663150)).height + + try basicReOrgTest(firstLatestHeight: mockLatestHeight, reorgHeight: reOrgHeight, walletBirthday: walletBirthday, targetHeight: targetLatestHeight) + } + + func basicReOrgTest(firstLatestHeight: BlockHeight, reorgHeight: BlockHeight, walletBirthday: BlockHeight, targetHeight: BlockHeight) throws { + + do { + try darksideWalletService.setLatestHeight(firstLatestHeight) + + } catch { + XCTFail("Error: \(error)") + return + } + + + /** + connect to dLWD + request latest height -> receive firstLatestHeight + */ + do { + print("first latest height: \(try darksideWalletService.latestBlockHeight())") + } catch { + XCTFail("Error: \(error)") + return + } + + + /** + download and sync blocks from walletBirthday to firstLatestHeight + */ + do { + try startProcessing() + + } catch { + XCTFail("Error: \(error)") + } + + wait(for: [downloadStartedExpect, startedValidatingNotificationExpectation,startedScanningNotificationExpectation, idleNotificationExpectation], timeout: 30) + idleNotificationExpectation.unsubscribeFromNotifications() + + /** + verify that mock height has been reached + */ + var latestDownloadedHeight = BlockHeight(0) + XCTAssertNoThrow(try {latestDownloadedHeight = try downloader.lastDownloadedBlockHeight()}()) + XCTAssertTrue(latestDownloadedHeight > 0) + + /** + trigger reorg! + */ + XCTAssertNoThrow( + try darksideWalletService.triggerReOrg(latestHeight: targetHeight, reOrgHeight: reorgHeight) + ) + + /** + request latest height -> receive targetHeight! + */ + + XCTAssertNoThrow(try {latestDownloadedHeight = try downloader.lastDownloadedBlockHeight()}()) + afterReorgIdleNotification.subscribe(to: .blockProcessorIdle, object: processor) + + /** + request latest height -> receive targetHeight! + download that block + observe that the prev hash of that block does not match the hash that we have for firstLatestHeight + rewind 10 blocks and request blocks targetHeight-10 to targetHeight + */ + try processor.start(retry: true) + + // now reorg should happen and reorg notifications and idle notification should be triggered + + wait(for: [reorgNotificationExpectation, afterReorgIdleNotification], timeout: 10) + + // now everything should be fine. latest block should be targetHeight + + XCTAssertNoThrow(try {latestDownloadedHeight = try downloader.lastDownloadedBlockHeight()}()) + XCTAssertEqual(latestDownloadedHeight, targetHeight) + } + + @objc func processorHandledReorg(_ notification: Notification) { + + XCTAssertNotNil(notification.userInfo) + if let reorg = notification.userInfo?[CompactBlockProcessorNotificationKey.reorgHeight] as? BlockHeight, + let rewind = notification.userInfo?[CompactBlockProcessorNotificationKey.rewindHeight] as? BlockHeight { + + XCTAssertTrue( rewind <= mockLatestHeight - processorConfig.rewindDistance) + XCTAssertTrue( rewind <= reorg ) + reorgNotificationExpectation.fulfill() + } else { + XCTFail("CompactBlockProcessor reorg notification is malformed") + } + } + + @objc func processorFailed(_ notification: Notification) { + XCTAssertNotNil(notification.userInfo) + if let error = notification.userInfo?["error"] { + XCTFail("CompactBlockProcessor failed with Error: \(error)") + } else { + XCTFail("CompactBlockProcessor failed") + } + } +} diff --git a/ZcashLightClientKitTests/proto/darkside.grpc.swift b/ZcashLightClientKitTests/proto/darkside.grpc.swift new file mode 100644 index 00000000..350ab98d --- /dev/null +++ b/ZcashLightClientKitTests/proto/darkside.grpc.swift @@ -0,0 +1,106 @@ +// +// 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 Dispatch +import Foundation +import SwiftGRPC +import SwiftProtobuf +@testable import ZcashLightClientKit +internal protocol DarksideStreamerDarksideGetIncomingTransactionsCall: ClientCallServerStreaming { + /// Do not call this directly, call `receive()` in the protocol extension below instead. + func _receive(timeout: DispatchTime) throws -> RawTransaction? + /// Call this to wait for a result. Nonblocking. + func receive(completion: @escaping (ResultOrRPCError) -> Void) throws +} + +internal extension DarksideStreamerDarksideGetIncomingTransactionsCall { + /// Call this to wait for a result. Blocking. + func receive(timeout: DispatchTime = .distantFuture) throws -> RawTransaction? { return try self._receive(timeout: timeout) } +} + +fileprivate final class DarksideStreamerDarksideGetIncomingTransactionsCallBase: ClientCallServerStreamingBase, DarksideStreamerDarksideGetIncomingTransactionsCall { + override class var method: String { return "/cash.z.wallet.sdk.rpc.DarksideStreamer/DarksideGetIncomingTransactions" } +} + +internal protocol DarksideStreamerDarksideSetStateCall: ClientCallUnary {} + +fileprivate final class DarksideStreamerDarksideSetStateCallBase: ClientCallUnaryBase, DarksideStreamerDarksideSetStateCall { + override class var method: String { return "/cash.z.wallet.sdk.rpc.DarksideStreamer/DarksideSetState" } +} + + +/// Instantiate DarksideStreamerServiceClient, then call methods of this protocol to make API calls. +internal protocol DarksideStreamerService: ServiceClient { + /// Asynchronous. Server-streaming. + /// Send the initial message. + /// Use methods on the returned object to get streamed responses. + func darksideGetIncomingTransactions(_ request: Empty, metadata customMetadata: Metadata, completion: ((CallResult) -> Void)?) throws -> DarksideStreamerDarksideGetIncomingTransactionsCall + + /// Synchronous. Unary. + func darksideSetState(_ request: DarksideState, metadata customMetadata: Metadata) throws -> Empty + /// Asynchronous. Unary. + @discardableResult + func darksideSetState(_ request: DarksideState, metadata customMetadata: Metadata, completion: @escaping (Empty?, CallResult) -> Void) throws -> DarksideStreamerDarksideSetStateCall + +} + +internal extension DarksideStreamerService { + /// Asynchronous. Server-streaming. + func darksideGetIncomingTransactions(_ request: Empty, completion: ((CallResult) -> Void)?) throws -> DarksideStreamerDarksideGetIncomingTransactionsCall { + return try self.darksideGetIncomingTransactions(request, metadata: self.metadata, completion: completion) + } + + /// Synchronous. Unary. + func darksideSetState(_ request: DarksideState) throws -> Empty { + return try self.darksideSetState(request, metadata: self.metadata) + } + /// Asynchronous. Unary. + @discardableResult + func darksideSetState(_ request: DarksideState, completion: @escaping (Empty?, CallResult) -> Void) throws -> DarksideStreamerDarksideSetStateCall { + return try self.darksideSetState(request, metadata: self.metadata, completion: completion) + } + +} + +internal final class DarksideStreamerServiceClient: ServiceClientBase, DarksideStreamerService { + /// Asynchronous. Server-streaming. + /// Send the initial message. + /// Use methods on the returned object to get streamed responses. + internal func darksideGetIncomingTransactions(_ request: Empty, metadata customMetadata: Metadata, completion: ((CallResult) -> Void)?) throws -> DarksideStreamerDarksideGetIncomingTransactionsCall { + return try DarksideStreamerDarksideGetIncomingTransactionsCallBase(channel) + .start(request: request, metadata: customMetadata, completion: completion) + } + + /// Synchronous. Unary. + internal func darksideSetState(_ request: DarksideState, metadata customMetadata: Metadata) throws -> Empty { + return try DarksideStreamerDarksideSetStateCallBase(channel) + .run(request: request, metadata: customMetadata) + } + /// Asynchronous. Unary. + @discardableResult + internal func darksideSetState(_ request: DarksideState, metadata customMetadata: Metadata, completion: @escaping (Empty?, CallResult) -> Void) throws -> DarksideStreamerDarksideSetStateCall { + return try DarksideStreamerDarksideSetStateCallBase(channel) + .start(request: request, metadata: customMetadata, completion: completion) + } + +} + diff --git a/ZcashLightClientKitTests/proto/darkside.pb.swift b/ZcashLightClientKitTests/proto/darkside.pb.swift new file mode 100644 index 00000000..b3e04b9c --- /dev/null +++ b/ZcashLightClientKitTests/proto/darkside.pb.swift @@ -0,0 +1,77 @@ +// 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 documenation: +// 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 your 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 DarksideState { + // 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 latestHeight: UInt64 = 0 + + var reorgHeight: UInt64 = 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 DarksideState: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".DarksideState" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "latestHeight"), + 2: .same(proto: "reorgHeight"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + switch fieldNumber { + case 1: try decoder.decodeSingularUInt64Field(value: &self.latestHeight) + case 2: try decoder.decodeSingularUInt64Field(value: &self.reorgHeight) + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.latestHeight != 0 { + try visitor.visitSingularUInt64Field(value: self.latestHeight, fieldNumber: 1) + } + if self.reorgHeight != 0 { + try visitor.visitSingularUInt64Field(value: self.reorgHeight, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: DarksideState, rhs: DarksideState) -> Bool { + if lhs.latestHeight != rhs.latestHeight {return false} + if lhs.reorgHeight != rhs.reorgHeight {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} diff --git a/ZcashLightClientKitTests/proto/darkside.proto b/ZcashLightClientKitTests/proto/darkside.proto new file mode 100644 index 00000000..0e4097e3 --- /dev/null +++ b/ZcashLightClientKitTests/proto/darkside.proto @@ -0,0 +1,25 @@ +// 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 DarksideState { + uint64 latestHeight = 1; + uint64 reorgHeight = 2; +} + +service DarksideStreamer { + // Darkside (testing, see --darkside-very-insecure): + + // Return the list of transactions that have been submitted (via SendTransaction). + rpc DarksideGetIncomingTransactions(Empty) returns (stream RawTransaction) {} + + // Set the information that GetLightdInfo returns, except that chainName specifies + // a file of blocks within testdata/darkside that GetBlock will return. + rpc DarksideSetState(DarksideState) returns (Empty) {} +} diff --git a/ZcashLightClientKitTests/utils/DarkSideWalletService.swift b/ZcashLightClientKitTests/utils/DarkSideWalletService.swift new file mode 100644 index 00000000..78360c54 --- /dev/null +++ b/ZcashLightClientKitTests/utils/DarkSideWalletService.swift @@ -0,0 +1,62 @@ +// +// DarkSideWalletService.swift +// ZcashLightClientKit-Unit-Tests +// +// Created by Francisco Gindre on 3/23/20. +// + +import Foundation +import ZcashLightClientKit +import SwiftGRPC +class DarksideWalletService: LightWalletService { + var channel: Channel + init() { + let channel = ChannelProvider().channel() + self.channel = channel + self.service = LightWalletGRPCService(channel: channel) + self.darksideService = DarksideStreamerServiceClient(channel: channel) + } + var service: LightWalletGRPCService + var darksideService: DarksideStreamerServiceClient + + func latestBlockHeight(result: @escaping (Result) -> 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) + } + + func submit(spendTransaction: Data, result: @escaping (Result) -> Void) { + service.submit(spendTransaction: spendTransaction, result: result) + } + + func submit(spendTransaction: Data) throws -> LightWalletServiceResponse { + try service.submit(spendTransaction: spendTransaction) + } + + func triggerReOrg(latestHeight: BlockHeight, reOrgHeight: BlockHeight) throws { + var darksideState = DarksideState() + darksideState.latestHeight = UInt64(latestHeight) + darksideState.reorgHeight = UInt64(reOrgHeight) + + _ = try darksideService.darksideSetState(darksideState, metadata: Metadata()) + } + + func setLatestHeight(_ latestHeight: BlockHeight) throws { + var state = DarksideState() + state.reorgHeight = 0 + state.latestHeight = UInt64(latestHeight) + _ = try darksideService.darksideSetState(state, metadata: Metadata()) + } + + +} diff --git a/ZcashLightClientKitTests/utils/SampleLogger.swift b/ZcashLightClientKitTests/utils/SampleLogger.swift new file mode 100644 index 00000000..08e1bc77 --- /dev/null +++ b/ZcashLightClientKitTests/utils/SampleLogger.swift @@ -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: 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) + } + + +} diff --git a/ZcashLightClientKitTests/utils/Stubs.swift b/ZcashLightClientKitTests/utils/Stubs.swift index 62a7885f..00355ea0 100644 --- a/ZcashLightClientKitTests/utils/Stubs.swift +++ b/ZcashLightClientKitTests/utils/Stubs.swift @@ -219,4 +219,8 @@ class MockRustBackend: ZcashRustBackendWelding { nil } + static func decryptAndStoreTransaction(dbData: URL, tx: [UInt8]) -> Bool { + false + } + } diff --git a/ZcashLightClientKitTests/utils/Tests+Utils.swift b/ZcashLightClientKitTests/utils/Tests+Utils.swift index f2633c63..7010c7d7 100644 --- a/ZcashLightClientKitTests/utils/Tests+Utils.swift +++ b/ZcashLightClientKitTests/utils/Tests+Utils.swift @@ -18,8 +18,8 @@ class LightWalletEndpointBuilder { } class ChannelProvider { - func channel() -> SwiftGRPC.Channel { - Channel(address: Constants.address, secure: false) + func channel(secure: Bool = false) -> SwiftGRPC.Channel { + Channel(address: Constants.address, secure: secure) } }