From 251b4c7aa382779355753a268a3b802bb69a641a Mon Sep 17 00:00:00 2001 From: Francisco Gindre Date: Mon, 10 Aug 2020 19:19:59 -0300 Subject: [PATCH] Error handling refactor (#170) * Improve error handling on processor and service * Error Refactoring * fix XCode12 issue * Fix Compilation errors and test * bump MIDDLE version --- Example/ZcashLightClientSample/Podfile | 2 +- Example/ZcashLightClientSample/Podfile.lock | 154 +++++++++--------- .../ZcashLightClientSample/SampleLogger.swift | 2 +- ZcashLightClientKit.podspec | 4 +- .../Processor/CompactBlockProcessor.swift | 42 +++-- .../Service/LightWalletGRPCService.swift | 107 +++++++++--- .../Service/LightWalletService.swift | 50 +++++- ZcashLightClientKit/Synchronizer.swift | 5 +- .../UIKit/Synchronizer/SDKSynchronizer.swift | 23 ++- .../CompactBlockReorgTests.swift | 4 +- .../utils/SampleLogger.swift | 2 +- ZcashLightClientKitTests/utils/Stubs.swift | 10 +- 12 files changed, 263 insertions(+), 142 deletions(-) diff --git a/Example/ZcashLightClientSample/Podfile b/Example/ZcashLightClientSample/Podfile index 134da991..f06d307f 100644 --- a/Example/ZcashLightClientSample/Podfile +++ b/Example/ZcashLightClientSample/Podfile @@ -6,7 +6,7 @@ target 'ZcashLightClientSample' do pod "KRProgressHUD" # Pods for ZcashLightClientSample pod 'ZcashLightClientKit', :path => '../../', :testspecs => ['Tests'] - pod 'gRPC-Swift', '~> 1.0.0-alpha.11' + pod 'gRPC-Swift', '= 1.0.0-alpha.17' pod 'PaginatedTableView' pod 'NotificationBubbles' pod 'MnemonicSwift' diff --git a/Example/ZcashLightClientSample/Podfile.lock b/Example/ZcashLightClientSample/Podfile.lock index 691c33b0..fb9fda4c 100644 --- a/Example/ZcashLightClientSample/Podfile.lock +++ b/Example/ZcashLightClientSample/Podfile.lock @@ -1,78 +1,78 @@ PODS: - - CGRPCZlib (1.0.0-alpha.14) - - CNIOAtomics (2.18.0) - - CNIOBoringSSL (2.7.4) - - CNIOBoringSSLShims (2.7.4): - - CNIOBoringSSL (= 2.7.4) - - CNIODarwin (2.18.0) - - CNIOHTTPParser (2.18.0) - - CNIOLinux (2.18.0) - - CNIOSHA1 (2.18.0) - - gRPC-Swift (1.0.0-alpha.14): - - CGRPCZlib (= 1.0.0-alpha.14) + - CGRPCZlib (1.0.0-alpha.17) + - CNIOAtomics (2.20.2) + - CNIOBoringSSL (2.9.0) + - CNIOBoringSSLShims (2.9.0): + - CNIOBoringSSL (= 2.9.0) + - CNIODarwin (2.20.2) + - CNIOHTTPParser (2.20.2) + - CNIOLinux (2.20.2) + - CNIOSHA1 (2.20.2) + - gRPC-Swift (1.0.0-alpha.17): + - CGRPCZlib (= 1.0.0-alpha.17) - Logging (< 2, >= 1.2.0) - - SwiftNIO (< 3, >= 2.18.0) + - SwiftNIO (< 3, >= 2.19.0) - SwiftNIOHTTP2 (< 2, >= 1.12.2) - - SwiftNIOSSL (< 3, >= 2.7.4) + - SwiftNIOSSL (< 3, >= 2.8.0) - SwiftNIOTransportServices (< 2, >= 1.6.0) - SwiftProtobuf (< 2, >= 1.9.0) - KRActivityIndicatorView (3.0.5) - KRProgressHUD (3.4.5): - KRActivityIndicatorView (= 3.0.5) - - Logging (1.2.0) + - Logging (1.4.0) - MnemonicSwift (1.0.0) - NotificationBubbles (0.1.1) - PaginatedTableView (1.0.1) - SQLite.swift (0.12.2): - SQLite.swift/standard (= 0.12.2) - SQLite.swift/standard (0.12.2) - - SwiftNIO (2.18.0): - - CNIOAtomics (= 2.18.0) - - CNIODarwin (= 2.18.0) - - CNIOLinux (= 2.18.0) - - CNIOSHA1 (= 2.18.0) - - SwiftNIOConcurrencyHelpers (= 2.18.0) - - SwiftNIOConcurrencyHelpers (2.18.0): - - CNIOAtomics (= 2.18.0) - - SwiftNIOFoundationCompat (2.18.0): - - SwiftNIO (= 2.18.0) - - SwiftNIOHPACK (1.12.2): - - SwiftNIO (= 2.18.0) - - SwiftNIOConcurrencyHelpers (= 2.18.0) - - SwiftNIOHTTP1 (= 2.18.0) - - SwiftNIOHTTP1 (2.18.0): - - CNIOHTTPParser (= 2.18.0) - - SwiftNIO (= 2.18.0) - - SwiftNIOConcurrencyHelpers (= 2.18.0) - - SwiftNIOHTTP2 (1.12.2): - - SwiftNIO (= 2.18.0) - - SwiftNIOConcurrencyHelpers (= 2.18.0) - - SwiftNIOHPACK (= 1.12.2) - - SwiftNIOHTTP1 (= 2.18.0) - - SwiftNIOTLS (= 2.18.0) - - SwiftNIOSSL (2.7.4): - - CNIOBoringSSL (= 2.7.4) - - CNIOBoringSSLShims (= 2.7.4) - - SwiftNIO (= 2.18.0) - - SwiftNIOConcurrencyHelpers (= 2.18.0) - - SwiftNIOTLS (= 2.18.0) - - SwiftNIOTLS (2.18.0): - - SwiftNIO (= 2.18.0) - - SwiftNIOTransportServices (1.6.0): - - SwiftNIO (~> 2.0) - - SwiftNIOConcurrencyHelpers (~> 2.0) - - SwiftNIOFoundationCompat (~> 2.0) - - SwiftNIOTLS (~> 2.0) - - SwiftProtobuf (1.9.0) + - SwiftNIO (2.20.2): + - CNIOAtomics (= 2.20.2) + - CNIODarwin (= 2.20.2) + - CNIOLinux (= 2.20.2) + - CNIOSHA1 (= 2.20.2) + - SwiftNIOConcurrencyHelpers (= 2.20.2) + - SwiftNIOConcurrencyHelpers (2.20.2): + - CNIOAtomics (= 2.20.2) + - SwiftNIOFoundationCompat (2.20.2): + - SwiftNIO (= 2.20.2) + - SwiftNIOHPACK (1.13.0): + - SwiftNIO (< 3, >= 2.19.0) + - SwiftNIOConcurrencyHelpers (< 3, >= 2.19.0) + - SwiftNIOHTTP1 (< 3, >= 2.19.0) + - SwiftNIOHTTP1 (2.20.2): + - CNIOHTTPParser (= 2.20.2) + - SwiftNIO (= 2.20.2) + - SwiftNIOConcurrencyHelpers (= 2.20.2) + - SwiftNIOHTTP2 (1.13.0): + - SwiftNIO (< 3, >= 2.19.0) + - SwiftNIOConcurrencyHelpers (< 3, >= 2.19.0) + - SwiftNIOHPACK (= 1.13.0) + - SwiftNIOHTTP1 (< 3, >= 2.19.0) + - SwiftNIOTLS (< 3, >= 2.19.0) + - SwiftNIOSSL (2.9.0): + - CNIOBoringSSL (= 2.9.0) + - CNIOBoringSSLShims (= 2.9.0) + - SwiftNIO (< 3, >= 2.19.0) + - SwiftNIOConcurrencyHelpers (< 3, >= 2.19.0) + - SwiftNIOTLS (< 3, >= 2.19.0) + - SwiftNIOTLS (2.20.2): + - SwiftNIO (= 2.20.2) + - SwiftNIOTransportServices (1.8.0): + - SwiftNIO (< 3, >= 2.19.0) + - SwiftNIOConcurrencyHelpers (< 3, >= 2.19.0) + - SwiftNIOFoundationCompat (< 3, >= 2.19.0) + - SwiftNIOTLS (< 3, >= 2.19.0) + - SwiftProtobuf (1.11.0) - ZcashLightClientKit (0.5.3): - - gRPC-Swift (~> 1.0.0-alpha.11) + - gRPC-Swift (~> 1.0.0-alpha.17) - SQLite.swift (~> 0.12.2) - ZcashLightClientKit/Tests (0.5.3): - - gRPC-Swift (~> 1.0.0-alpha.11) + - gRPC-Swift (~> 1.0.0-alpha.11, ~> 1.0.0-alpha.17) - SQLite.swift (~> 0.12.2) DEPENDENCIES: - - gRPC-Swift (~> 1.0.0-alpha.11) + - gRPC-Swift (= 1.0.0-alpha.17) - KRProgressHUD - MnemonicSwift - NotificationBubbles @@ -114,34 +114,34 @@ EXTERNAL SOURCES: :path: "../../" SPEC CHECKSUMS: - CGRPCZlib: 06247b0687f3a3edbfbfb204d53721c3ba262a34 - CNIOAtomics: b6053043649c8b9afbf2560d9d64899d5d7ce368 - CNIOBoringSSL: 21bbd36e5a68c9d2ea1aea7bfea10eea60d25a82 - CNIOBoringSSLShims: e9e57b1fd0e060a25f2f3b0b944cc84dd272368f - CNIODarwin: 2e814fe13ee1b16a6d7603affbd95b3fbf1e65b1 - CNIOHTTPParser: 215fe669981deb6b7e4533b0b42a01e5c165d8ac - CNIOLinux: 45e91eaba50bb850a48135aabbd7671625f15188 - CNIOSHA1: f8f5b0c8cbd067c195d1b44ab237927765bd4914 - gRPC-Swift: fb8ff0d8cdd5a020c170ba827f50c393ae31e307 + CGRPCZlib: 245283cd656bf0c309a48a887c27865d6f54f7ef + CNIOAtomics: 7daf6423d00650104436c5f8a2a341679104ae83 + CNIOBoringSSL: fb66746214b40dded1a8ddf2e078af0eff07bfb8 + CNIOBoringSSLShims: c7a6e1e29088739872e51f70f8305beadfa6ca61 + CNIODarwin: 65509d8e6a65a3f3f7bf5b09455cbabd535f48f3 + CNIOHTTPParser: ec6aa14852274b383a52bacd4d6bb3d2432a50e9 + CNIOLinux: 380e2601f1f32f8f32a78437c5f4f5a688205e2b + CNIOSHA1: 5cc4fc5054beeb21c8e0017ad6bf559ce8095c10 + gRPC-Swift: 871cf2df24d9f5424763ae5af7248ed26a1b1314 KRActivityIndicatorView: 912bc0413d98340f384b12a4e1e6aa8f42fb377d KRProgressHUD: df4cdc3a1baf708d4276a089ee34d133fa9a6f3e - Logging: 7838d379d234d7e5ae1265fa02804a9084caf04c + Logging: beeb016c9c80cf77042d62e83495816847ef108b MnemonicSwift: d743ec3dcc1cdce706a1876dba0b6250ad1ef90d NotificationBubbles: 91ee10deee54f35b5d49a161e1cb3d788ab5d011 PaginatedTableView: 294d9831d5ddf5c0dcfbb4a281d4fcfb8d12f55a SQLite.swift: d2b4642190917051ce6bd1d49aab565fe794eea3 - SwiftNIO: c17d311e9006fc10c1d17fbc6a7a9cb21aa46368 - SwiftNIOConcurrencyHelpers: 69762a04ed76d7254012e1f17787ee162d2cf764 - SwiftNIOFoundationCompat: 9af7b2e265c4d55eb8599a515d63784c2691dc39 - SwiftNIOHPACK: 871e4e893ecc1aeaf74435b73c13240f50d6853c - SwiftNIOHTTP1: 67eafb3f27c3aee867dc4c0ec947e2ab71f1e2ce - SwiftNIOHTTP2: c7ce23b714868de63dae083478b40da20d933e08 - SwiftNIOSSL: 0f28ad98a39b4c625196c2c8f154a4fded66bd96 - SwiftNIOTLS: 78c6d6798ea29f7eb0a4e1214a00bc8f07e7390c - SwiftNIOTransportServices: 775bcda101a0d921feb69d79b5884acfce0fc0f2 - SwiftProtobuf: ecbec1be9036d15655f6b3443a1c4ea693c97932 - ZcashLightClientKit: 45c910a419418feb081e53d179875f740091812e + SwiftNIO: a3c1b37da3e97ce44b3cfafc61c05fe0cac540b8 + SwiftNIOConcurrencyHelpers: cd7afc3d1f1ceaf55bbd030e7d1a469d3024db92 + SwiftNIOFoundationCompat: 573f44d5b24f7ead54b6b111264c0a420fc6eccf + SwiftNIOHPACK: 0346ad2091511232c8cf19a6183b9c96ce33baf3 + SwiftNIOHTTP1: b54fae8700b647e908597aa7ed3370389f634b87 + SwiftNIOHTTP2: 8a0f333b99f9283f457762f58baa30acfee354cb + SwiftNIOSSL: 3e67b565a567c20ed38586ae0b3566f190776a95 + SwiftNIOTLS: 9225a214c2483873406322d83c9db3731d8350e9 + SwiftNIOTransportServices: e69704b2d41b68a531bf3aa0177fec81ba1361e6 + SwiftProtobuf: f889fe5772f90ef7d7b8aac352d1fddf39650713 + ZcashLightClientKit: 52c03806e721784c91b87a2ab038ee302e1635b9 -PODFILE CHECKSUM: 191070101f577589f53b033656190b612695c5f7 +PODFILE CHECKSUM: 0055c060ebfda59d4243e428eedc1db06c245f19 COCOAPODS: 1.9.3 diff --git a/Example/ZcashLightClientSample/ZcashLightClientSample/SampleLogger.swift b/Example/ZcashLightClientSample/ZcashLightClientSample/SampleLogger.swift index 00e0bba7..e385269c 100644 --- a/Example/ZcashLightClientSample/ZcashLightClientSample/SampleLogger.swift +++ b/Example/ZcashLightClientSample/ZcashLightClientSample/SampleLogger.swift @@ -10,7 +10,7 @@ import Foundation import ZcashLightClientKit import os -class SampleLogger: Logger { +class SampleLogger: ZcashLightClientKit.Logger { enum LogLevel: Int { case debug case error diff --git a/ZcashLightClientKit.podspec b/ZcashLightClientKit.podspec index 8912b351..80da2e96 100644 --- a/ZcashLightClientKit.podspec +++ b/ZcashLightClientKit.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'ZcashLightClientKit' - s.version = '0.5.3' + s.version = '0.6.0' s.summary = 'Zcash Light Client wallet SDK for iOS' s.description = <<-DESC @@ -19,7 +19,7 @@ Pod::Spec.new do |s| s.module_map = 'ZcashLightClientKit.modulemap' s.swift_version = '5.1' s.ios.deployment_target = '12.0' - s.dependency 'gRPC-Swift', '~> 1.0.0-alpha.11' + s.dependency 'gRPC-Swift', '~> 1.0.0-alpha.17' s.dependency 'SQLite.swift', '~> 0.12.2' s.ios.vendored_libraries = 'lib/libzcashlc.a' s.preserve_paths = ['Scripts', 'rust','docs','Cargo.*','ZcashLightClientKit/Stencil'] diff --git a/ZcashLightClientKit/Block/Processor/CompactBlockProcessor.swift b/ZcashLightClientKit/Block/Processor/CompactBlockProcessor.swift index 68567659..d454a048 100644 --- a/ZcashLightClientKit/Block/Processor/CompactBlockProcessor.swift +++ b/ZcashLightClientKit/Block/Processor/CompactBlockProcessor.swift @@ -16,10 +16,13 @@ public enum CompactBlockProcessorError: Error { case invalidConfiguration case missingDbPath(path: String) case dataDbInitFailed(path: String) - case connectionError(message: String) + case connectionError(underlyingError: Error) case grpcError(statusCode: Int, message: String) + case connectionTimeout case generalError(message: String) case maxAttemptsReached(attempts: Int) + case unspecifiedError(underlyingError: Error) + case criticalError } /** CompactBlockProcessor notification userInfo object keys. @@ -686,16 +689,9 @@ public class CompactBlockProcessor { NotificationCenter.default.post(name: Notification.Name.blockProcessorFailed, object: self, userInfo: [CompactBlockProcessorNotificationKey.error: mapError(err)]) } // TODO: encapsulate service errors better - func mapError(_ error: Error) -> Error { + func mapError(_ error: Error) -> CompactBlockProcessorError { if let lwdError = error as? LightWalletServiceError { - switch lwdError { - case .failed(let statusCode, let message): - return CompactBlockProcessorError.connectionError(message: "Connection failed - Status code: \(statusCode) - message: \(message)") - case .invalidBlock: - return CompactBlockProcessorError.generalError(message: "Invalid block: \(lwdError)") - default: - return CompactBlockProcessorError.generalError(message: "Error: \(lwdError)") - } + return lwdError.mapToProcessorError() } else if let rpcError = error as? GRPC.GRPCStatus { switch rpcError { case .ok: @@ -707,7 +703,7 @@ public class CompactBlockProcessor { } } - return error + return .unspecifiedError(underlyingError: error) } } @@ -721,6 +717,30 @@ public extension CompactBlockProcessor.Configuration { } } +extension LightWalletServiceError { + func mapToProcessorError() -> CompactBlockProcessorError { + switch self { + case .failed(let statusCode, let message): + return CompactBlockProcessorError.grpcError(statusCode: statusCode, message: message) + case .invalidBlock: + return CompactBlockProcessorError.generalError(message: "\(self)") + case .generalError(let message): + return CompactBlockProcessorError.generalError(message: message) + case .sentFailed(let error): + return CompactBlockProcessorError.connectionError(underlyingError: error) + case .genericError(let error): + return CompactBlockProcessorError.unspecifiedError(underlyingError: error) + case .timeOut: + return CompactBlockProcessorError.connectionTimeout + case .criticalError: + return CompactBlockProcessorError.criticalError + case .userCancelled: + return CompactBlockProcessorError.connectionTimeout + case .unknown: + return CompactBlockProcessorError.unspecifiedError(underlyingError: self) + } + } +} extension CompactBlockProcessor.State: Equatable { public static func == (lhs: CompactBlockProcessor.State, rhs: CompactBlockProcessor.State) -> Bool { switch lhs { diff --git a/ZcashLightClientKit/Service/LightWalletGRPCService.swift b/ZcashLightClientKit/Service/LightWalletGRPCService.swift index 4f56d8d8..bc7a76a4 100644 --- a/ZcashLightClientKit/Service/LightWalletGRPCService.swift +++ b/ZcashLightClientKit/Service/LightWalletGRPCService.swift @@ -9,8 +9,24 @@ import Foundation import GRPC import NIO +import NIOHPACK public typealias Channel = GRPC.GRPCChannel + +extension TimeAmount { + static let singleCallTimeout = TimeAmount.seconds(10) + static let streamingCallTimeout = TimeAmount.seconds(90) +} +extension CallOptions { + static var lwdCall: CallOptions { + CallOptions(customMetadata: HPACKHeaders(), + timeLimit: .timeout(.singleCallTimeout), + messageEncoding: .disabled, + requestIDProvider: .autogenerated, + requestIDHeader: nil, + cacheable: false) + } +} /** Swift GRPC implementation of Lightwalletd service */ public class LightWalletGRPCService { @@ -22,7 +38,7 @@ public class LightWalletGRPCService { public init(channel: Channel) { self.channel = channel - compactTxStreamer = CompactTxStreamerClient(channel: self.channel) + compactTxStreamer = CompactTxStreamerClient(channel: self.channel, defaultCallOptions: CallOptions.lwdCall) } public convenience init(endpoint: LightWalletEndpoint) { @@ -59,9 +75,14 @@ extension LightWalletGRPCService: LightWalletService { public func fetchTransaction(txId: Data) throws -> TransactionEntity { var txFilter = TxFilter() txFilter.hash = txId - let rawTx = try compactTxStreamer.getTransaction(txFilter).response.wait() - return TransactionBuilder.createTransactionEntity(txId: txId, rawTransaction: rawTx) + do { + let rawTx = try compactTxStreamer.getTransaction(txFilter).response.wait() + + return TransactionBuilder.createTransactionEntity(txId: txId, rawTransaction: rawTx) + } catch { + throw error.mapToServiceError() + } } public func fetchTransaction(txId: Data, result: @escaping (Result) -> Void) { @@ -73,7 +94,7 @@ extension LightWalletGRPCService: LightWalletService { switch response { case .failure(let error): - result(.failure(LightWalletServiceError.genericError(error: error))) + result(.failure(error.mapToServiceError())) case .success(let rawTx): result(.success(TransactionBuilder.createTransactionEntity(txId: txId, rawTransaction: rawTx))) } @@ -88,13 +109,13 @@ extension LightWalletGRPCService: LightWalletService { response.whenComplete { (responseResult) in switch responseResult { case .failure(let e): - result(.failure(LightWalletServiceError.genericError(error: e))) + result(.failure(LightWalletServiceError.sentFailed(error: e))) case .success(let s): result(.success(s)) } } } catch { - result(.failure(LightWalletServiceError.genericError(error: error))) + result(.failure(error.mapToServiceError())) } } @@ -103,7 +124,11 @@ extension LightWalletGRPCService: LightWalletService { let rawTx = RawTransaction.with { (raw) in raw.data = spendTransaction } - return try compactTxStreamer.sendTransaction(rawTx).response.wait() + do { + return try compactTxStreamer.sendTransaction(rawTx).response.wait() + } catch { + throw error.mapToServiceError() + } } public func blockRange(_ range: CompactBlockRange) throws -> [ZcashCompactBlock] { @@ -113,18 +138,21 @@ extension LightWalletGRPCService: LightWalletService { blocks.append($0) }) - do { - _ = try response.status.wait() - } catch { - throw LightWalletServiceError.genericError(error: error) + let status = try response.status.wait() + switch status.code { + + case .ok: + do { + return try blocks.asZcashCompactBlocks() + } catch { + LoggerProxy.error("invalid block in range: \(range) - Error: \(error)") + throw LightWalletServiceError.genericError(error: error) + } + default: + throw LightWalletServiceError.mapCode(status) } - do { - return try blocks.asZcashCompactBlocks() - } catch { - LoggerProxy.error("invalid block in range: \(range) - Error: \(error)") - throw LightWalletServiceError.genericError(error: error) - } + } public func latestBlockHeight(result: @escaping (Result) -> Void) { @@ -132,19 +160,18 @@ extension LightWalletGRPCService: LightWalletService { response.whenSuccess { (blockID) in guard let blockHeight = Int(exactly: blockID.height) else { - result(.failure(LightWalletServiceError.generalError)) + result(.failure(LightWalletServiceError.generalError(message: "error creating blockheight from BlockID \(blockID)"))) return } result(.success(blockHeight)) } response.whenFailure { (error) in - result(.failure(LightWalletServiceError.genericError(error: error))) + result(.failure(error.mapToServiceError())) } } - // TODO: Make cancellable public func blockRange(_ range: CompactBlockRange, result: @escaping (Result<[ZcashCompactBlock], LightWalletServiceError>) -> Void) { queue.async { [weak self] in @@ -162,15 +189,16 @@ extension LightWalletGRPCService: LightWalletService { do { result(.success(try blocks.asZcashCompactBlocks())) } catch { - result(.failure(LightWalletServiceError.generalError)) + LoggerProxy.error("Error parsing compact blocks \(error)") + result(.failure(LightWalletServiceError.invalidBlock)) } - + default: - result(Result.failure(LightWalletServiceError.failed(statusCode: status.code.rawValue, message: status.message ?? "No Message"))) + result(.failure(.mapCode(status))) } } catch { - result(.failure(LightWalletServiceError.genericError(error: error))) + result(.failure(error.mapToServiceError())) } } @@ -184,5 +212,34 @@ extension LightWalletGRPCService: LightWalletService { } return height } - +} + +extension Error { + func mapToServiceError() -> LightWalletServiceError { + guard let grpcError = self as? GRPCStatusTransformable + else { + return LightWalletServiceError.genericError(error: self) + } + + return LightWalletServiceError.mapCode(grpcError.makeGRPCStatus()) + } +} + +extension LightWalletServiceError { + static func mapCode(_ status: GRPCStatus) -> LightWalletServiceError { + switch status.code { + + case .ok: + return LightWalletServiceError.unknown + case .cancelled: + return LightWalletServiceError.userCancelled + case .unknown: + return LightWalletServiceError.unknown + + case .deadlineExceeded: + return LightWalletServiceError.timeOut + default: + return LightWalletServiceError.genericError(error: status) + } + } } diff --git a/ZcashLightClientKit/Service/LightWalletService.swift b/ZcashLightClientKit/Service/LightWalletService.swift index 32e06e18..e2988b8b 100644 --- a/ZcashLightClientKit/Service/LightWalletService.swift +++ b/ZcashLightClientKit/Service/LightWalletService.swift @@ -14,20 +14,24 @@ import SwiftProtobuf Wrapper for errors received from a Lightwalletd endpoint */ public enum LightWalletServiceError: Error { - case generalError + case generalError(message: String) case failed(statusCode: Int, message: String) case invalidBlock - case sentFailed(sendResponse: LightWalletServiceResponse) + case sentFailed(error: Error) case genericError(error: Error) + case timeOut + case criticalError + case userCancelled + case unknown } extension LightWalletServiceError: Equatable { public static func == (lhs: Self, rhs: Self) -> Bool { switch lhs { - case .generalError: + case .generalError(let m): switch rhs { - case .generalError: - return true + case .generalError(let msg): + return m == msg default: return false } @@ -46,18 +50,46 @@ extension LightWalletServiceError: Equatable { default: return false } - case .sentFailed(let sendResponse): + case .sentFailed(_): switch rhs { - case .sentFailed(let response): - return response.errorCode == sendResponse.errorCode + case .sentFailed(_): + return true default: return false } case .genericError: return false + + case .timeOut: + switch rhs { + case .timeOut: + return true + default: + return false + } + case .criticalError: + switch rhs { + case .criticalError: + return true + default: + return false + } + case .userCancelled: + switch rhs { + case .userCancelled: + return true + default: + return false + } + case .unknown: + switch rhs { + case .unknown: + return true + default: + return false + } } } - } public protocol LightWalletServiceResponse { diff --git a/ZcashLightClientKit/Synchronizer.swift b/ZcashLightClientKit/Synchronizer.swift index 5f884f1d..071e7f57 100644 --- a/ZcashLightClientKit/Synchronizer.swift +++ b/ZcashLightClientKit/Synchronizer.swift @@ -14,10 +14,13 @@ Represents errors thrown by a Synchronizer public enum SynchronizerError: Error { case initFailed(message: String) case syncFailed - case connectionFailed(message: String) + case connectionFailed(message: Error) case generalError(message: String) case maxRetryAttemptsReached(attempts: Int) case connectionError(status: Int, message: String) + case networkTimeout + case uncategorized(underlyingError: Error) + case criticalError } /** diff --git a/ZcashLightClientKit/UIKit/Synchronizer/SDKSynchronizer.swift b/ZcashLightClientKit/UIKit/Synchronizer/SDKSynchronizer.swift index bfda4893..f465f085 100644 --- a/ZcashLightClientKit/UIKit/Synchronizer/SDKSynchronizer.swift +++ b/ZcashLightClientKit/UIKit/Synchronizer/SDKSynchronizer.swift @@ -142,15 +142,18 @@ public class SDKSynchronizer: Synchronizer { return } - try processor.start(retry: retry) + do { + try processor.start(retry: retry) + } catch { + throw mapError(error) + } } /** Stops the synchronizer - - Throws: CompactBlockProcessorError when failures occur */ - public func stop() throws { + public func stop() { defer { @@ -329,6 +332,8 @@ public class SDKSynchronizer: Synchronizer { guard let self = self else { return } if let error = notification.userInfo?[CompactBlockProcessorNotificationKey.error] as? Error { self.notifyFailure(error) + } else { + self.notifyFailure(CompactBlockProcessorError.generalError(message: "This is strange. processorFailed Call received no error message")) } self.status = .disconnected } @@ -400,9 +405,7 @@ public class SDKSynchronizer: Synchronizer { } @objc func applicationWillTerminate(_ notification: Notification) { - do { - try stop() - } catch {} + stop() } // MARK: Synchronizer methods @@ -547,9 +550,15 @@ public class SDKSynchronizer: Synchronizer { return SynchronizerError.maxRetryAttemptsReached(attempts: attempts) case .grpcError(let statusCode, let message): return SynchronizerError.connectionError(status: statusCode, message: message) + case .connectionTimeout: + return SynchronizerError.networkTimeout + case .unspecifiedError(let underlyingError): + return SynchronizerError.uncategorized(underlyingError: underlyingError) + case .criticalError: + return SynchronizerError.criticalError } } - return error + return SynchronizerError.uncategorized(underlyingError: error) } private func notifyFailure(_ error: Error) { diff --git a/ZcashLightClientKitTests/CompactBlockReorgTests.swift b/ZcashLightClientKitTests/CompactBlockReorgTests.swift index 1b892c80..75dd283c 100644 --- a/ZcashLightClientKitTests/CompactBlockReorgTests.swift +++ b/ZcashLightClientKitTests/CompactBlockReorgTests.swift @@ -79,14 +79,14 @@ class CompactBlockReorgTests: XCTestCase { } @objc func processorFailed(_ notification: Notification) { - DispatchQueue.main.sync { + XCTAssertNotNil(notification.userInfo) if let error = notification.userInfo?["error"] { XCTFail("CompactBlockProcessor failed with Error: \(error)") } else { XCTFail("CompactBlockProcessor failed") } - } + } fileprivate func startProcessing() { diff --git a/ZcashLightClientKitTests/utils/SampleLogger.swift b/ZcashLightClientKitTests/utils/SampleLogger.swift index 08e1bc77..eb41eb03 100644 --- a/ZcashLightClientKitTests/utils/SampleLogger.swift +++ b/ZcashLightClientKitTests/utils/SampleLogger.swift @@ -9,7 +9,7 @@ import Foundation import os import ZcashLightClientKit -class SampleLogger: Logger { +class SampleLogger: ZcashLightClientKit.Logger { enum LogLevel: Int { case debug case error diff --git a/ZcashLightClientKitTests/utils/Stubs.swift b/ZcashLightClientKitTests/utils/Stubs.swift index 06c93272..f1c5ac4e 100644 --- a/ZcashLightClientKitTests/utils/Stubs.swift +++ b/ZcashLightClientKitTests/utils/Stubs.swift @@ -13,7 +13,7 @@ import SwiftProtobuf class AwfulLightWalletService: MockLightWalletService { override func latestBlockHeight() throws -> BlockHeight { - throw LightWalletServiceError.generalError + throw LightWalletServiceError.criticalError } override func blockRange(_ range: CompactBlockRange) throws -> [ZcashCompactBlock] { @@ -22,20 +22,20 @@ class AwfulLightWalletService: MockLightWalletService { override func latestBlockHeight(result: @escaping (Result) -> Void) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - result(.failure(LightWalletServiceError.generalError)) + result(.failure(LightWalletServiceError.invalidBlock)) } } override func blockRange(_ range: CompactBlockRange, result: @escaping (Result<[ZcashCompactBlock], LightWalletServiceError>) -> Void) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - result(.failure(LightWalletServiceError.generalError)) + result(.failure(LightWalletServiceError.invalidBlock)) } } override func submit(spendTransaction: Data, result: @escaping(Result) -> Void) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - result(.failure(LightWalletServiceError.generalError)) + result(.failure(LightWalletServiceError.invalidBlock)) } } @@ -44,7 +44,7 @@ class AwfulLightWalletService: MockLightWalletService { */ override func submit(spendTransaction: Data) throws -> LightWalletServiceResponse { - throw LightWalletServiceError.generalError + throw LightWalletServiceError.invalidBlock } }