You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

192 lines
6.2 KiB

import split2 from 'split2'
import { spawn } from 'child_process'
import EventEmitter from 'events'
import config from '../config'
import { mainLog, lndLog, lndLogGetLevel } from '../../utils/log'
import { fetchBlockHeight } from './util'
// Sync status is currenty pending.
const NEUTRINO_SYNC_STATUS_PENDING = 'chain-sync-pending'
// Waiting for chain backend to finish synchronizing.
const NEUTRINO_SYNC_STATUS_WAITING = 'chain-sync-waiting'
// Initial sync is currently in progress.
const NEUTRINO_SYNC_STATUS_IN_PROGRESS = 'chain-sync-started'
// Initial sync has completed.
const NEUTRINO_SYNC_STATUS_COMPLETE = 'chain-sync-finished'
/**
* Wrapper class for Lnd to run and monitor it in Neutrino mode.
* @extends EventEmitter
*/
class Neutrino extends EventEmitter {
constructor(alias, autopilot) {
super()
this.alias = alias
this.autopilot = autopilot
this.process = null
this.grpcProxyStarted = false
this.walletOpened = false
this.chainSyncStatus = NEUTRINO_SYNC_STATUS_PENDING
}
/**
* Start the Lnd process in Neutrino mode.
* @return {Number} PID of the Lnd process that was started.
*/
start() {
if (this.process) {
throw new Error('Neutrino process with PID ${this.process.pid} already exists.')
}
const lndConfig = config.lnd()
mainLog.info('Starting lnd in neutrino mode')
mainLog.debug(' > lndPath', lndConfig.lndPath)
mainLog.debug(' > rpcProtoPath:', lndConfig.rpcProtoPath)
mainLog.debug(' > host:', lndConfig.host)
mainLog.debug(' > cert:', lndConfig.cert)
mainLog.debug(' > macaroon:', lndConfig.macaroon)
const neutrinoArgs = [
`--configfile=${lndConfig.configPath}`,
`${this.autopilot ? '--autopilot.active' : ''}`,
`${this.alias ? `--alias=${this.alias}` : ''}`
]
this.process = spawn(lndConfig.lndPath, neutrinoArgs)
.on('error', error => this.emit('error', error))
.on('close', code => {
this.emit('close', code)
this.process = null
})
// Listen for when neutrino prints odata to stderr.
this.process.stderr.pipe(split2()).on('data', line => {
if (process.env.NODE_ENV === 'development') {
lndLog[lndLogGetLevel(line)](line)
}
})
// Listen for when neutrino prints data to stdout.
this.process.stdout.pipe(split2()).on('data', line => {
if (process.env.NODE_ENV === 'development') {
lndLog[lndLogGetLevel(line)](line)
}
// gRPC started.
if (!this.grpcProxyStarted) {
if (line.includes('gRPC proxy started') && line.includes('password')) {
this.grpcProxyStarted = true
this.emit('grpc-proxy-started')
}
}
// Wallet opened.
if (!this.walletOpened) {
if (line.includes('gRPC proxy started') && !line.includes('password')) {
this.walletOpened = true
this.emit('wallet-opened')
}
}
// If the sync has already completed then we don't need to do anythibng else.
if (this.is(NEUTRINO_SYNC_STATUS_COMPLETE)) {
return
}
// Lnd waiting for backend to finish syncing.
if (this.is(NEUTRINO_SYNC_STATUS_PENDING) || this.is(NEUTRINO_SYNC_STATUS_IN_PROGRESS)) {
if (
line.includes('No sync peer candidates available') ||
line.includes('Unable to synchronize wallet to chain') ||
line.includes('Waiting for chain backend to finish sync')
) {
this.setState(NEUTRINO_SYNC_STATUS_WAITING)
}
}
// Lnd syncing has started or resumed.
if (this.is(NEUTRINO_SYNC_STATUS_PENDING) || this.is(NEUTRINO_SYNC_STATUS_WAITING)) {
const match = line.match(/Syncing to block height (\d+)/)
if (match) {
// Notify that chhain syncronisation has now started.
this.setState(NEUTRINO_SYNC_STATUS_IN_PROGRESS)
// This is the latest block that BTCd is aware of.
const btcdHeight = Number(match[1])
this.emit('got-current-block-height', btcdHeight)
// The height returned from the LND log output may not be the actual current block height (this is the case
// when BTCD is still in the middle of syncing the blockchain) so try to fetch thhe current height from from
// some block explorers just incase.
fetchBlockHeight()
.then(
height => (height > btcdHeight ? this.emit('got-current-block-height', height) : null)
)
// If we were unable to fetch from bock explorers at least we already have what BTCd gave us so just warn.
.catch(err => mainLog.warn(`Unable to fetch block height: ${err.message}`))
}
}
// Lnd as received some updated block data.
if (this.is(NEUTRINO_SYNC_STATUS_WAITING) || this.is(NEUTRINO_SYNC_STATUS_IN_PROGRESS)) {
let height
let match
if ((match = line.match(/Rescanned through block.+\(height (\d+)/))) {
height = match[1]
} else if ((match = line.match(/Caught up to height (\d+)/))) {
height = match[1]
} else if ((match = line.match(/Processed \d* blocks? in the last.+\(height (\d+)/))) {
height = match[1]
}
if (height) {
this.setState(NEUTRINO_SYNC_STATUS_IN_PROGRESS)
this.emit('got-lnd-block-height', height)
}
// Lnd syncing has completed.
if (line.includes('Chain backend is fully synced')) {
this.setState(NEUTRINO_SYNC_STATUS_COMPLETE)
}
}
})
return this.process
}
/**
* Stop the Lnd process.
*/
stop() {
if (this.process) {
this.process.kill()
this.process = null
}
}
/**
* Check if the current state matches the passted in state.
* @param {String} state State to compare against the current state.
* @return {Boolean} Boolean indicating if the current state matches the passed in state.
*/
is(state) {
return this.chainSyncStatus === state
}
/**
* Set the current state and emit an event to notify others if te state as canged.
* @param {String} state Target state.
*/
setState(state) {
if (state !== this.chainSyncStatus) {
this.chainSyncStatus = state
this.emit(state)
}
}
}
export default Neutrino