From 52cefd7cdf977f288ae5f0bae3fbc989bc5864e6 Mon Sep 17 00:00:00 2001 From: Tom Kirkpatrick Date: Fri, 21 Sep 2018 08:55:59 +0200 Subject: [PATCH] feat(lnd): use free port for lnd Select free ports for lnd from a pool of options. By default we will use the standard lnd ports (10009 and 9735) but if those are not available then we will search for free ports in a nearby range. This allows us to have multiple instances of the wallet running and avoids possible conflicts with other instances of lnd that the user may be running. --- app/lib/lnd/neutrino.js | 28 +++++++++++++++++++++++++--- app/lib/zap/controller.js | 2 +- package.json | 1 + test/unit/lnd/neutrino.spec.js | 12 ++++++------ yarn.lock | 4 ++++ 5 files changed, 37 insertions(+), 10 deletions(-) diff --git a/app/lib/lnd/neutrino.js b/app/lib/lnd/neutrino.js index 6b6856e4..20f760c5 100644 --- a/app/lib/lnd/neutrino.js +++ b/app/lib/lnd/neutrino.js @@ -3,6 +3,7 @@ import split2 from 'split2' import { spawn } from 'child_process' import EventEmitter from 'events' +import getPort from 'get-port' import { mainLog, lndLog, lndLogGetLevel } from '../utils/log' import { fetchBlockHeight } from './util' import LndConfig from './config' @@ -63,9 +64,11 @@ class Neutrino extends EventEmitter { * Start the Lnd process in Neutrino mode. * @return {Number} PID of the Lnd process that was started. */ - start() { + async start() { if (this.process) { - throw new Error('Neutrino process with PID ${this.process.pid} already exists.') + return Promise.reject( + new Error('Neutrino process with PID ${this.process.pid} already exists.') + ) } mainLog.info('Starting lnd in neutrino mode') @@ -75,13 +78,30 @@ class Neutrino extends EventEmitter { mainLog.info(' > cert:', this.lndConfig.cert) mainLog.info(' > macaroon:', this.lndConfig.macaroon) + // Get a free port to use as the rpc listen address. + const rpcListen = await getPort({ + host: 'localhost', + port: [10009, 10008, 10007, 10006, 10005, 10004, 10003, 10002, 10001] + }) + this.lndConfig.host = `localhost:${rpcListen}` + + // Get a free port to use as the p2p listen address. + const p2pListen = await getPort({ + host: '0.0.0.0', + port: [9735, 9734, 9733, 9732, 9731, 9736, 9737, 9738, 9739] + }) + + //Configure lnd. const neutrinoArgs = [ `--configfile=${this.lndConfig.configPath}`, `--lnddir=${this.lndConfig.lndDir}`, + `--listen=0.0.0.0:${p2pListen}`, + `--rpclisten=localhost:${rpcListen}`, `${this.lndConfig.autopilot ? '--autopilot.active' : ''}`, `${this.lndConfig.alias ? `--alias=${this.lndConfig.alias}` : ''}` ] + // Configure neutrino backend. if (this.lndConfig.network === 'mainnet') { neutrinoArgs.push('--neutrino.connect=mainnet1-btcd.zaphq.io') // neutrinoArgs.push('--neutrino.connect=mainnet2-btcd.zaphq.io') @@ -90,12 +110,14 @@ class Neutrino extends EventEmitter { // neutrinoArgs.push('--neutrino.connect=testnet2-btcd.zaphq.io') } + // Log the final config. mainLog.info( 'Spawning Neutrino process: %s %s', this.lndConfig.binaryPath, - neutrinoArgs.join(' ') + neutrinoArgs.filter(v => v != '').join(' ') ) + // Spawn lnd process. this.process = spawn(this.lndConfig.binaryPath, neutrinoArgs) .on('error', error => { mainLog.debug('Neutrino process received "error" event with error: %s', error) diff --git a/app/lib/zap/controller.js b/app/lib/zap/controller.js index a7198f51..c698ca7d 100644 --- a/app/lib/zap/controller.js +++ b/app/lib/zap/controller.js @@ -353,7 +353,7 @@ class ZapController { this.sendMessage('lndCfilterHeight', Number(height)) }) - this.neutrino.start() + return this.neutrino.start() } /** diff --git a/package.json b/package.json index e43a4b86..1a21fe48 100644 --- a/package.json +++ b/package.json @@ -292,6 +292,7 @@ "electron-is-dev": "^0.3.0", "electron-store": "^2.0.0", "font-awesome": "^4.7.0", + "get-port": "^4.0.0", "history": "^4.7.2", "javascript-state-machine": "^3.1.0", "jstimezonedetect": "^1.0.6", diff --git a/test/unit/lnd/neutrino.spec.js b/test/unit/lnd/neutrino.spec.js index 4c80d918..f9450002 100644 --- a/test/unit/lnd/neutrino.spec.js +++ b/test/unit/lnd/neutrino.spec.js @@ -179,9 +179,9 @@ describe('Neutrino', function() { describe('.start', () => { describe('called when neutrino is not running', () => { - beforeEach(() => { + beforeEach(async () => { this.neutrino = new Neutrino(new LndConfig()) - this.neutrino.start() + await this.neutrino.start() }) it('should set the subprocess object on the `process` property', () => { expect(this.neutrino.process.pid).toBeDefined() @@ -193,10 +193,10 @@ describe('Neutrino', function() { this.neutrino = new Neutrino(new LndConfig()) this.neutrino.process = mockSpawn() }) - it('should throw an error', () => { - expect(() => { - this.neutrino.start() - }).toThrow() + it('should throw an error', async () => { + await expect(this.neutrino.start()).rejects.toThrowErrorMatchingInlineSnapshot( + `"Neutrino process with PID \${this.process.pid} already exists."` + ) }) }) }) diff --git a/yarn.lock b/yarn.lock index d124a614..eafe70c2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5571,6 +5571,10 @@ get-port@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/get-port/-/get-port-3.2.0.tgz#dd7ce7de187c06c8bf353796ac71e099f0980ebc" +get-port@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/get-port/-/get-port-4.0.0.tgz#373c85960138ee20027c070e3cb08019fea29816" + get-stdin@5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-5.0.1.tgz#122e161591e21ff4c52530305693f20e6393a398"