diff --git a/app/components/Onboarding/Syncing.js b/app/components/Onboarding/Syncing.js
index c8932b67..27b76d71 100644
--- a/app/components/Onboarding/Syncing.js
+++ b/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 {
)}
- Syncing to the blockchain...
+ Syncing to the blockchain
-
- {typeof syncPercentage === 'undefined' && 'Preparing...'}
- {Boolean(syncPercentage >= 0 && syncPercentage < 99) && `${syncPercentage}%`}
- {Boolean(syncPercentage >= 99) && 'Finalizing...'}
-
- {Boolean(syncPercentage >= 0 && syncPercentage < 99) && (
-
- {Boolean(!blockHeight || !lndBlockHeight) && 'starting...'}
- {Boolean(blockHeight && lndBlockHeight) &&
- `${lndBlockHeight.toLocaleString()} of ${blockHeight.toLocaleString()}`}
-
+ {syncMessage}
+ {syncMessageDetail && (
+ {syncMessageDetail}
)}
@@ -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
diff --git a/app/components/Onboarding/Syncing.scss b/app/components/Onboarding/Syncing.scss
index 9f0513b2..3ffd9808 100644
--- a/app/components/Onboarding/Syncing.scss
+++ b/app/components/Onboarding/Syncing.scss
@@ -95,7 +95,7 @@
margin-top: 10px;
}
- .progressCounter {
+ .progressDetail {
color: $gold;
font-size: 12px;
margin-top: 10px;
diff --git a/app/containers/Root.js b/app/containers/Root.js
index b64a26b1..e6bd7677 100644
--- a/app/containers/Root.js
+++ b/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
}
diff --git a/app/lnd/lib/neutrino.js b/app/lnd/lib/neutrino.js
index b6b6fe61..0685a877 100644
--- a/app/lnd/lib/neutrino.js
+++ b/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
diff --git a/app/reducers/ipc.js b/app/reducers/ipc.js
index e8064367..beabeeb9 100644
--- a/app/reducers/ipc.js
+++ b/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,
diff --git a/app/reducers/lnd.js b/app/reducers/lnd.js
index c90d241b..ab3efef8 100644
--- a/app/reducers/lnd.js
+++ b/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
}
diff --git a/app/zap.js b/app/zap.js
index e2172db4..8e9abc77 100644
--- a/app/zap.js
+++ b/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 => {