Browse Source

Merge pull request #548 from mrfelton/fix/fetch-block-height-early

Ensure that we fetch the correct block height in the case where BTCd is mid way through syncing
renovate/lint-staged-8.x
Ben Woosley 7 years ago
committed by GitHub
parent
commit
15a9889d8c
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 30
      app/api/index.js
  2. 10
      app/components/Onboarding/Syncing.js
  3. 7
      app/containers/Root.js
  4. 31
      app/lnd/lib/neutrino.js
  5. 41
      app/lnd/lib/util.js
  6. 4
      app/reducers/ipc.js
  7. 21
      app/reducers/lnd.js
  8. 3
      app/routes/app/containers/AppContainer.js
  9. 6
      app/zap.js
  10. 1
      package.json
  11. 30
      webpack.config.renderer.dev.js
  12. 9
      webpack.config.renderer.prod.js
  13. 4
      yarn.lock

30
app/api/index.js

@ -28,36 +28,6 @@ export function requestTickers(ids) {
)
}
export function requestBlockHeight() {
const sources = [
{
baseUrl: `${scheme}testnet-api.smartbit.com.au/v1/blockchain/blocks?limit=1`,
path: 'blocks[0].height'
},
{
baseUrl: `${scheme}tchain.api.btc.com/v3/block/latest`,
path: 'data.height'
},
{
baseUrl: `${scheme}api.blockcypher.com/v1/btc/test3`,
path: 'height'
}
]
const fetchData = (baseUrl, path) => {
return axios({
method: 'get',
timeout: 5000,
url: baseUrl
})
.then(response => path.split('.').reduce((a, b) => a[b], response.data))
.catch(() => null)
}
const promises = []
sources.forEach(source => promises.push(fetchData(source.baseUrl, source.path)))
return Promise.race(promises)
}
export function requestSuggestedNodes() {
const BASE_URL = `${scheme}zap.jackmallers.com/suggested-peers`
return axios({

10
app/components/Onboarding/Syncing.js

@ -8,14 +8,7 @@ import { showNotification } from 'notifications'
import styles from './Syncing.scss'
class Syncing extends Component {
componentWillMount() {
const { fetchBlockHeight, blockHeight } = this.props
// If we don't already know the target block height, fetch it now.
if (!blockHeight) {
fetchBlockHeight()
}
}
componentWillMount() {}
render() {
const { hasSynced, syncPercentage, address, blockHeight, lndBlockHeight } = this.props
@ -112,7 +105,6 @@ class Syncing extends Component {
}
Syncing.propTypes = {
fetchBlockHeight: PropTypes.func.isRequired,
address: PropTypes.string.isRequired,
hasSynced: PropTypes.bool,
syncPercentage: PropTypes.number,

7
app/containers/Root.js

@ -31,7 +31,7 @@ import {
updateRecoverSeedInput,
setReEnterSeedIndexes
} from '../reducers/onboarding'
import { fetchBlockHeight, lndSelectors } from '../reducers/lnd'
import { lndSelectors } from '../reducers/lnd'
import { walletAddress } from '../reducers/address'
import Routes from '../routes'
@ -57,9 +57,7 @@ const mapDispatchToProps = {
walletAddress,
updateReEnterSeedInput,
updateRecoverSeedInput,
setReEnterSeedIndexes,
fetchBlockHeight
setReEnterSeedIndexes
}
const mapStateToProps = state => ({
@ -82,7 +80,6 @@ const mapStateToProps = state => ({
const mergeProps = (stateProps, dispatchProps, ownProps) => {
const syncingProps = {
fetchBlockHeight: dispatchProps.fetchBlockHeight,
blockHeight: stateProps.lnd.blockHeight,
lndBlockHeight: stateProps.lnd.lndBlockHeight,
hasSynced: stateProps.info.hasSynced,

31
app/lnd/lib/neutrino.js

@ -3,6 +3,7 @@ import { spawn } from 'child_process'
import EventEmitter from 'events'
import config from '../config'
import { mainLog, lndLog, lndLogGetLevel } from '../../utils/log'
import { fetchBlockHeight } from './util'
class Neutrino extends EventEmitter {
constructor(alias, autopilot) {
@ -14,9 +15,7 @@ class Neutrino extends EventEmitter {
grpcProxyStarted: false,
walletOpened: false,
chainSyncStarted: false,
chainSyncFinished: false,
currentBlockHeight: null,
targetBlockHeight: null
chainSyncFinished: false
}
}
@ -79,11 +78,23 @@ class Neutrino extends EventEmitter {
if (!this.state.chainSyncStarted) {
const match = line.match(/Syncing to block height (\d+)/)
if (match) {
const height = match[1]
// Notify that chhain syncronisation has now started.
this.state.chainSyncStarted = true
this.state.targetBlockHeight = height
this.emit('chain-sync-started')
this.emit('got-final-block-height', height)
// This is the latest block that BTCd is aware of.
const btcdHeight = Number(match[1])
this.emit('got-current-block-height', btcdHeight)
// The height returned from the LND log output may not be the actual current block height (this is the case
// when BTCD is still in the middle of syncing the blockchain) so try to fetch thhe current height from from
// some block explorers just incase.
fetchBlockHeight()
.then(
height => (height > btcdHeight ? this.emit('got-current-block-height', height) : null)
)
// If we were unable to fetch from bock explorers at least we already have what BTCd gave us so just warn.
.catch(err => mainLog.warn(`Unable to fetch block height: ${err.message}`))
}
}
@ -99,13 +110,13 @@ class Neutrino extends EventEmitter {
if (this.state.chainSyncStarted) {
let match
if ((match = line.match(/Downloading headers for blocks (\d+) to \d+/))) {
this.emit('got-current-block-height', match[1])
this.emit('got-lnd-block-height', match[1])
} else if ((match = line.match(/Rescanned through block.+\(height (\d+)/))) {
this.emit('got-current-block-height', match[1])
this.emit('got-lnd-block-height', match[1])
} else if ((match = line.match(/Caught up to height (\d+)/))) {
this.emit('got-current-block-height', match[1])
this.emit('got-lnd-block-height', match[1])
} else if ((match = line.match(/Processed \d* blocks? in the last.+\(height (\d+)/))) {
this.emit('got-current-block-height', match[1])
this.emit('got-lnd-block-height', match[1])
}
}
})

41
app/lnd/lib/util.js

@ -1,15 +1,56 @@
import dns from 'dns'
import fs from 'fs'
import axios from 'axios'
import { promisify } from 'util'
import { lookup } from 'ps-node'
import grpc from 'grpc'
import isIP from 'validator/lib/isIP'
import isPort from 'validator/lib/isPort'
import get from 'lodash.get'
import { mainLog } from '../../utils/log'
const fsReadFile = promisify(fs.readFile)
const dnsLookup = promisify(dns.lookup)
/**
* Helper function to get the current block height.
* @return {Number} The current block height.
*/
export const fetchBlockHeight = () => {
const sources = [
{
baseUrl: `https://testnet-api.smartbit.com.au/v1/blockchain/blocks?limit=1`,
path: 'blocks[0].height'
},
{
baseUrl: `https://tchain.api.btc.com/v3/block/latest`,
path: 'data.height'
},
{
baseUrl: `https://api.blockcypher.com/v1/btc/test3`,
path: 'height'
}
]
const fetchData = (baseUrl, path) => {
mainLog.info(`Fetching current block height from ${baseUrl}`)
return axios({
method: 'get',
timeout: 5000,
url: baseUrl
})
.then(response => {
const height = Number(get(response.data, path))
mainLog.info(`Fetched block height as ${height} from: ${baseUrl}`)
return height
})
.catch(err => {
mainLog.warn(`Unable to fetch block height from ${baseUrl}: ${err.message}`)
})
}
return Promise.race(sources.map(source => fetchData(source.baseUrl, source.path)))
}
/**
* Helper function to return an absolute deadline given a relative timeout in seconds.
* @param {number} timeoutSecs The number of seconds to wait before timing out

4
app/reducers/ipc.js

@ -2,7 +2,7 @@ import createIpc from 'redux-electron-ipc'
import {
lndSyncing,
lndSynced,
lndBlockHeightTarget,
currentBlockHeight,
lndBlockHeight,
grpcDisconnected,
grpcConnected
@ -60,7 +60,7 @@ import {
const ipc = createIpc({
lndSyncing,
lndSynced,
lndBlockHeightTarget,
currentBlockHeight,
lndBlockHeight,
grpcDisconnected,
grpcConnected,

21
app/reducers/lnd.js

@ -3,7 +3,6 @@ import { createSelector } from 'reselect'
import { fetchTicker } from './ticker'
import { fetchBalance } from './balance'
import { fetchInfo, setHasSynced } from './info'
import { requestBlockHeight } from '../api'
import { showNotification } from '../notifications'
// ------------------------------------
// Constants
@ -62,16 +61,10 @@ export const lndBlockHeight = (event, height) => dispatch => {
dispatch({ type: RECEIVE_BLOCK, lndBlockHeight: height })
}
export const lndBlockHeightTarget = (event, height) => dispatch => {
export const currentBlockHeight = (event, height) => dispatch => {
dispatch({ type: RECEIVE_BLOCK_HEIGHT, blockHeight: height })
}
export function getBlockHeight() {
return {
type: GET_BLOCK_HEIGHT
}
}
export function receiveBlockHeight(blockHeight) {
return {
type: RECEIVE_BLOCK_HEIGHT,
@ -79,13 +72,6 @@ export function receiveBlockHeight(blockHeight) {
}
}
// Fetch current block height
export const fetchBlockHeight = () => async dispatch => {
dispatch(getBlockHeight())
const blockHeight = await requestBlockHeight()
dispatch(receiveBlockHeight(blockHeight))
}
// ------------------------------------
// Action Handlers
// ------------------------------------
@ -93,11 +79,9 @@ const ACTION_HANDLERS = {
[START_SYNCING]: state => ({ ...state, syncing: true }),
[STOP_SYNCING]: state => ({ ...state, syncing: false }),
[GET_BLOCK_HEIGHT]: state => ({ ...state, fetchingBlockHeight: true }),
[RECEIVE_BLOCK_HEIGHT]: (state, { blockHeight }) => ({
...state,
blockHeight,
fetchingBlockHeight: false
blockHeight
}),
[RECEIVE_BLOCK]: (state, { lndBlockHeight }) => ({ ...state, lndBlockHeight }),
@ -111,7 +95,6 @@ const ACTION_HANDLERS = {
const initialState = {
syncing: false,
grpcStarted: false,
fetchingBlockHeight: false,
lines: [],
blockHeight: 0,
lndBlockHeight: 0

3
app/routes/app/containers/AppContainer.js

@ -32,7 +32,7 @@ import { payInvoice } from 'reducers/payment'
import { createInvoice, fetchInvoice } from 'reducers/invoice'
import { fetchBlockHeight, lndSelectors } from 'reducers/lnd'
import { lndSelectors } from 'reducers/lnd'
import {
fetchChannels,
@ -99,7 +99,6 @@ const mapDispatchToProps = {
createInvoice,
fetchInvoice,
fetchBlockHeight,
clearError,
fetchBalance,

6
app/zap.js

@ -170,11 +170,11 @@ class ZapController {
this.sendMessage('lndSynced')
})
this.neutrino.on('got-final-block-height', height => {
this.sendMessage('lndBlockHeightTarget', Number(height))
this.neutrino.on('got-current-block-height', height => {
this.sendMessage('currentBlockHeight', Number(height))
})
this.neutrino.on('got-current-block-height', height => {
this.neutrino.on('got-lnd-block-height', height => {
this.sendMessage('lndBlockHeight', Number(height))
})

1
package.json

@ -236,6 +236,7 @@
"electron-store": "^2.0.0",
"font-awesome": "^4.7.0",
"history": "^4.6.3",
"lodash.get": "^4.4.2",
"moment": "^2.22.2",
"prop-types": "^15.5.10",
"qrcode.react": "0.8.0",

30
webpack.config.renderer.dev.js

@ -233,8 +233,7 @@ export default merge.smart(baseConfig, {
'http://localhost:*',
'ws://localhost:*',
'https://api.coinmarketcap.com',
'https://zap.jackmallers.com',
'https://testnet-api.smartbit.com.au'
'https://zap.jackmallers.com'
],
'script-src': ["'self'", 'http://localhost:*', "'unsafe-eval'"],
'font-src': [
@ -297,33 +296,6 @@ export default merge.smart(baseConfig, {
})
)
)
app.use(
convert(
proxy('/proxy/testnet-api.smartbit.com.au', {
target: 'https://testnet-api.smartbit.com.au',
pathRewrite: { '^/proxy/testnet-api.smartbit.com.au': '' },
changeOrigin: true
})
)
)
app.use(
convert(
proxy('/proxy/tchain.api.btc.com', {
target: 'https://tchain.api.btc.com',
pathRewrite: { '^/proxy/tchain.api.btc.com': '' },
changeOrigin: true
})
)
)
app.use(
convert(
proxy('/proxy/api.blockcypher.com', {
target: 'https://api.blockcypher.com',
pathRewrite: { '^/proxy/api.blockcypher.com': '' },
changeOrigin: true
})
)
)
app.use(
convert(
history({

9
webpack.config.renderer.prod.js

@ -155,14 +155,7 @@ export default merge.smart(baseConfig, {
new CspHtmlWebpackPlugin({
'default-src': "'self'",
'object-src': "'none'",
'connect-src': [
"'self'",
'https://api.coinmarketcap.com',
'https://zap.jackmallers.com',
'https://testnet-api.smartbit.com.au',
'https://tchain.api.btc.com',
'https://api.blockcypher.com'
],
'connect-src': ["'self'", 'https://api.coinmarketcap.com', 'https://zap.jackmallers.com'],
'script-src': ["'self'"],
'font-src': [
"'self'",

4
yarn.lock

@ -7268,6 +7268,10 @@ lodash.foreach@^4.3.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz#1a6a35eace401280c7f06dddec35165ab27e3e53"
lodash.get@^4.4.2:
version "4.4.2"
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
lodash.isarguments@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a"

Loading…
Cancel
Save