From 71ac6adeff39d6f8d9948b60e8f9f736c431871c Mon Sep 17 00:00:00 2001 From: Tom Kirkpatrick Date: Wed, 5 Sep 2018 11:11:09 +0200 Subject: [PATCH] fix(grpc): show error on connect to locked wallet When connecting to a remote wallet we assume that the wallet is already unlocked. In the case where it is not already unlocked, forward the error message to the UI. Fix #738 --- app/lib/lnd/lightning.js | 76 +++++++++++++++++++---------------- app/lib/lnd/walletUnlocker.js | 2 +- app/lib/zap/controller.js | 10 +++++ 3 files changed, 52 insertions(+), 36 deletions(-) diff --git a/app/lib/lnd/lightning.js b/app/lib/lnd/lightning.js index 7cdc0070..0f0e7600 100644 --- a/app/lib/lnd/lightning.js +++ b/app/lib/lnd/lightning.js @@ -11,6 +11,7 @@ import { mainLog } from '../utils/log' import subscribeToTransactions from './subscribe/transactions' import subscribeToInvoices from './subscribe/invoices' import subscribeToChannelGraph from './subscribe/channelgraph' +import { getInfo } from './methods/networkController' // Type definition for subscriptions property. type LightningSubscriptionsType = { @@ -65,43 +66,48 @@ class Lightning { const { rpcProtoPath, host, cert, macaroon } = this.lndConfig // Verify that the host is valid before creating a gRPC client that is connected to it. - return await validateHost(host).then(async () => { - // Load the gRPC proto file. - // The following options object closely approximates the existing behavior of grpc.load. - // See https://github.com/grpc/grpc-node/blob/master/packages/grpc-protobufjs/README.md - const options = { - keepCase: true, - longs: Number, - enums: String, - defaults: true, - oneofs: true - } - const packageDefinition = loadSync(rpcProtoPath, options) - - // Load gRPC package definition as a gRPC object hierarchy. - const rpc = grpc.loadPackageDefinition(packageDefinition) - - // Create ssl and macaroon credentials to use with the gRPC client. - const [sslCreds, macaroonCreds] = await Promise.all([ - createSslCreds(cert), - createMacaroonCreds(macaroon) - ]) - const credentials = grpc.credentials.combineChannelCredentials(sslCreds, macaroonCreds) - - // Create a new gRPC client instance. - this.service = new rpc.lnrpc.Lightning(host, credentials) - - // Wait for the gRPC connection to be established. - return new Promise((resolve, reject) => { - this.service.waitForReady(getDeadline(2), err => { - if (err) { - this.service.close() - return reject(err) - } - return resolve() + return validateHost(host) + .then(async () => { + // Load the gRPC proto file. + // The following options object closely approximates the existing behavior of grpc.load. + // See https://github.com/grpc/grpc-node/blob/master/packages/grpc-protobufjs/README.md + const options = { + keepCase: true, + longs: Number, + enums: String, + defaults: true, + oneofs: true + } + const packageDefinition = loadSync(rpcProtoPath, options) + + // Load gRPC package definition as a gRPC object hierarchy. + const rpc = grpc.loadPackageDefinition(packageDefinition) + + // Create ssl and macaroon credentials to use with the gRPC client. + const [sslCreds, macaroonCreds] = await Promise.all([ + createSslCreds(cert), + createMacaroonCreds(macaroon) + ]) + const credentials = grpc.credentials.combineChannelCredentials(sslCreds, macaroonCreds) + + // Create a new gRPC client instance. + this.service = new rpc.lnrpc.Lightning(host, credentials) + + // Wait for the gRPC connection to be established. + return new Promise((resolve, reject) => { + this.service.waitForReady(getDeadline(5), err => { + if (err) { + return reject(err) + } + return resolve() + }) }) }) - }) + .then(() => getInfo(this.service)) + .catch(err => { + this.service.close() + throw err + }) } /** diff --git a/app/lib/lnd/walletUnlocker.js b/app/lib/lnd/walletUnlocker.js index b712424c..d833a19d 100644 --- a/app/lib/lnd/walletUnlocker.js +++ b/app/lib/lnd/walletUnlocker.js @@ -74,7 +74,7 @@ class WalletUnlocker { // Wait for the gRPC connection to be established. return new Promise((resolve, reject) => { - this.service.waitForReady(getDeadline(2), err => { + this.service.waitForReady(getDeadline(5), err => { if (err) { this.service.close() return reject(err) diff --git a/app/lib/zap/controller.js b/app/lib/zap/controller.js index 58171686..b3592119 100644 --- a/app/lib/zap/controller.js +++ b/app/lib/zap/controller.js @@ -193,6 +193,16 @@ class ZapController { else if (e.code === 'LND_GRPC_MACAROON_ERROR') { errors.macaroon = e.message } + + // The `startLightningWallet` call attempts to call the `getInfo` method on the Lightning service in order to + // verify that it is accessible. If it is not, an error 12 is throw whcih is the gRPC code for `UNIMPLEMENTED` + // which indicates that the requested operation is not implemented or not supported/enabled in the service. + // See https://github.com/grpc/grpc-node/blob/master/packages/grpc-native-core/src/constants.js#L129 + if (e.code === 12) { + errors.host = + 'Unable to connect to host. Please ensure wallet is unlocked before connecting.' + } + // Other error codes such as UNAVAILABLE most likely indicate that there is a problem with the host. else { errors.host = `Unable to connect to host: ${e.details || e.message}`