Browse Source

Merge pull request #658 from mrfelton/chore/update-lnd

feat(lnd): update BTCD and LND to latest versions
renovate/lint-staged-8.x
JimmyMow 6 years ago
committed by GitHub
parent
commit
28f3427d76
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 37
      app/components/Onboarding/Syncing.js
  2. 1
      app/containers/Root.js
  3. 127
      app/lib/lnd/neutrino.js
  4. 14
      app/lib/zap/controller.js
  5. 9
      app/reducers/ipc.js
  6. 40
      app/reducers/lnd.js
  7. 5
      package.json
  8. 2
      resources/lnd.conf
  9. 218
      resources/rpc.proto
  10. 171
      test/unit/lnd/neutrino.spec.js
  11. 22
      yarn.lock

37
app/components/Onboarding/Syncing.js

@ -11,7 +11,8 @@ import styles from './Syncing.scss'
class Syncing extends Component { class Syncing extends Component {
state = { state = {
timer: null, timer: null,
syncMessageDetail: null syncMessageDetail: null,
syncMessageExtraDetail: null
} }
componentWillMount() { componentWillMount() {
@ -42,9 +43,10 @@ class Syncing extends Component {
syncPercentage, syncPercentage,
address, address,
blockHeight, blockHeight,
lndBlockHeight lndBlockHeight,
lndCfilterHeight
} = this.props } = this.props
let { syncMessageDetail } = this.state let { syncMessageDetail, syncMessageExtraDetail } = this.state
const copyClicked = () => { const copyClicked = () => {
copy(address) copy(address)
@ -53,15 +55,17 @@ class Syncing extends Component {
let syncMessage let syncMessage
if (syncStatus === 'waiting') { if (syncStatus === 'waiting') {
syncMessage = 'Waiting for peers...' syncMessage = 'Waiting for peers...'
} else if (typeof syncPercentage === 'undefined' || syncPercentage <= 0) { } else if (syncStatus === 'in-progress') {
syncMessage = 'Preparing...' if (typeof syncPercentage === 'undefined' || syncPercentage <= 0) {
syncMessageDetail = null syncMessage = 'Preparing...'
} else if (syncPercentage > 0 && syncPercentage < 99) { syncMessageDetail = null
syncMessage = `${syncPercentage}%` } else if (syncPercentage) {
syncMessageDetail = `${lndBlockHeight.toLocaleString()} of ${blockHeight.toLocaleString()}` syncMessage = `${syncPercentage}%`
} else if (syncPercentage >= 99) { syncMessageDetail = `Block:
syncMessage = 'Finalizing...' ${lndBlockHeight.toLocaleString()} of ${blockHeight.toLocaleString()}`
syncMessageDetail = null syncMessageExtraDetail = `Commitment Filter:
${lndCfilterHeight.toLocaleString()} of ${blockHeight.toLocaleString()}`
}
} }
if (typeof hasSynced === 'undefined') { if (typeof hasSynced === 'undefined') {
@ -137,6 +141,12 @@ class Syncing extends Component {
{syncMessageDetail && ( {syncMessageDetail && (
<span className={styles.progressDetail}>{syncMessageDetail}</span> <span className={styles.progressDetail}>{syncMessageDetail}</span>
)} )}
{syncMessageExtraDetail && (
<span className={styles.progressDetail}>
<br />
{syncMessageExtraDetail}
</span>
)}
</section> </section>
</div> </div>
</div> </div>
@ -150,7 +160,8 @@ Syncing.propTypes = {
syncStatus: PropTypes.string.isRequired, syncStatus: PropTypes.string.isRequired,
syncPercentage: PropTypes.number, syncPercentage: PropTypes.number,
blockHeight: PropTypes.number, blockHeight: PropTypes.number,
lndBlockHeight: PropTypes.number lndBlockHeight: PropTypes.number,
lndCfilterHeight: PropTypes.number
} }
export default Syncing export default Syncing

1
app/containers/Root.js

@ -80,6 +80,7 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
blockHeight: stateProps.lnd.blockHeight, blockHeight: stateProps.lnd.blockHeight,
syncStatus: stateProps.lnd.syncStatus, syncStatus: stateProps.lnd.syncStatus,
lndBlockHeight: stateProps.lnd.lndBlockHeight, lndBlockHeight: stateProps.lnd.lndBlockHeight,
lndCfilterHeight: stateProps.lnd.lndCfilterHeight,
hasSynced: stateProps.info.hasSynced, hasSynced: stateProps.info.hasSynced,
syncPercentage: stateProps.syncPercentage, syncPercentage: stateProps.syncPercentage,
address: stateProps.address.address address: stateProps.address.address

127
app/lib/lnd/neutrino.js

@ -1,8 +1,11 @@
// @flow
import split2 from 'split2' import split2 from 'split2'
import { spawn } from 'child_process' import { spawn } from 'child_process'
import EventEmitter from 'events' import EventEmitter from 'events'
import { mainLog, lndLog, lndLogGetLevel } from '../utils/log' import { mainLog, lndLog, lndLogGetLevel } from '../utils/log'
import { fetchBlockHeight } from './util' import { fetchBlockHeight } from './util'
import LndConfig from './config'
// Sync statuses // Sync statuses
const CHAIN_SYNC_PENDING = 'chain-sync-pending' const CHAIN_SYNC_PENDING = 'chain-sync-pending'
@ -17,19 +20,41 @@ const WALLET_UNLOCKER_GRPC_ACTIVE = 'wallet-unlocker-grpc-active'
const LIGHTNING_GRPC_ACTIVE = 'lightning-grpc-active' const LIGHTNING_GRPC_ACTIVE = 'lightning-grpc-active'
const GOT_CURRENT_BLOCK_HEIGHT = 'got-current-block-height' const GOT_CURRENT_BLOCK_HEIGHT = 'got-current-block-height'
const GOT_LND_BLOCK_HEIGHT = 'got-lnd-block-height' const GOT_LND_BLOCK_HEIGHT = 'got-lnd-block-height'
const GOT_LND_CFILTER_HEIGHT = 'got-lnd-cfilter-height'
/** /**
* Wrapper class for Lnd to run and monitor it in Neutrino mode. * Wrapper class for Lnd to run and monitor it in Neutrino mode.
* @extends EventEmitter * @extends EventEmitter
*/ */
class Neutrino extends EventEmitter { class Neutrino extends EventEmitter {
constructor(lndConfig) { lndConfig: LndConfig
process: any
walletUnlockerGrpcActive: boolean
lightningGrpcActive: boolean
chainSyncStatus: string
currentBlockHeight: number
lndBlockHeight: number
lndCfilterHeight: number
constructor(lndConfig: LndConfig) {
super() super()
this.lndConfig = lndConfig this.lndConfig = lndConfig
this.process = null this.process = null
this.walletUnlockerGrpcActive = false this.walletUnlockerGrpcActive = false
this.lightningGrpcActive = false this.lightningGrpcActive = false
this.chainSyncStatus = CHAIN_SYNC_PENDING this.chainSyncStatus = CHAIN_SYNC_PENDING
this.currentBlockHeight = 0
this.lndBlockHeight = 0
this.lndCfilterHeight = 0
}
static incrementIfHigher = (context: any, property: string, newVal: any): boolean => {
const { [property]: oldVal } = context
if (newVal > oldVal) {
context[property] = newVal
return true
}
return false
} }
/** /**
@ -55,6 +80,14 @@ class Neutrino extends EventEmitter {
`${this.lndConfig.alias ? `--alias=${this.lndConfig.alias}` : ''}` `${this.lndConfig.alias ? `--alias=${this.lndConfig.alias}` : ''}`
] ]
if (this.lndConfig.network === 'mainnet') {
neutrinoArgs.push('--neutrino.connect=mainnet1-btcd.zaphq.io')
neutrinoArgs.push('--neutrino.connect=mainnet2-btcd.zaphq.io')
} else {
neutrinoArgs.push('--neutrino.connect=testnet1-btcd.zaphq.io')
neutrinoArgs.push('--neutrino.connect=testnet2-btcd.zaphq.io')
}
this.process = spawn(this.lndConfig.binaryPath, neutrinoArgs) this.process = spawn(this.lndConfig.binaryPath, neutrinoArgs)
.on('error', error => this.emit(ERROR, error)) .on('error', error => this.emit(ERROR, error))
.on('close', code => { .on('close', code => {
@ -96,35 +129,36 @@ class Neutrino extends EventEmitter {
return return
} }
// Lnd waiting for backend to finish syncing.
if (this.is(CHAIN_SYNC_PENDING) || this.is(CHAIN_SYNC_IN_PROGRESS)) { if (this.is(CHAIN_SYNC_PENDING) || this.is(CHAIN_SYNC_IN_PROGRESS)) {
if ( // If we cant get a connectionn to the backend.
line.includes('No sync peer candidates available') || if (line.includes('Waiting for chain backend to finish sync')) {
line.includes('Unable to synchronize wallet to chain') || this.setState(CHAIN_SYNC_WAITING)
line.includes('Waiting for chain backend to finish sync') }
) { // If we are still waiting for the back end to finish synncing.
if (line.includes('No sync peer candidates available')) {
this.setState(CHAIN_SYNC_WAITING) this.setState(CHAIN_SYNC_WAITING)
} }
} }
// Lnd syncing has started or resumed. // Lnd syncing has started or resumed.
if (this.is(CHAIN_SYNC_PENDING) || this.is(CHAIN_SYNC_WAITING)) { if (this.is(CHAIN_SYNC_PENDING) || this.is(CHAIN_SYNC_WAITING)) {
const match = line.match(/Syncing to block height (\d+)/) const match =
line.match(/Syncing to block height (\d+)/) ||
line.match(/Starting cfilters sync at block_height=(\d+)/)
if (match) { if (match) {
// Notify that chhain syncronisation has now started. // Notify that chhain syncronisation has now started.
this.setState(CHAIN_SYNC_IN_PROGRESS) this.setState(CHAIN_SYNC_IN_PROGRESS)
// This is the latest block that BTCd is aware of. // This is the latest block that BTCd is aware of.
const btcdHeight = Number(match[1]) const btcdHeight = match[1]
this.emit(GOT_CURRENT_BLOCK_HEIGHT, btcdHeight) this.setCurrentBlockHeight(btcdHeight)
// The height returned from the LND log output may not be the actual current block height (this is the case // 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 // 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. // some block explorers just incase.
fetchBlockHeight() fetchBlockHeight()
.then( .then(height => (height > btcdHeight ? this.setCurrentBlockHeight(height) : null))
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. // 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}`)) .catch(err => mainLog.warn(`Unable to fetch block height: ${err.message}`))
} }
@ -133,19 +167,41 @@ class Neutrino extends EventEmitter {
// Lnd as received some updated block data. // Lnd as received some updated block data.
if (this.is(CHAIN_SYNC_WAITING) || this.is(CHAIN_SYNC_IN_PROGRESS)) { if (this.is(CHAIN_SYNC_WAITING) || this.is(CHAIN_SYNC_IN_PROGRESS)) {
let height let height
let cfilter
let match let match
if ((match = line.match(/Rescanned through block.+\(height (\d+)/))) { if ((match = line.match(/Caught up to height (\d+)/))) {
height = match[1]
} else if ((match = line.match(/Caught up to height (\d+)/))) {
height = match[1] height = match[1]
} else if ((match = line.match(/Processed \d* blocks? in the last.+\(height (\d+)/))) { } else if ((match = line.match(/Processed \d* blocks? in the last.+\(height (\d+)/))) {
height = match[1] height = match[1]
} else if ((match = line.match(/Fetching set of headers from tip \(height=(\d+)/))) {
height = match[1]
} else if ((match = line.match(/Waiting for filter headers \(height=(\d+)\) to catch/))) {
height = match[1]
} else if ((match = line.match(/Writing filter headers up to height=(\d+)/))) {
height = match[1]
} else if ((match = line.match(/Starting cfheaders sync at block_height=(\d+)/))) {
height = match[1]
} else if ((match = line.match(/Got cfheaders from height=(\d*) to height=(\d+)/))) {
cfilter = match[2]
} else if ((match = line.match(/Verified \d* filter headers? in the.+\(height (\d+)/))) {
cfilter = match[1]
}
if (!this.lndCfilterHeight || this.lndCfilterHeight > this.currentBlockHeight - 10000) {
if ((match = line.match(/Fetching filter for height=(\d+)/))) {
cfilter = match[1]
}
} }
if (height) { if (height) {
this.setState(CHAIN_SYNC_IN_PROGRESS) this.setState(CHAIN_SYNC_IN_PROGRESS)
this.emit(GOT_LND_BLOCK_HEIGHT, height) this.setLndBlockHeight(Number(height))
}
if (cfilter) {
this.setState(CHAIN_SYNC_IN_PROGRESS)
this.setLndCfilterHeight(Number(cfilter))
} }
// Lnd syncing has completed. // Lnd syncing has completed.
@ -173,7 +229,7 @@ class Neutrino extends EventEmitter {
* @param {String} state State to compare against the current state. * @param {String} state State to compare against the current state.
* @return {Boolean} Boolean indicating if the current state matches the passed in state. * @return {Boolean} Boolean indicating if the current state matches the passed in state.
*/ */
is(state) { is(state: string) {
return this.chainSyncStatus === state return this.chainSyncStatus === state
} }
@ -181,12 +237,45 @@ class Neutrino extends EventEmitter {
* Set the current state and emit an event to notify others if te state as canged. * Set the current state and emit an event to notify others if te state as canged.
* @param {String} state Target state. * @param {String} state Target state.
*/ */
setState(state) { setState(state: string) {
if (state !== this.chainSyncStatus) { if (state !== this.chainSyncStatus) {
this.chainSyncStatus = state this.chainSyncStatus = state
this.emit(state) this.emit(state)
} }
} }
/**
* Set the current block height and emit an event to notify others if it has changed.
* @param {String|Number} height Block height
*/
setCurrentBlockHeight(height: number) {
const changed = Neutrino.incrementIfHigher(this, 'currentBlockHeight', height)
if (changed) {
this.emit(GOT_CURRENT_BLOCK_HEIGHT, height)
}
}
/**
* Set the lnd block height and emit an event to notify others if it has changed.
* @param {String|Number} height Block height
*/
setLndBlockHeight(height: number) {
const changed = Neutrino.incrementIfHigher(this, 'lndBlockHeight', height)
if (changed) {
this.emit(GOT_LND_BLOCK_HEIGHT, height)
this.setCurrentBlockHeight(height)
}
}
/**
* Set the lnd cfilter height and emit an event to notify others if it has changed.
* @param {String|Number} height Block height
*/
setLndCfilterHeight(height: number) {
const heightAsNumber = Number(height)
this.lndCfilterHeight = heightAsNumber
this.emit(GOT_LND_CFILTER_HEIGHT, heightAsNumber)
}
} }
export default Neutrino export default Neutrino

14
app/lib/zap/controller.js

@ -5,7 +5,6 @@ import pick from 'lodash.pick'
import Store from 'electron-store' import Store from 'electron-store'
import StateMachine from 'javascript-state-machine' import StateMachine from 'javascript-state-machine'
import LndConfig from '../lnd/config' import LndConfig from '../lnd/config'
import Neutrino from '../lnd/neutrino'
import Lightning from '../lnd/lightning' import Lightning from '../lnd/lightning'
import { initWalletUnlocker } from '../lnd/walletUnlocker' import { initWalletUnlocker } from '../lnd/walletUnlocker'
import { mainLog } from '../utils/log' import { mainLog } from '../utils/log'
@ -51,8 +50,8 @@ const grpcSslCipherSuites = connectionType =>
*/ */
class ZapController { class ZapController {
mainWindow: BrowserWindow mainWindow: BrowserWindow
neutrino: Neutrino neutrino: any
lightning: Lightning lightning: any
splashScreenTime: number splashScreenTime: number
lightningGrpcConnected: boolean lightningGrpcConnected: boolean
lndConfig: LndConfig lndConfig: LndConfig
@ -74,10 +73,10 @@ class ZapController {
this.mainWindow = mainWindow this.mainWindow = mainWindow
// Keep a reference any neutrino process started by us. // Keep a reference any neutrino process started by us.
this.neutrino = null this.neutrino = undefined
// Keep a reference to the lightning gRPC instance. // Keep a reference to the lightning gRPC instance.
this.lightning = null this.lightning = undefined
// Time for the splash screen to remain visible. // Time for the splash screen to remain visible.
this.splashScreenTime = 500 this.splashScreenTime = 500
@ -303,7 +302,6 @@ class ZapController {
*/ */
startNeutrino() { startNeutrino() {
mainLog.info('Starting Neutrino...') mainLog.info('Starting Neutrino...')
this.neutrino = new Neutrino(this.lndConfig)
this.neutrino.on('error', error => { this.neutrino.on('error', error => {
mainLog.error(`Got error from lnd process: ${error})`) mainLog.error(`Got error from lnd process: ${error})`)
@ -357,6 +355,10 @@ class ZapController {
this.sendMessage('lndBlockHeight', Number(height)) this.sendMessage('lndBlockHeight', Number(height))
}) })
this.neutrino.on('got-lnd-cfilter-height', height => {
this.sendMessage('lndCfilterHeight', Number(height))
})
this.neutrino.start() this.neutrino.start()
} }

9
app/reducers/ipc.js

@ -1,5 +1,11 @@
import createIpc from 'redux-electron-ipc' import createIpc from 'redux-electron-ipc'
import { lndSyncStatus, currentBlockHeight, lndBlockHeight, lightningGrpcActive } from './lnd' import {
lndSyncStatus,
currentBlockHeight,
lndBlockHeight,
lndCfilterHeight,
lightningGrpcActive
} from './lnd'
import { receiveInfo } from './info' import { receiveInfo } from './info'
import { receiveAddress } from './address' import { receiveAddress } from './address'
import { receiveCryptocurrency } from './ticker' import { receiveCryptocurrency } from './ticker'
@ -54,6 +60,7 @@ const ipc = createIpc({
lndSyncStatus, lndSyncStatus,
currentBlockHeight, currentBlockHeight,
lndBlockHeight, lndBlockHeight,
lndCfilterHeight,
lightningGrpcActive, lightningGrpcActive,
receiveInfo, receiveInfo,

40
app/reducers/lnd.js

@ -12,8 +12,9 @@ export const SET_SYNC_STATUS_WAITING = 'SET_SYNC_STATUS_WAITING'
export const SET_SYNC_STATUS_IN_PROGRESS = 'SET_SYNC_STATUS_IN_PROGRESS' export const SET_SYNC_STATUS_IN_PROGRESS = 'SET_SYNC_STATUS_IN_PROGRESS'
export const SET_SYNC_STATUS_COMPLETE = 'SET_SYNC_STATUS_COMPLETE' export const SET_SYNC_STATUS_COMPLETE = 'SET_SYNC_STATUS_COMPLETE'
export const RECEIVE_BLOCK_HEIGHT = 'RECEIVE_BLOCK_HEIGHT' export const RECEIVE_CURRENT_BLOCK_HEIGHT = 'RECEIVE_CURRENT_BLOCK_HEIGHT'
export const RECEIVE_BLOCK = 'RECEIVE_BLOCK' export const RECEIVE_LND_BLOCK_HEIGHT = 'RECEIVE_LND_BLOCK_HEIGHT'
export const RECEIVE_LND_CFILTER_HEIGHT = 'RECEIVE_LND_CFILTER_HEIGHT'
export const SET_LIGHTNING_WALLET_ACTIVE = 'SET_LIGHTNING_WALLET_ACTIVE' export const SET_LIGHTNING_WALLET_ACTIVE = 'SET_LIGHTNING_WALLET_ACTIVE'
@ -64,20 +65,19 @@ export const lightningGrpcActive = () => dispatch => {
dispatch({ type: SET_LIGHTNING_WALLET_ACTIVE }) dispatch({ type: SET_LIGHTNING_WALLET_ACTIVE })
} }
// Receive IPC event for LND streaming a line // Receive IPC event for current height.
export const lndBlockHeight = (event, height) => dispatch => { export const currentBlockHeight = (event, height) => dispatch => {
dispatch({ type: RECEIVE_BLOCK, lndBlockHeight: height }) dispatch({ type: RECEIVE_CURRENT_BLOCK_HEIGHT, blockHeight: height })
} }
export const currentBlockHeight = (event, height) => dispatch => { // Receive IPC event for LND block height.
dispatch({ type: RECEIVE_BLOCK_HEIGHT, blockHeight: height }) export const lndBlockHeight = (event, height) => dispatch => {
dispatch({ type: RECEIVE_LND_BLOCK_HEIGHT, lndBlockHeight: height })
} }
export function receiveBlockHeight(blockHeight) { // Receive IPC event for LND cfilter height.
return { export const lndCfilterHeight = (event, height) => dispatch => {
type: RECEIVE_BLOCK_HEIGHT, dispatch({ type: RECEIVE_LND_CFILTER_HEIGHT, lndCfilterHeight: height })
blockHeight
}
} }
// ------------------------------------ // ------------------------------------
@ -89,11 +89,12 @@ const ACTION_HANDLERS = {
[SET_SYNC_STATUS_IN_PROGRESS]: state => ({ ...state, syncStatus: 'in-progress' }), [SET_SYNC_STATUS_IN_PROGRESS]: state => ({ ...state, syncStatus: 'in-progress' }),
[SET_SYNC_STATUS_COMPLETE]: state => ({ ...state, syncStatus: 'complete' }), [SET_SYNC_STATUS_COMPLETE]: state => ({ ...state, syncStatus: 'complete' }),
[RECEIVE_BLOCK_HEIGHT]: (state, { blockHeight }) => ({ [RECEIVE_CURRENT_BLOCK_HEIGHT]: (state, { blockHeight }) => ({
...state, ...state,
blockHeight blockHeight
}), }),
[RECEIVE_BLOCK]: (state, { lndBlockHeight }) => ({ ...state, lndBlockHeight }), [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_LIGHTNING_WALLET_ACTIVE]: state => ({ ...state, lightningGrpcActive: true })
} }
@ -105,7 +106,8 @@ const initialState = {
syncStatus: 'pending', syncStatus: 'pending',
lightningGrpcActive: false, lightningGrpcActive: false,
blockHeight: 0, blockHeight: 0,
lndBlockHeight: 0 lndBlockHeight: 0,
lndCfilterHeight: 0
} }
// ------------------------------------ // ------------------------------------
@ -114,12 +116,16 @@ const initialState = {
const lndSelectors = {} const lndSelectors = {}
const blockHeightSelector = state => state.lnd.blockHeight const blockHeightSelector = state => state.lnd.blockHeight
const lndBlockHeightSelector = state => state.lnd.lndBlockHeight const lndBlockHeightSelector = state => state.lnd.lndBlockHeight
const lndCfilterHeightSelector = state => state.lnd.lndCfilterHeight
lndSelectors.syncPercentage = createSelector( lndSelectors.syncPercentage = createSelector(
blockHeightSelector, blockHeightSelector,
lndBlockHeightSelector, lndBlockHeightSelector,
(blockHeight, lndBlockHeight) => { lndCfilterHeightSelector,
const percentage = Math.floor((lndBlockHeight / blockHeight) * 100) (blockHeight, lndBlockHeight, lndCfilterHeight) => {
// We set the total amount to the blockheight x 2 because there are twi pahases to the sync process that each
// take about the same amount of time (syncing blocks and syncing cfilters)
const percentage = Math.floor(((lndBlockHeight + lndCfilterHeight) / (blockHeight * 2)) * 100)
if (percentage === Infinity || Number.isNaN(percentage)) { if (percentage === Infinity || Number.isNaN(percentage)) {
return undefined return undefined

5
package.json

@ -42,7 +42,8 @@
"config": { "config": {
"style_paths": "app/styles/*.scss app/components/**/*.scss", "style_paths": "app/styles/*.scss app/components/**/*.scss",
"lnd-binary": { "lnd-binary": {
"binaryVersion": "0.4.2-beta" "binaryVersion": "0.4.2-beta-635-g72aa7969",
"binarySite": "https://github.com/LN-Zap/lnd/releases/download"
} }
}, },
"browserslist": "electron 2.0", "browserslist": "electron 2.0",
@ -255,7 +256,7 @@
"jsdom": "^11.12.0", "jsdom": "^11.12.0",
"koa-connect": "^2.0.1", "koa-connect": "^2.0.1",
"lint-staged": "^7.2.0", "lint-staged": "^7.2.0",
"lnd-binary": "^0.3.4", "lnd-binary": "^0.3.5",
"minimist": "^1.2.0", "minimist": "^1.2.0",
"node-sass": "^4.9.0", "node-sass": "^4.9.0",
"prettier": "^1.13.5", "prettier": "^1.13.5",

2
resources/lnd.conf

@ -212,8 +212,6 @@ bitcoin.node=neutrino
; Add a peer to connect with at startup. ; Add a peer to connect with at startup.
; neutrino.addpeer= ; neutrino.addpeer=
neutrino.connect=206.189.72.180
neutrino.connect=127.0.0.1:18333
[Litecoin] [Litecoin]

218
resources/rpc.proto

@ -1,4 +1,4 @@
// Imported from https://github.com/lightningnetwork/lnd/blob/v0.4.2-beta/lnrpc/rpc.proto // Imported from https://github.com/lightningnetwork/lnd/blob/72aa79692cf4960ad27526358e333ba81e8ad99b/lnrpc/rpc.proto
syntax = "proto3"; syntax = "proto3";
// import "google/api/annotations.proto"; // import "google/api/annotations.proto";
@ -76,6 +76,17 @@ service WalletUnlocker {
body: "*" body: "*"
}; };
} }
/** lncli: `changepassword`
ChangePassword changes the password of the encrypted wallet. This will
automatically unlock the wallet database if successful.
*/
rpc ChangePassword (ChangePasswordRequest) returns (ChangePasswordResponse) {
option (google.api.http) = {
post: "/v1/changepassword"
body: "*"
};
}
} }
message GenSeedRequest { message GenSeedRequest {
@ -160,6 +171,21 @@ message UnlockWalletRequest {
} }
message UnlockWalletResponse {} message UnlockWalletResponse {}
message ChangePasswordRequest {
/**
current_password should be the current valid passphrase used to unlock the
daemon.
*/
bytes current_password = 1;
/**
new_password should be the new passphrase that will be needed to unlock the
daemon.
*/
bytes new_password = 2;
}
message ChangePasswordResponse {}
service Lightning { service Lightning {
/** lncli: `walletbalance` /** lncli: `walletbalance`
WalletBalance returns total unspent outputs(confirmed and unconfirmed), all WalletBalance returns total unspent outputs(confirmed and unconfirmed), all
@ -316,6 +342,17 @@ service Lightning {
}; };
} }
/** lncli: `closedchannels`
ClosedChannels returns a description of all the closed channels that
this node was a participant in.
*/
rpc ClosedChannels (ClosedChannelsRequest) returns (ClosedChannelsResponse) {
option (google.api.http) = {
get: "/v1/channels/closed"
};
}
/** /**
OpenChannelSync is a synchronous version of the OpenChannel RPC call. This OpenChannelSync is a synchronous version of the OpenChannel RPC call. This
call is meant to be consumed by clients to the REST proxy. As with all call is meant to be consumed by clients to the REST proxy. As with all
@ -374,6 +411,25 @@ service Lightning {
}; };
} }
/** lncli: `sendtoroute`
SendToRoute is a bi-directional streaming RPC for sending payment through
the Lightning Network. This method differs from SendPayment in that it
allows users to specify a full route manually. This can be used for things
like rebalancing, and atomic swaps.
*/
rpc SendToRoute(stream SendToRouteRequest) returns (stream SendResponse);
/**
SendToRouteSync is a synchronous version of SendToRoute. It Will block
until the payment either fails or succeeds.
*/
rpc SendToRouteSync (SendToRouteRequest) returns (SendResponse) {
option (google.api.http) = {
post: "/v1/channels/transactions/route"
body: "*"
};
}
/** lncli: `addinvoice` /** lncli: `addinvoice`
AddInvoice attempts to add a new invoice to the invoice database. Any AddInvoice attempts to add a new invoice to the invoice database. Any
duplicated invoices are rejected, therefore all invoices *must* have a duplicated invoices are rejected, therefore all invoices *must* have a
@ -409,7 +465,14 @@ service Lightning {
/** /**
SubscribeInvoices returns a uni-directional stream (sever -> client) for SubscribeInvoices returns a uni-directional stream (sever -> client) for
notifying the client of newly added/settled invoices. notifying the client of newly added/settled invoices. The caller can
optionally specify the add_index and/or the settle_index. If the add_index
is specified, then we'll first start by sending add invoice events for all
invoices with an add_index greater than the specified value. If the
settle_index is specified, the next, we'll send out all settle events for
invoices with a settle_index greater than the specified value. One or both
of these fields can be set. If no fields are set, then we'll only send out
the latest add/settle events.
*/ */
rpc SubscribeInvoices (InvoiceSubscription) returns (stream Invoice) { rpc SubscribeInvoices (InvoiceSubscription) returns (stream Invoice) {
option (google.api.http) = { option (google.api.http) = {
@ -602,6 +665,16 @@ message TransactionDetails {
repeated Transaction transactions = 1 [json_name = "transactions"]; repeated Transaction transactions = 1 [json_name = "transactions"];
} }
message FeeLimit {
oneof limit {
/// The fee limit expressed as a fixed amount of satoshis.
int64 fixed = 1;
/// The fee limit expressed as a percentage of the payment amount.
int64 percent = 2;
}
}
message SendRequest { message SendRequest {
/// The identity pubkey of the payment recipient /// The identity pubkey of the payment recipient
bytes dest = 1; bytes dest = 1;
@ -625,8 +698,19 @@ message SendRequest {
*/ */
string payment_request = 6; string payment_request = 6;
/// The CLTV delta from the current height that should be used to set the timelock for the final hop. /**
The CLTV delta from the current height that should be used to set the
timelock for the final hop.
*/
int32 final_cltv_delta = 7; int32 final_cltv_delta = 7;
/**
The maximum number of satoshis that will be paid as a fee of the payment.
This value can be represented either as a percentage of the amount being
sent, or as a fixed amount of the maximum fee the user is willing the pay to
send the payment.
*/
FeeLimit fee_limit = 8;
} }
message SendResponse { message SendResponse {
string payment_error = 1 [json_name = "payment_error"]; string payment_error = 1 [json_name = "payment_error"];
@ -634,6 +718,17 @@ message SendResponse {
Route payment_route = 3 [json_name = "payment_route"]; Route payment_route = 3 [json_name = "payment_route"];
} }
message SendToRouteRequest {
/// The payment hash to use for the HTLC.
bytes payment_hash = 1;
/// An optional hex-encoded payment hash to be used for the HTLC.
string payment_hash_string = 2;
/// The set of routes that should be used to attempt to complete the payment.
repeated Route routes = 3;
}
message ChannelPoint { message ChannelPoint {
oneof funding_txid { oneof funding_txid {
/// Txid of the funding transaction /// Txid of the funding transaction
@ -844,6 +939,7 @@ message Channel {
bool private = 17 [json_name = "private"]; bool private = 17 [json_name = "private"];
} }
message ListChannelsRequest { message ListChannelsRequest {
bool active_only = 1; bool active_only = 1;
bool inactive_only = 2; bool inactive_only = 2;
@ -855,6 +951,58 @@ message ListChannelsResponse {
repeated Channel channels = 11 [json_name = "channels"]; repeated Channel channels = 11 [json_name = "channels"];
} }
message ChannelCloseSummary {
/// The outpoint (txid:index) of the funding transaction.
string channel_point = 1 [json_name = "channel_point"];
/// The unique channel ID for the channel.
uint64 chan_id = 2 [json_name = "chan_id"];
/// The hash of the genesis block that this channel resides within.
string chain_hash = 3 [json_name = "chain_hash"];
/// The txid of the transaction which ultimately closed this channel.
string closing_tx_hash = 4 [json_name = "closing_tx_hash"];
/// Public key of the remote peer that we formerly had a channel with.
string remote_pubkey = 5 [json_name = "remote_pubkey"];
/// Total capacity of the channel.
int64 capacity = 6 [json_name = "capacity"];
/// Height at which the funding transaction was spent.
uint32 close_height = 7 [json_name = "close_height"];
/// Settled balance at the time of channel closure
int64 settled_balance = 8 [json_name = "settled_balance"];
/// The sum of all the time-locked outputs at the time of channel closure
int64 time_locked_balance = 9 [json_name = "time_locked_balance"];
enum ClosureType {
COOPERATIVE_CLOSE = 0;
LOCAL_FORCE_CLOSE = 1;
REMOTE_FORCE_CLOSE = 2;
BREACH_CLOSE = 3;
FUNDING_CANCELED = 4;
}
/// Details on how the channel was closed.
ClosureType close_type = 10 [json_name = "close_type"];
}
message ClosedChannelsRequest {
bool cooperative = 1;
bool local_force = 2;
bool remote_force = 3;
bool breach = 4;
bool funding_canceled = 5;
}
message ClosedChannelsResponse {
repeated ChannelCloseSummary channels = 1 [json_name = "channels"];
}
message Peer { message Peer {
/// The identity pubkey of the peer /// The identity pubkey of the peer
string pub_key = 1 [json_name = "pub_key"]; string pub_key = 1 [json_name = "pub_key"];
@ -1172,9 +1320,20 @@ message QueryRoutesRequest {
/// The max number of routes to return. /// The max number of routes to return.
int32 num_routes = 3; int32 num_routes = 3;
/// An optional CLTV delta from the current height that should be used for the timelock of the final hop
int32 final_cltv_delta = 4;
/**
The maximum number of satoshis that will be paid as a fee of the payment.
This value can be represented either as a percentage of the amount being
sent, or as a fixed amount of the maximum fee the user is willing the pay to
send the payment.
*/
FeeLimit fee_limit = 5;
} }
message QueryRoutesResponse { message QueryRoutesResponse {
repeated Route routes = 1 [ json_name = "routes"]; repeated Route routes = 1 [json_name = "routes"];
} }
message Hop { message Hop {
@ -1284,6 +1443,7 @@ message RoutingPolicy {
int64 min_htlc = 2 [json_name = "min_htlc"]; int64 min_htlc = 2 [json_name = "min_htlc"];
int64 fee_base_msat = 3 [json_name = "fee_base_msat"]; int64 fee_base_msat = 3 [json_name = "fee_base_msat"];
int64 fee_rate_milli_msat = 4 [json_name = "fee_rate_milli_msat"]; int64 fee_rate_milli_msat = 4 [json_name = "fee_rate_milli_msat"];
bool disabled = 5 [json_name = "disabled"];
} }
/** /**
@ -1491,6 +1651,32 @@ message Invoice {
/// Whether this invoice should include routing hints for private channels. /// Whether this invoice should include routing hints for private channels.
bool private = 15 [json_name = "private"]; bool private = 15 [json_name = "private"];
/**
The "add" index of this invoice. Each newly created invoice will increment
this index making it monotonically increasing. Callers to the
SubscribeInvoices call can use this to instantly get notified of all added
invoices with an add_index greater than this one.
*/
uint64 add_index = 16 [json_name = "add_index"];
/**
The "settle" index of this invoice. Each newly settled invoice will
increment this index making it monotonically increasing. Callers to the
SubscribeInvoices call can use this to instantly get notified of all
settled invoices with an settle_index greater than this one.
*/
uint64 settle_index = 17 [json_name = "settle_index"];
/**
The amount that was accepted for this invoice. This will ONLY be set if
this invoice has been settled. We provide this field as if the invoice was
created with a zero value, then we need to record what amount was
ultimately accepted. Additionally, it's possible that the sender paid MORE
that was specified in the original invoice. So we'll record that here as
well.
*/
int64 amt_paid = 18 [json_name = "amt_paid"];
} }
message AddInvoiceResponse { message AddInvoiceResponse {
bytes r_hash = 1 [json_name = "r_hash"]; bytes r_hash = 1 [json_name = "r_hash"];
@ -1501,6 +1687,14 @@ message AddInvoiceResponse {
payment to the recipient. payment to the recipient.
*/ */
string payment_request = 2 [json_name = "payment_request"]; string payment_request = 2 [json_name = "payment_request"];
/**
The "add" index of this invoice. Each newly created invoice will increment
this index making it monotonically increasing. Callers to the
SubscribeInvoices call can use this to instantly get notified of all added
invoices with an add_index greater than this one.
*/
uint64 add_index = 16 [json_name = "add_index"];
} }
message PaymentHash { message PaymentHash {
/** /**
@ -1521,6 +1715,21 @@ message ListInvoiceResponse {
} }
message InvoiceSubscription { message InvoiceSubscription {
/**
If specified (non-zero), then we'll first start by sending out
notifications for all added indexes with an add_index greater than this
value. This allows callers to catch up on any events they missed while they
weren't connected to the streaming RPC.
*/
uint64 add_index = 1 [json_name = "add_index"];
/**
If specified (non-zero), then we'll first start by sending out
notifications for all settled indexes with an settle_index greater than
this value. This allows callers to catch up on any events they missed while
they weren't connected to the streaming RPC.
*/
uint64 settle_index = 2 [json_name = "settle_index"];
} }
@ -1675,4 +1884,3 @@ message ForwardingHistoryResponse {
/// The index of the last time in the set of returned forwarding events. Can be used to seek further, pagination style. /// The index of the last time in the set of returned forwarding events. Can be used to seek further, pagination style.
uint32 last_offset_index = 2 [json_name = "last_offset_index"]; uint32 last_offset_index = 2 [json_name = "last_offset_index"];
} }

171
test/unit/lnd/neutrino.spec.js

@ -0,0 +1,171 @@
import Neutrino from 'lib/lnd/neutrino'
jest.mock('electron', () => {
const { normalize } = require('path')
return {
app: {
getPath: name => normalize(`/tmp/zap-test/${name}`),
getAppPath: () => normalize('/tmp/zap-test')
}
}
})
describe('Neutrino', function() {
describe('Constructor', () => {
beforeAll(() => (this.neutrino = new Neutrino()))
describe('initial values', () => {
it('should set the "process" property to null', () => {
expect(this.neutrino.process).toEqual(null)
})
it('should set the "walletUnlockerGrpcActive" property to false', () => {
expect(this.neutrino.walletUnlockerGrpcActive).toEqual(false)
})
it('should set the "lightningGrpcActive" property to false', () => {
expect(this.neutrino.lightningGrpcActive).toEqual(false)
})
it('should set the "chainSyncStatus" property to "chain-sync-pending"', () => {
expect(this.neutrino.chainSyncStatus).toEqual('chain-sync-pending')
})
it('should set the "currentBlockHeight" property to 0', () => {
expect(this.neutrino.currentBlockHeight).toEqual(0)
})
it('should set the "lndBlockHeight" property to 0', () => {
expect(this.neutrino.lndBlockHeight).toEqual(0)
})
it('should set the "lndCfilterHeight" property to 0', () => {
expect(this.neutrino.lndCfilterHeight).toEqual(0)
})
})
})
describe('.setState', () => {
describe('called with new state', () => {
beforeEach(() => {
this.neutrino = new Neutrino()
this.callback = jest.fn()
this.newVal = 'chain-sync-finished'
this.neutrino.on('chain-sync-finished', this.callback)
this.neutrino.setState(this.newVal)
})
it('should set the state', () => {
expect(this.neutrino.chainSyncStatus).toEqual(this.newVal)
})
it('should emit an event with the new state name', () => {
expect(this.callback).toHaveBeenCalledTimes(1)
})
})
describe('called with current state', () => {
beforeEach(() => {
this.neutrino = new Neutrino()
this.callback = jest.fn()
this.newVal = 'chain-sync-pending'
this.neutrino.on('chain-sync-pending', this.callback)
this.neutrino.setState(this.newVal)
})
it('should not change the state', () => {
expect(this.neutrino.chainSyncStatus).toEqual(this.newVal)
})
it('should not emit an event with the new state name', () => {
expect(this.callback).not.toHaveBeenCalled()
})
})
})
describe('.setCurrentBlockHeight', () => {
describe('called with higher height', () => {
beforeEach(() => {
this.neutrino = new Neutrino()
this.callback = jest.fn()
this.newVal = 100
this.neutrino.on('got-current-block-height', this.callback)
this.neutrino.setCurrentBlockHeight(this.newVal)
})
it('should change the current block height', () => {
expect(this.neutrino.currentBlockHeight).toEqual(this.newVal)
})
it('should emit an event with the new current block height', () => {
expect(this.callback).toHaveBeenCalledTimes(1)
expect(this.callback).toHaveBeenCalledWith(this.newVal)
})
})
describe('called with lower height', () => {
beforeEach(() => {
this.neutrino = new Neutrino()
this.callback = jest.fn()
this.newVal = -1
this.neutrino.on('got-current-block-height', this.callback)
this.neutrino.setCurrentBlockHeight(this.newVal)
})
it('should not change the current block height', () => {
expect(this.neutrino.currentBlockHeight).toEqual(0)
})
it('should not emit an event with the new current block height', () => {
expect(this.callback).not.toHaveBeenCalled()
})
})
})
describe('.setLndBlockHeight', () => {
describe('called with higher height', () => {
beforeEach(() => {
this.neutrino = new Neutrino()
this.callback = jest.fn()
this.newVal = 100
this.neutrino.on('got-lnd-block-height', this.callback)
this.neutrino.setCurrentBlockHeight = jest.fn()
this.neutrino.setLndBlockHeight(this.newVal)
})
it('should change the lnd block height', () => {
expect(this.neutrino.lndBlockHeight).toEqual(this.newVal)
})
it('should emit an event with the new lnd block height', () => {
expect(this.callback).toHaveBeenCalledTimes(1)
expect(this.callback).toHaveBeenCalledWith(this.newVal)
})
it('should call this.setCurrentBlockHeight', () => {
expect(this.neutrino.setCurrentBlockHeight).toHaveBeenCalledTimes(1)
expect(this.neutrino.setCurrentBlockHeight).toHaveBeenCalledWith(this.newVal)
})
})
describe('called with lower height', () => {
beforeEach(() => {
this.neutrino = new Neutrino()
this.callback = jest.fn()
this.newVal = -1
this.neutrino.on('got-lnd-block-height', this.callback)
this.neutrino.setLndBlockHeight(this.newVal)
this.neutrino.setCurrentBlockHeight = jest.fn()
})
it('should not change the lnd block height', () => {
expect(this.neutrino.lndBlockHeight).toEqual(0)
})
it('should not emit an event with the new lnd block height', () => {
expect(this.callback).not.toHaveBeenCalled()
})
it('should not call this.setCurrentBlockHeight', () => {
expect(this.neutrino.setCurrentBlockHeight).not.toHaveBeenCalled()
})
})
})
describe('.is', () => {
describe('called with current state', () => {
beforeEach(() => (this.neutrino = new Neutrino()))
it('should returnn true if the current state matches', () => {
expect(this.neutrino.is('chain-sync-pending')).toEqual(true)
})
it('should return false if the current state does not matche', () => {
expect(this.neutrino.is('some-other-state')).toEqual(false)
})
})
})
})

22
yarn.lock

@ -2555,15 +2555,15 @@ cacache@^10.0.4:
y18n "^4.0.0" y18n "^4.0.0"
cacache@^11.0.2: cacache@^11.0.2:
version "11.0.2" version "11.1.0"
resolved "https://registry.yarnpkg.com/cacache/-/cacache-11.0.2.tgz#ff30541a05302200108a759e660e30786f788764" resolved "https://registry.yarnpkg.com/cacache/-/cacache-11.1.0.tgz#3d76dbc2e9da413acaad2557051960a4dad3e1a4"
dependencies: dependencies:
bluebird "^3.5.1" bluebird "^3.5.1"
chownr "^1.0.1" chownr "^1.0.1"
figgy-pudding "^3.1.0" figgy-pudding "^3.1.0"
glob "^7.1.2" glob "^7.1.2"
graceful-fs "^4.1.11" graceful-fs "^4.1.11"
lru-cache "^4.1.2" lru-cache "^4.1.3"
mississippi "^3.0.0" mississippi "^3.0.0"
mkdirp "^0.5.1" mkdirp "^0.5.1"
move-concurrently "^1.0.1" move-concurrently "^1.0.1"
@ -4961,8 +4961,8 @@ fd-slicer@~1.0.1:
pend "~1.2.0" pend "~1.2.0"
figgy-pudding@^3.1.0: figgy-pudding@^3.1.0:
version "3.1.0" version "3.2.0"
resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.1.0.tgz#a77ed2284175976c424b390b298569e9df86dd1e" resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.2.0.tgz#464626b73d7b0fc045a99753d191b0785957ff73"
figures@^1.7.0: figures@^1.7.0:
version "1.7.0" version "1.7.0"
@ -5137,8 +5137,8 @@ follow-redirects@^1.2.3:
debug "^2.4.5" debug "^2.4.5"
follow-redirects@^1.3.0: follow-redirects@^1.3.0:
version "1.5.1" version "1.5.2"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.1.tgz#67a8f14f5a1f67f962c2c46469c79eaec0a90291" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.2.tgz#5a9d80e0165957e5ef0c1210678fc5c4acb9fb03"
dependencies: dependencies:
debug "^3.1.0" debug "^3.1.0"
@ -7444,9 +7444,9 @@ listr@^0.14.1:
rxjs "^6.1.0" rxjs "^6.1.0"
strip-ansi "^3.0.1" strip-ansi "^3.0.1"
lnd-binary@^0.3.4: lnd-binary@^0.3.5:
version "0.3.4" version "0.3.5"
resolved "https://registry.yarnpkg.com/lnd-binary/-/lnd-binary-0.3.4.tgz#51fcf221a355e7c25499a3948230613aa282abde" resolved "https://registry.yarnpkg.com/lnd-binary/-/lnd-binary-0.3.5.tgz#5ae89acebc9b6f3801ae26633a4f02bd1e2f9b51"
dependencies: dependencies:
axios "^0.18.0" axios "^0.18.0"
cacache "^11.0.2" cacache "^11.0.2"
@ -7706,7 +7706,7 @@ lowercase-keys@^1.0.0:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f"
lru-cache@^4.0.1, lru-cache@^4.1.1, lru-cache@^4.1.2: lru-cache@^4.0.1, lru-cache@^4.1.1, lru-cache@^4.1.3:
version "4.1.3" version "4.1.3"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.3.tgz#a1175cf3496dfc8436c156c334b4955992bce69c" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.3.tgz#a1175cf3496dfc8436c156c334b4955992bce69c"
dependencies: dependencies:

Loading…
Cancel
Save