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

1
app/containers/Root.js

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

127
app/lib/lnd/neutrino.js

@ -1,8 +1,11 @@
// @flow
import split2 from 'split2'
import { spawn } from 'child_process'
import EventEmitter from 'events'
import { mainLog, lndLog, lndLogGetLevel } from '../utils/log'
import { fetchBlockHeight } from './util'
import LndConfig from './config'
// Sync statuses
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 GOT_CURRENT_BLOCK_HEIGHT = 'got-current-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.
* @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()
this.lndConfig = lndConfig
this.process = null
this.walletUnlockerGrpcActive = false
this.lightningGrpcActive = false
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}` : ''}`
]
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)
.on('error', error => this.emit(ERROR, error))
.on('close', code => {
@ -96,35 +129,36 @@ class Neutrino extends EventEmitter {
return
}
// Lnd waiting for backend to finish syncing.
if (this.is(CHAIN_SYNC_PENDING) || this.is(CHAIN_SYNC_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')
) {
// If we cant get a connectionn to the backend.
if (line.includes('Waiting for chain backend to finish sync')) {
this.setState(CHAIN_SYNC_WAITING)
}
// 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)
}
}
// Lnd syncing has started or resumed.
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) {
// Notify that chhain syncronisation has now started.
this.setState(CHAIN_SYNC_IN_PROGRESS)
// This is the latest block that BTCd is aware of.
const btcdHeight = Number(match[1])
this.emit(GOT_CURRENT_BLOCK_HEIGHT, btcdHeight)
const btcdHeight = match[1]
this.setCurrentBlockHeight(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)
)
.then(height => (height > btcdHeight ? this.setCurrentBlockHeight(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}`))
}
@ -133,19 +167,41 @@ class Neutrino extends EventEmitter {
// Lnd as received some updated block data.
if (this.is(CHAIN_SYNC_WAITING) || this.is(CHAIN_SYNC_IN_PROGRESS)) {
let height
let cfilter
let match
if ((match = line.match(/Rescanned through block.+\(height (\d+)/))) {
height = match[1]
} else if ((match = line.match(/Caught up to height (\d+)/))) {
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]
} 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) {
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.
@ -173,7 +229,7 @@ class Neutrino extends EventEmitter {
* @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) {
is(state: string) {
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.
* @param {String} state Target state.
*/
setState(state) {
setState(state: string) {
if (state !== this.chainSyncStatus) {
this.chainSyncStatus = 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

14
app/lib/zap/controller.js

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

9
app/reducers/ipc.js

@ -1,5 +1,11 @@
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 { receiveAddress } from './address'
import { receiveCryptocurrency } from './ticker'
@ -54,6 +60,7 @@ const ipc = createIpc({
lndSyncStatus,
currentBlockHeight,
lndBlockHeight,
lndCfilterHeight,
lightningGrpcActive,
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_COMPLETE = 'SET_SYNC_STATUS_COMPLETE'
export const RECEIVE_BLOCK_HEIGHT = 'RECEIVE_BLOCK_HEIGHT'
export const RECEIVE_BLOCK = 'RECEIVE_BLOCK'
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_LIGHTNING_WALLET_ACTIVE = 'SET_LIGHTNING_WALLET_ACTIVE'
@ -64,20 +65,19 @@ export const lightningGrpcActive = () => dispatch => {
dispatch({ type: SET_LIGHTNING_WALLET_ACTIVE })
}
// Receive IPC event for LND streaming a line
export const lndBlockHeight = (event, height) => dispatch => {
dispatch({ type: RECEIVE_BLOCK, lndBlockHeight: height })
// Receive IPC event for current height.
export const currentBlockHeight = (event, height) => dispatch => {
dispatch({ type: RECEIVE_CURRENT_BLOCK_HEIGHT, blockHeight: height })
}
export const currentBlockHeight = (event, height) => dispatch => {
dispatch({ type: RECEIVE_BLOCK_HEIGHT, blockHeight: height })
// Receive IPC event for LND block height.
export const lndBlockHeight = (event, height) => dispatch => {
dispatch({ type: RECEIVE_LND_BLOCK_HEIGHT, lndBlockHeight: height })
}
export function receiveBlockHeight(blockHeight) {
return {
type: RECEIVE_BLOCK_HEIGHT,
blockHeight
}
// Receive IPC event for LND cfilter height.
export const lndCfilterHeight = (event, height) => dispatch => {
dispatch({ type: RECEIVE_LND_CFILTER_HEIGHT, lndCfilterHeight: height })
}
// ------------------------------------
@ -89,11 +89,12 @@ const ACTION_HANDLERS = {
[SET_SYNC_STATUS_IN_PROGRESS]: state => ({ ...state, syncStatus: 'in-progress' }),
[SET_SYNC_STATUS_COMPLETE]: state => ({ ...state, syncStatus: 'complete' }),
[RECEIVE_BLOCK_HEIGHT]: (state, { blockHeight }) => ({
[RECEIVE_CURRENT_BLOCK_HEIGHT]: (state, { blockHeight }) => ({
...state,
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 })
}
@ -105,7 +106,8 @@ const initialState = {
syncStatus: 'pending',
lightningGrpcActive: false,
blockHeight: 0,
lndBlockHeight: 0
lndBlockHeight: 0,
lndCfilterHeight: 0
}
// ------------------------------------
@ -114,12 +116,16 @@ const initialState = {
const lndSelectors = {}
const blockHeightSelector = state => state.lnd.blockHeight
const lndBlockHeightSelector = state => state.lnd.lndBlockHeight
const lndCfilterHeightSelector = state => state.lnd.lndCfilterHeight
lndSelectors.syncPercentage = createSelector(
blockHeightSelector,
lndBlockHeightSelector,
(blockHeight, lndBlockHeight) => {
const percentage = Math.floor((lndBlockHeight / blockHeight) * 100)
lndCfilterHeightSelector,
(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)) {
return undefined

5
package.json

@ -42,7 +42,8 @@
"config": {
"style_paths": "app/styles/*.scss app/components/**/*.scss",
"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",
@ -255,7 +256,7 @@
"jsdom": "^11.12.0",
"koa-connect": "^2.0.1",
"lint-staged": "^7.2.0",
"lnd-binary": "^0.3.4",
"lnd-binary": "^0.3.5",
"minimist": "^1.2.0",
"node-sass": "^4.9.0",
"prettier": "^1.13.5",

2
resources/lnd.conf

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

220
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";
// import "google/api/annotations.proto";
@ -76,6 +76,17 @@ service WalletUnlocker {
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 {
@ -160,6 +171,21 @@ message UnlockWalletRequest {
}
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 {
/** lncli: `walletbalance`
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
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`
AddInvoice attempts to add a new invoice to the invoice database. Any
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
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) {
option (google.api.http) = {
@ -602,6 +665,16 @@ message TransactionDetails {
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 {
/// The identity pubkey of the payment recipient
bytes dest = 1;
@ -609,7 +682,7 @@ message SendRequest {
/// The hex-encoded identity pubkey of the payment recipient
string dest_string = 2;
/// Number of satoshis to send.
/// Number of satoshis to send.
int64 amt = 3;
/// The hash to use within the payment's HTLC
@ -625,8 +698,19 @@ message SendRequest {
*/
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;
/**
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 {
string payment_error = 1 [json_name = "payment_error"];
@ -634,6 +718,17 @@ message SendResponse {
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 {
oneof funding_txid {
/// Txid of the funding transaction
@ -844,6 +939,7 @@ message Channel {
bool private = 17 [json_name = "private"];
}
message ListChannelsRequest {
bool active_only = 1;
bool inactive_only = 2;
@ -855,6 +951,58 @@ message ListChannelsResponse {
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 {
/// The identity pubkey of the peer
string pub_key = 1 [json_name = "pub_key"];
@ -1172,9 +1320,20 @@ message QueryRoutesRequest {
/// The max number of routes to return.
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 {
repeated Route routes = 1 [ json_name = "routes"];
repeated Route routes = 1 [json_name = "routes"];
}
message Hop {
@ -1284,6 +1443,7 @@ message RoutingPolicy {
int64 min_htlc = 2 [json_name = "min_htlc"];
int64 fee_base_msat = 3 [json_name = "fee_base_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.
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 {
bytes r_hash = 1 [json_name = "r_hash"];
@ -1501,6 +1687,14 @@ message AddInvoiceResponse {
payment to the recipient.
*/
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 {
/**
@ -1521,6 +1715,21 @@ message ListInvoiceResponse {
}
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.
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"
cacache@^11.0.2:
version "11.0.2"
resolved "https://registry.yarnpkg.com/cacache/-/cacache-11.0.2.tgz#ff30541a05302200108a759e660e30786f788764"
version "11.1.0"
resolved "https://registry.yarnpkg.com/cacache/-/cacache-11.1.0.tgz#3d76dbc2e9da413acaad2557051960a4dad3e1a4"
dependencies:
bluebird "^3.5.1"
chownr "^1.0.1"
figgy-pudding "^3.1.0"
glob "^7.1.2"
graceful-fs "^4.1.11"
lru-cache "^4.1.2"
lru-cache "^4.1.3"
mississippi "^3.0.0"
mkdirp "^0.5.1"
move-concurrently "^1.0.1"
@ -4961,8 +4961,8 @@ fd-slicer@~1.0.1:
pend "~1.2.0"
figgy-pudding@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.1.0.tgz#a77ed2284175976c424b390b298569e9df86dd1e"
version "3.2.0"
resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.2.0.tgz#464626b73d7b0fc045a99753d191b0785957ff73"
figures@^1.7.0:
version "1.7.0"
@ -5137,8 +5137,8 @@ follow-redirects@^1.2.3:
debug "^2.4.5"
follow-redirects@^1.3.0:
version "1.5.1"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.1.tgz#67a8f14f5a1f67f962c2c46469c79eaec0a90291"
version "1.5.2"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.2.tgz#5a9d80e0165957e5ef0c1210678fc5c4acb9fb03"
dependencies:
debug "^3.1.0"
@ -7444,9 +7444,9 @@ listr@^0.14.1:
rxjs "^6.1.0"
strip-ansi "^3.0.1"
lnd-binary@^0.3.4:
version "0.3.4"
resolved "https://registry.yarnpkg.com/lnd-binary/-/lnd-binary-0.3.4.tgz#51fcf221a355e7c25499a3948230613aa282abde"
lnd-binary@^0.3.5:
version "0.3.5"
resolved "https://registry.yarnpkg.com/lnd-binary/-/lnd-binary-0.3.5.tgz#5ae89acebc9b6f3801ae26633a4f02bd1e2f9b51"
dependencies:
axios "^0.18.0"
cacache "^11.0.2"
@ -7706,7 +7706,7 @@ lowercase-keys@^1.0.0:
version "1.0.1"
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"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.3.tgz#a1175cf3496dfc8436c156c334b4955992bce69c"
dependencies:

Loading…
Cancel
Save