Browse Source

Merge pull request #538 from mrfelton/feat/handle-btcd-not-synced-case

Handle case where BTCd node is not fully synced with the blockchain
renovate/lint-staged-8.x
Ben Woosley 6 years ago
committed by GitHub
parent
commit
3eacc492db
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 66
      app/components/Onboarding/Syncing.js
  2. 2
      app/components/Onboarding/Syncing.scss
  3. 7
      app/containers/Root.js
  4. 117
      app/lnd/lib/neutrino.js
  5. 6
      app/reducers/ipc.js
  6. 60
      app/reducers/lnd.js
  7. 9
      app/zap.js

66
app/components/Onboarding/Syncing.js

@ -8,15 +8,60 @@ import { showNotification } from 'notifications'
import styles from './Syncing.scss'
class Syncing extends Component {
componentWillMount() {}
state = {
timer: null,
syncMessageDetail: null
}
componentWillMount() {
const { syncStatus } = this.props
// If we are still waiting for peers after some time, advise te user it could take a wile.
let timer = setTimeout(() => {
if (syncStatus === 'waiting') {
this.setState({
syncMessageDetail:
'It looks like this could take some time - you might want to grab a coffee or try again later!'
})
}
}, 10000)
this.setState({ timer })
}
componentWillUnmount() {
const { timer } = this.state
clearInterval(timer)
}
render() {
const { hasSynced, syncPercentage, address, blockHeight, lndBlockHeight } = this.props
const {
hasSynced,
syncStatus,
syncPercentage,
address,
blockHeight,
lndBlockHeight
} = this.props
let { syncMessageDetail } = this.state
const copyClicked = () => {
copy(address)
showNotification('Noice', 'Successfully copied to clipboard')
}
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
}
if (typeof hasSynced === 'undefined') {
return null
@ -78,24 +123,16 @@ class Syncing extends Component {
)}
<section className={styles.progressContainer}>
<h3>Syncing to the blockchain...</h3>
<h3>Syncing to the blockchain</h3>
<div className={styles.progressBar}>
<div
className={styles.progress}
style={{ width: syncPercentage ? `${syncPercentage}%` : 0 }}
/>
</div>
<h4>
{typeof syncPercentage === 'undefined' && 'Preparing...'}
{Boolean(syncPercentage >= 0 && syncPercentage < 99) && `${syncPercentage}%`}
{Boolean(syncPercentage >= 99) && 'Finalizing...'}
</h4>
{Boolean(syncPercentage >= 0 && syncPercentage < 99) && (
<span className={styles.progressCounter}>
{Boolean(!blockHeight || !lndBlockHeight) && 'starting...'}
{Boolean(blockHeight && lndBlockHeight) &&
`${lndBlockHeight.toLocaleString()} of ${blockHeight.toLocaleString()}`}
</span>
<h4>{syncMessage}</h4>
{syncMessageDetail && (
<span className={styles.progressDetail}>{syncMessageDetail}</span>
)}
</section>
</div>
@ -107,6 +144,7 @@ class Syncing extends Component {
Syncing.propTypes = {
address: PropTypes.string.isRequired,
hasSynced: PropTypes.bool,
syncStatus: PropTypes.string.isRequired,
syncPercentage: PropTypes.number,
blockHeight: PropTypes.number,
lndBlockHeight: PropTypes.number

2
app/components/Onboarding/Syncing.scss

@ -95,7 +95,7 @@
margin-top: 10px;
}
.progressCounter {
.progressDetail {
color: $gold;
font-size: 12px;
margin-top: 10px;

7
app/containers/Root.js

@ -81,6 +81,7 @@ const mapStateToProps = state => ({
const mergeProps = (stateProps, dispatchProps, ownProps) => {
const syncingProps = {
blockHeight: stateProps.lnd.blockHeight,
syncStatus: stateProps.lnd.syncStatus,
lndBlockHeight: stateProps.lnd.lndBlockHeight,
hasSynced: stateProps.info.hasSynced,
syncPercentage: stateProps.syncPercentage,
@ -213,7 +214,11 @@ const Root = ({
}
// If we are syncing show the syncing screen
if (lnd.grpcStarted && lnd.syncing) {
if (
onboardingProps.onboarding.connectionType === 'local' &&
lnd.grpcStarted &&
lnd.syncStatus !== 'complete'
) {
return <Syncing {...syncingProps} />
}

117
app/lnd/lib/neutrino.js

@ -5,20 +5,37 @@ 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.state = {
grpcProxyStarted: false,
walletOpened: false,
chainSyncStarted: false,
chainSyncFinished: false
}
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.')
@ -59,28 +76,43 @@ class Neutrino extends EventEmitter {
}
// gRPC started.
if (!this.state.grpcProxyStarted) {
if (!this.grpcProxyStarted) {
if (line.includes('gRPC proxy started') && line.includes('password')) {
this.state.grpcProxyStarted = true
this.grpcProxyStarted = true
this.emit('grpc-proxy-started')
}
}
// Wallet opened.
if (!this.state.walletOpened) {
if (!this.walletOpened) {
if (line.includes('gRPC proxy started') && !line.includes('password')) {
this.state.walletOpened = true
this.walletOpened = true
this.emit('wallet-opened')
}
}
// LND syncing has started.
if (!this.state.chainSyncStarted) {
// 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.state.chainSyncStarted = true
this.emit('chain-sync-started')
this.setState(NEUTRINO_SYNC_STATUS_IN_PROGRESS)
// This is the latest block that BTCd is aware of.
const btcdHeight = Number(match[1])
@ -98,25 +130,27 @@ class Neutrino extends EventEmitter {
}
}
// LND syncing has completed.
if (!this.state.chainSyncFinished) {
if (line.includes('Chain backend is fully synced')) {
this.state.chainSyncFinished = true
this.emit('chain-sync-finished')
}
}
// Pass current block height progress to front end for loading state UX
if (this.state.chainSyncStarted) {
// 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(/Downloading headers for blocks (\d+) to \d+/))) {
this.emit('got-lnd-block-height', match[1])
} else if ((match = line.match(/Rescanned through block.+\(height (\d+)/))) {
this.emit('got-lnd-block-height', match[1])
if ((match = line.match(/Rescanned through block.+\(height (\d+)/))) {
height = match[1]
} else if ((match = line.match(/Caught up to height (\d+)/))) {
this.emit('got-lnd-block-height', match[1])
height = match[1]
} else if ((match = line.match(/Processed \d* blocks? in the last.+\(height (\d+)/))) {
this.emit('got-lnd-block-height', match[1])
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)
}
}
})
@ -124,12 +158,35 @@ class Neutrino extends EventEmitter {
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

6
app/reducers/ipc.js

@ -1,7 +1,6 @@
import createIpc from 'redux-electron-ipc'
import {
lndSyncing,
lndSynced,
lndSyncStatus,
currentBlockHeight,
lndBlockHeight,
grpcDisconnected,
@ -58,8 +57,7 @@ import {
// Import all receiving IPC event handlers and pass them into createIpc
const ipc = createIpc({
lndSyncing,
lndSynced,
lndSyncStatus,
currentBlockHeight,
lndBlockHeight,
grpcDisconnected,

60
app/reducers/lnd.js

@ -7,10 +7,11 @@ import { showNotification } from '../notifications'
// ------------------------------------
// Constants
// ------------------------------------
export const START_SYNCING = 'START_SYNCING'
export const STOP_SYNCING = 'STOP_SYNCING'
export const SET_SYNC_STATUS_PENDING = 'SET_SYNC_STATUS_PENDING'
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 GET_BLOCK_HEIGHT = 'GET_BLOCK_HEIGHT'
export const RECEIVE_BLOCK_HEIGHT = 'RECEIVE_BLOCK_HEIGHT'
export const RECEIVE_BLOCK = 'RECEIVE_BLOCK'
@ -21,11 +22,11 @@ export const GRPC_CONNECTED = 'GRPC_CONNECTED'
// Actions
// ------------------------------------
// Receive IPC event for LND starting its syncing process
export const lndSyncing = () => dispatch => dispatch({ type: START_SYNCING })
// Receive IPC event for LND sync status change.
export const lndSyncStatus = (event, status) => (dispatch, getState) => {
const notifTitle = 'Lightning Node Synced'
const notifBody = "Visa who? You're your own payment processor now!"
// Receive IPC event for LND stoping sync
export const lndSynced = () => (dispatch, getState) => {
// Persist the fact that the wallet has been synced at least once.
const state = getState()
const pubKey = state.info.data.identity_pubkey
@ -34,19 +35,29 @@ export const lndSynced = () => (dispatch, getState) => {
store.set(`${pubKey}.hasSynced`, true)
}
dispatch({ type: STOP_SYNCING })
dispatch(setHasSynced(true))
// Fetch data now that we know LND is synced
dispatch(fetchTicker())
dispatch(fetchBalance())
dispatch(fetchInfo())
// HTML 5 desktop notification for the new transaction
const notifTitle = 'Lightning Node Synced'
const notifBody = "Visa who? You're your own payment processor now!"
showNotification(notifTitle, notifBody)
switch (status) {
case 'waiting':
dispatch({ type: SET_SYNC_STATUS_WAITING })
break
case 'in-progress':
dispatch({ type: SET_SYNC_STATUS_IN_PROGRESS })
break
case 'complete':
dispatch({ type: SET_SYNC_STATUS_COMPLETE })
dispatch(setHasSynced(true))
// Fetch data now that we know LND is synced
dispatch(fetchTicker())
dispatch(fetchBalance())
dispatch(fetchInfo())
// HTML 5 desktop notification for the new transaction
showNotification(notifTitle, notifBody)
break
default:
dispatch({ type: SET_SYNC_STATUS_PENDING })
}
}
export const grpcDisconnected = () => dispatch => dispatch({ type: GRPC_DISCONNECTED })
@ -76,8 +87,10 @@ export function receiveBlockHeight(blockHeight) {
// Action Handlers
// ------------------------------------
const ACTION_HANDLERS = {
[START_SYNCING]: state => ({ ...state, syncing: true }),
[STOP_SYNCING]: state => ({ ...state, syncing: false }),
[SET_SYNC_STATUS_PENDING]: state => ({ ...state, syncStatus: 'pending' }),
[SET_SYNC_STATUS_WAITING]: state => ({ ...state, syncStatus: 'waiting' }),
[SET_SYNC_STATUS_IN_PROGRESS]: state => ({ ...state, syncStatus: 'in-progress' }),
[SET_SYNC_STATUS_COMPLETE]: state => ({ ...state, syncStatus: 'complete' }),
[RECEIVE_BLOCK_HEIGHT]: (state, { blockHeight }) => ({
...state,
@ -93,9 +106,8 @@ const ACTION_HANDLERS = {
// Reducer
// ------------------------------------
const initialState = {
syncing: false,
syncStatus: 'pending',
grpcStarted: false,
lines: [],
blockHeight: 0,
lndBlockHeight: 0
}

9
app/zap.js

@ -160,14 +160,19 @@ class ZapController {
this.startGrpc()
})
this.neutrino.on('chain-sync-waiting', () => {
mainLog.info('Neutrino sync waiting')
this.sendMessage('lndSyncStatus', 'waiting')
})
this.neutrino.on('chain-sync-started', () => {
mainLog.info('Neutrino sync started')
this.sendMessage('lndSyncing')
this.sendMessage('lndSyncStatus', 'in-progress')
})
this.neutrino.on('chain-sync-finished', () => {
mainLog.info('Neutrino sync finished')
this.sendMessage('lndSynced')
this.sendMessage('lndSyncStatus', 'complete')
})
this.neutrino.on('got-current-block-height', height => {

Loading…
Cancel
Save