Browse Source

fix(grpc): ensure connection to lnd on init

Consolidate the flow for local and remote wallet connections for
consolidate. When unlocking a local wallet wait for the macaroon to be
generated before we try to use it.
renovate/lint-staged-8.x
Tom Kirkpatrick 6 years ago
parent
commit
42a956a1a8
No known key found for this signature in database GPG Key ID: 72203A8EC5967EA8
  1. 4
      app/containers/Root.js
  2. 18
      app/lib/lnd/lightning.js
  3. 36
      app/lib/lnd/util.js
  4. 4
      app/lib/lnd/walletUnlocker.js
  5. 2
      app/lib/lnd/walletUnlockerMethods/index.js
  6. 2
      app/lib/zap/controller.js
  7. 10
      app/reducers/ipc.js
  8. 28
      app/reducers/lnd.js
  9. 58
      app/reducers/onboarding.js

4
app/containers/Root.js

@ -225,8 +225,8 @@ const Root = ({
return <Syncing {...syncingProps} />
}
// Don't launch the app without a connection to the lightning wallet gRPC interface.
if (!lnd.lightningGrpcActive) {
// Don't launch the app without a connection to lnd.
if (!lnd.lightningGrpcActive && !lnd.walletUnlockerGrpcActive) {
return <LoadingBolt />
}

18
app/lib/lnd/lightning.js

@ -5,7 +5,7 @@ import { loadSync } from '@grpc/proto-loader'
import { BrowserWindow } from 'electron'
import StateMachine from 'javascript-state-machine'
import LndConfig from './config'
import { getDeadline, validateHost, createSslCreds, createMacaroonCreds } from './util'
import { getDeadline, validateHost, createSslCreds, createMacaroonCreds, waitForFile } from './util'
import methods from './methods'
import { mainLog } from '../utils/log'
import subscribeToTransactions from './subscribe/transactions'
@ -63,10 +63,14 @@ class Lightning {
*/
async onBeforeConnect() {
mainLog.info('Connecting to Lightning gRPC service')
const { rpcProtoPath, host, cert, macaroon } = this.lndConfig
const { rpcProtoPath, host, cert, macaroon, type } = this.lndConfig
// Verify that the host is valid before creating a gRPC client that is connected to it.
return validateHost(host)
return (
validateHost(host)
// If we are trying to connect to the internal lnd, wait upto 20 seconds for the macaroon to be generated.
.then(() => (type === 'local' ? waitForFile(macaroon, 20000) : Promise.resolve()))
// Attempt to connect using the supplied credentials.
.then(async () => {
// Load the gRPC proto file.
// The following options object closely approximates the existing behavior of grpc.load.
@ -93,9 +97,9 @@ class Lightning {
// Create a new gRPC client instance.
this.service = new rpc.lnrpc.Lightning(host, credentials)
// Wait for the gRPC connection to be established.
// Wait upto 20 seconds for the gRPC connection to be established.
return new Promise((resolve, reject) => {
this.service.waitForReady(getDeadline(10), err => {
this.service.waitForReady(getDeadline(20), err => {
if (err) {
return reject(err)
}
@ -103,11 +107,15 @@ class Lightning {
})
})
})
// Once connected, make a call to getInfo to verify that we can make successful calls.
.then(() => getInfo(this.service))
.catch(err => {
if (this.service) {
this.service.close()
}
throw err
})
)
}
/**

36
app/lib/lnd/util.js

@ -182,3 +182,39 @@ export const createMacaroonCreds = async macaroonPath => {
callback(null, metadata)
)
}
/**
* Wait for a file to exist.
* @param {String} filepath
*/
export const waitForFile = (filepath, timeout = 1000) => {
let timeoutId
let intervalId
// Promise A rejects after the timeout has passed.
let promiseA = new Promise((resolve, reject) => {
timeoutId = setTimeout(() => {
mainLog.debug('deadline (%sms) exceeded before file (%s) was found', timeout, filepath)
clearInterval(intervalId)
clearTimeout(timeoutId)
reject(new Error(`Unable to find file: ${filepath}`))
}, timeout)
})
// Promise B resolves when the file has been found.
let promiseB = new Promise(resolve => {
let intervalId = setInterval(() => {
mainLog.debug('waiting for file: %s', filepath)
if (!fs.existsSync(filepath)) {
return
}
mainLog.debug('found file: %s', filepath)
clearInterval(intervalId)
clearTimeout(timeoutId)
resolve()
}, 200)
})
// Let's race our promises.
return Promise.race([promiseA, promiseB])
}

4
app/lib/lnd/walletUnlocker.js

@ -70,9 +70,11 @@ class WalletUnlocker {
// Wait for the gRPC connection to be established.
return new Promise((resolve, reject) => {
this.service.waitForReady(getDeadline(10), err => {
this.service.waitForReady(getDeadline(20), err => {
if (err) {
if (this.service) {
this.service.close()
}
return reject(err)
}
return resolve()

2
app/lib/lnd/walletUnlockerMethods/index.js

@ -30,7 +30,7 @@ export default function(walletUnlocker, log, event, msg, data, lndConfig) {
case 'initWallet':
walletController
.initWallet(walletUnlocker, data)
.then(() => event.sender.send('finishOnboarding'))
.then(() => event.sender.send('walletCreated'))
.catch(error => log.error('initWallet:', error))
break
default:

2
app/lib/zap/controller.js

@ -166,7 +166,7 @@ class ZapController {
mainLog.info(' > macaroon:', this.lndConfig.macaroon)
return this.startLightningWallet()
.then(() => this.sendMessage('finishOnboarding'))
.then(() => this.sendMessage('walletConnected'))
.catch(e => {
const errors = {}
// There was a problem connectig to the host.

10
app/reducers/ipc.js

@ -4,7 +4,8 @@ import {
currentBlockHeight,
lndBlockHeight,
lndCfilterHeight,
lightningGrpcActive
lightningGrpcActive,
walletUnlockerGrpcActive
} from './lnd'
import { receiveInfo } from './info'
import { receiveAddress } from './address'
@ -47,11 +48,11 @@ import { receiveDescribeNetwork, receiveQueryRoutes, receiveInvoiceAndQueryRoute
import {
startOnboarding,
startLndError,
walletUnlockerGrpcActive,
receiveSeed,
receiveSeedError,
finishOnboarding,
walletCreated,
walletUnlocked,
walletConnected,
unlockWalletError
} from './onboarding'
@ -118,8 +119,9 @@ const ipc = createIpc({
walletUnlockerGrpcActive,
receiveSeed,
receiveSeedError,
finishOnboarding,
walletCreated,
walletUnlocked,
walletConnected,
unlockWalletError
})

28
app/reducers/lnd.js

@ -4,6 +4,8 @@ import { showNotification } from 'lib/utils/notifications'
import { fetchTicker } from './ticker'
import { fetchBalance } from './balance'
import { fetchInfo, setHasSynced } from './info'
import { lndWalletStarted, lndWalletUnlockerStarted } from './onboarding'
// ------------------------------------
// Constants
// ------------------------------------
@ -16,6 +18,7 @@ export const RECEIVE_CURRENT_BLOCK_HEIGHT = 'RECEIVE_CURRENT_BLOCK_HEIGHT'
export const RECEIVE_LND_BLOCK_HEIGHT = 'RECEIVE_LND_BLOCK_HEIGHT'
export const RECEIVE_LND_CFILTER_HEIGHT = 'RECEIVE_LND_CFILTER_HEIGHT'
export const SET_WALLET_UNLOCKER_ACTIVE = 'SET_WALLET_UNLOCKER_ACTIVE'
export const SET_LIGHTNING_WALLET_ACTIVE = 'SET_LIGHTNING_WALLET_ACTIVE'
// ------------------------------------
@ -60,9 +63,20 @@ export const lndSyncStatus = (event, status) => (dispatch, getState) => {
}
}
// Connected to Lightning gRPC interface (lnd wallet is connected and unlocked)
export const lightningGrpcActive = () => dispatch => {
dispatch(fetchInfo())
dispatch({ type: SET_LIGHTNING_WALLET_ACTIVE })
// Let the onboarding process know that wallet is active.
dispatch(lndWalletStarted())
}
// Connected to WalletUnlocker gRPC interface (lnd is ready to unlock or create wallet)
export const walletUnlockerGrpcActive = () => dispatch => {
dispatch({ type: SET_WALLET_UNLOCKER_ACTIVE })
// Let the onboarding process know that the wallet unlocker has started.
dispatch(lndWalletUnlockerStarted())
}
// Receive IPC event for current height.
@ -96,7 +110,16 @@ const ACTION_HANDLERS = {
[RECEIVE_LND_BLOCK_HEIGHT]: (state, { lndBlockHeight }) => ({ ...state, lndBlockHeight }),
[RECEIVE_LND_CFILTER_HEIGHT]: (state, { lndCfilterHeight }) => ({ ...state, lndCfilterHeight }),
[SET_LIGHTNING_WALLET_ACTIVE]: state => ({ ...state, lightningGrpcActive: true })
[SET_WALLET_UNLOCKER_ACTIVE]: state => ({
...state,
walletUnlockerGrpcActive: true,
lightningGrpcActive: false
}),
[SET_LIGHTNING_WALLET_ACTIVE]: state => ({
...state,
lightningGrpcActive: true,
walletUnlockerGrpcActive: false
})
}
// ------------------------------------
@ -104,6 +127,7 @@ const ACTION_HANDLERS = {
// ------------------------------------
const initialState = {
syncStatus: 'pending',
walletUnlockerGrpcActive: false,
lightningGrpcActive: false,
blockHeight: 0,
lndBlockHeight: 0,

58
app/reducers/onboarding.js

@ -1,6 +1,7 @@
import { createSelector } from 'reselect'
import { ipcRenderer } from 'electron'
import get from 'lodash.get'
import { fetchInfo } from './info'
// ------------------------------------
// Constants
@ -178,13 +179,6 @@ export function changeStep(step) {
}
}
export function setStartLndError(errors) {
return {
type: SET_START_LND_ERROR,
errors
}
}
export function startLnd(options) {
// once the user submits the data needed to start LND we will alert the app that it should start LND
ipcRenderer.send('startLnd', options)
@ -194,6 +188,19 @@ export function startLnd(options) {
}
}
export function lndStarted() {
return {
type: LND_STARTED
}
}
export function setStartLndError(errors) {
return {
type: SET_START_LND_ERROR,
errors
}
}
export function setReEnterSeedIndexes() {
// we only want the user to have to verify 3 random indexes from the seed they were just given
const INDEX_AMOUNT = 3
@ -216,6 +223,25 @@ export function setReEnterSeedIndexes() {
}
}
/**
* As soon as we have an active connection to a WalletUnlocker service, attempt to generate a new seed which kicks off
* the process of creating or unlocking a wallet.
*/
export const lndWalletUnlockerStarted = () => dispatch => {
dispatch(lndStarted())
ipcRenderer.send('walletUnlocker', { msg: 'genSeed' })
dispatch({ type: FETCH_SEED })
}
/**
* As soon as we have an active connection to an unlocked wallet, fetch the wallet info so that we have the key data as
* early as possible.
*/
export const lndWalletStarted = () => dispatch => {
dispatch(lndStarted())
dispatch(fetchInfo())
}
export const submitNewWallet = (
wallet_password,
cipher_seed_mnemonic,
@ -279,13 +305,6 @@ export const startLndError = (event, errors) => (dispatch, getState) => {
}
}
// Listener from after the LND walletUnlocker has started
export const walletUnlockerGrpcActive = () => dispatch => {
dispatch({ type: LND_STARTED })
ipcRenderer.send('walletUnlocker', { msg: 'genSeed' })
dispatch({ type: FETCH_SEED })
}
export const createWallet = () => dispatch => {
ipcRenderer.send('walletUnlocker', { msg: 'genSeed' })
dispatch({ type: CHANGE_STEP, step: 4 })
@ -317,12 +336,23 @@ export const unlockWallet = wallet_password => dispatch => {
dispatch({ type: UNLOCKING_WALLET })
}
export const walletCreated = () => dispatch => {
dispatch({ type: WALLET_UNLOCKED })
dispatch({ type: ONBOARDING_FINISHED })
ipcRenderer.send('startLightningWallet')
}
export const walletUnlocked = () => dispatch => {
dispatch({ type: WALLET_UNLOCKED })
dispatch({ type: ONBOARDING_FINISHED })
ipcRenderer.send('startLightningWallet')
}
export const walletConnected = () => dispatch => {
dispatch({ type: WALLET_UNLOCKED })
dispatch({ type: ONBOARDING_FINISHED })
}
export const unlockWalletError = () => dispatch => {
dispatch({ type: SET_UNLOCK_WALLET_ERROR })
}

Loading…
Cancel
Save