// // ServiceHelper.swift // gRPC-PoC // // Created by Francisco Gindre on 29/08/2019. // Copyright © 2019 Electric Coin Company. All rights reserved. // import Foundation import SwiftGRPC public class LightWalletGRPCService { var queue = DispatchQueue.init(label: "LightWalletGRPCService") let channel: Channel let compactTxStreamer: CompactTxStreamerServiceClient public init(channel: Channel) { self.channel = channel compactTxStreamer = CompactTxStreamerServiceClient(channel: self.channel) } public convenience init(endpoint: LightWalletEndpoint) { self.init(host: endpoint.host, secure: endpoint.secure) } public convenience init(host: String, secure: Bool = true) { let channel = Channel(address: host, secure: secure, arguments: []) self.init(channel: channel) } func blockRange(startHeight: BlockHeight, endHeight: BlockHeight? = nil, result: @escaping (CallResult) -> Void) throws -> CompactTxStreamerGetBlockRangeCall { try compactTxStreamer.getBlockRange(BlockRange(startHeight: startHeight, endHeight: endHeight)) { result($0) } } func latestBlock() throws -> BlockID { try compactTxStreamer.getLatestBlock(ChainSpec()) } func getTx(hash: String) throws -> RawTransaction { var filter = TxFilter() filter.hash = Data(hash.utf8) return try compactTxStreamer.getTransaction(filter) } func getAllBlocksSinceSaplingLaunch(_ result: @escaping (CallResult) -> Void) throws -> CompactTxStreamerGetBlockRangeCall { try compactTxStreamer.getBlockRange(BlockRange.sinceSaplingActivation(), completion: result) } } extension LightWalletGRPCService: LightWalletService { public func submit(spendTransaction: Data, result: @escaping (Result) -> Void) { DispatchQueue.global(qos: .userInitiated).async { [weak self] in guard let self = self else { return } do { let response = try self.compactTxStreamer.sendTransaction(RawTransaction(serializedData: spendTransaction)) result(.success(response)) } catch { result(.failure(LightWalletServiceError.genericError(error: error))) } } } public func submit(spendTransaction: Data) throws -> LightWalletServiceResponse { let rawTx = RawTransaction.with { (raw) in raw.data = spendTransaction } return try compactTxStreamer.sendTransaction(rawTx) } public func blockRange(_ range: CompactBlockRange) throws -> [ZcashCompactBlock] { var blocks = [ZcashCompactBlock]() let call = try compactTxStreamer.getBlockRange(range.blockRange(), completion: { statusCode in print("finished with statusCode: \(statusCode)") }) while let block = try call.receive() { if let compactBlock = ZcashCompactBlock(compactBlock: block) { blocks.append(compactBlock) } else { throw LightWalletServiceError.invalidBlock } } return blocks } public func latestBlockHeight(result: @escaping (Result) -> Void) { do { try compactTxStreamer.getLatestBlock(ChainSpec()) { (blockID, callResult) in guard let rawHeight = blockID?.height, let blockHeight = Int(exactly: rawHeight) else { result(.failure(LightWalletServiceError.failed(statusCode: callResult.statusCode, message: callResult.statusMessage ?? "No message"))) return } result(.success(blockHeight)) } } catch { // TODO: Handle Error print(error.localizedDescription) result(.failure(LightWalletServiceError.generalError)) } } // TODO: Make cancellable public func blockRange(_ range: CompactBlockRange, result: @escaping (Result<[ZcashCompactBlock], LightWalletServiceError>) -> Void) { queue.async { var blocks = [CompactBlock]() var isSyncing = true guard let response = try? self.compactTxStreamer.getBlockRange(range.blockRange(),completion: { (callResult) in isSyncing = false if callResult.success { let code = callResult.statusCode switch code{ case .ok: do { result(.success(try blocks.asZcashCompactBlocks())) } catch { result(.failure(LightWalletServiceError.generalError)) } default: result(.failure(LightWalletServiceError.failed(statusCode: code, message: callResult.statusMessage ?? "No Message"))) } } else { result(.failure(LightWalletServiceError.generalError)) return } }) else { result(.failure(LightWalletServiceError.generalError)) return } do { var element: CompactBlock? repeat { element = try response.receive() if let e = element { blocks.append(e) } } while isSyncing && element != nil } catch { result(.failure(LightWalletServiceError.generalError)) } } } public func latestBlockHeight() throws -> BlockHeight { guard let height = try? latestBlock().compactBlockHeight() else { throw LightWalletServiceError.invalidBlock } return height } }