Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fabo/1831 reconnect #1856

Merged
merged 8 commits into from
Jan 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- [\#1791](https://github.com/cosmos/voyager/pull/1791) Fixed a problem with initializing the Voyager config dir @faboweb
- [\#1707](https://github.com/cosmos/voyager/issues/1707) Governance txs are now disabled if the user doesn't hold any min_deposit token @fedekunze
- [\#1815](https://github.com/cosmos/voyager/pull/1815) Fixed getters for proposals denominator, reverted to 945803d586b83d65547cd16f4cd5994eac2957ea until interfaces are ready @sabau
- [\#1831](https://github.com/cosmos/voyager/issues/1831) Fixed websocket reconnection @faboweb
- [\#1850](https://github.com/cosmos/voyager/pull/1850) Snapshots aligned for unit tests @sabau
- [\#1859](https://github.com/cosmos/voyager/pull/1859) Fix security check in circleci @sabau

Expand Down
3 changes: 0 additions & 3 deletions app/src/renderer/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
v-if="config.modals.error.active"
:body="config.modals.error.message"
/>
<modal-node-halted v-if="config.modals.nodeHalted.active" />
</div>
</template>

Expand All @@ -23,7 +22,6 @@ import AppHeader from "common/AppHeader"
import TmNotifications from "common/TmNotifications"
import ModalError from "common/TmModalError"
import ModalHelp from "common/TmModalHelp"
import ModalNodeHalted from "common/TmModalNodeHalted"
import ModalReceive from "common/TmModalReceive"
import Onboarding from "common/TmOnboarding"
import Session from "common/TmSession"
Expand All @@ -46,7 +44,6 @@ export default {
ModalHelp,
ModalReceive,
TmNotifications,
ModalNodeHalted,
Onboarding,
Session
},
Expand Down
97 changes: 0 additions & 97 deletions app/src/renderer/components/common/TmModalNodeHalted.vue

This file was deleted.

24 changes: 16 additions & 8 deletions app/src/renderer/connectors/rpcWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ module.exports = function setRpcWrapper(container) {

rpcWrapper.rpcInfo.connected = false
},
rpcConnect(rpcURL) {
async rpcConnect(rpcURL) {
let rpcHost =
rpcURL.startsWith(`http`) && rpcURL.indexOf(`//`) !== -1
? rpcURL.split(`//`)[1]
Expand All @@ -38,15 +38,23 @@ module.exports = function setRpcWrapper(container) {
let newRpc = new RpcClient(`${https ? `wss` : `ws`}://${rpcHost}`)
rpcWrapper.rpcInfo.connected = true
// we need to check immediately if the connection fails. later we will not be able to check this error
newRpc.on(`error`, err => {
console.log(`rpc error`, err)
if (err.code === `ECONNREFUSED` || err.code === `ENETUNREACH`) {
rpcWrapper.rpcInfo.connected = false
}
})

container.rpc = newRpc
const connectionAttempt = await Promise.race([
new Promise(resolve => {
newRpc.on(`error`, err => {
resolve({ error: err })
})
}),
newRpc.health()
])
rpcWrapper.rpcInfo.connecting = false

if (connectionAttempt.error) {
rpcWrapper.rpcInfo.connected = false
throw new Error(`WS connection failed`)
}

container.rpc = newRpc
}
}

Expand Down
2 changes: 1 addition & 1 deletion app/src/renderer/connectors/rpcWrapperMock.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const RpcClientMock = {
cb(null, {
block_metas: state.blockMetas.slice(minHeight)
}),
status: cb =>
health: cb =>
cb(null, {
sync_info: {
latest_block_height: 42
Expand Down
23 changes: 2 additions & 21 deletions app/src/renderer/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import routes from "./routes"
import _Node from "./connectors/node"
import _Store from "./vuex/store"
import axios from "axios"
import { sleep } from "./scripts/common"

const _config = require(`../../src/config.json`)

Expand Down Expand Up @@ -78,14 +77,8 @@ export async function main(
next()
})

// in parallel wait for the node to be reachable and then connect to it
onBackendAvailable(node, () => {
node.rpcConnect(config.node_rpc)
store.dispatch(`rpcSubscribe`)
store.dispatch(`subscribeToBlocks`)

store.dispatch(`showInitialScreen`)
})
store.dispatch(`connect`)
store.dispatch(`showInitialScreen`)

return new Vue({
router,
Expand All @@ -94,16 +87,4 @@ export async function main(
}).$mount(`#app`)
}

async function onBackendAvailable(node, callback) {
while (true) {
try {
await node.lcdConnected()
break
} catch (err) {}
await sleep(1000)
}

callback()
}

main()
6 changes: 1 addition & 5 deletions app/src/renderer/vuex/modules/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ export default () => {
active: true,
state: `loading`
},
noNodes: { active: false },
nodeHalted: { active: false }
noNodes: { active: false }
}
}
const state = JSON.parse(JSON.stringify(emptyState))
Expand Down Expand Up @@ -57,9 +56,6 @@ export default () => {
setModalNoNodes(state, value) {
state.modals.noNodes.active = value
},
setModalNodeHalted(state, value) {
state.modals.nodeHalted.active = value
},
setActiveMenu(state, value) {
state.activeMenu = value
},
Expand Down
80 changes: 46 additions & 34 deletions app/src/renderer/vuex/modules/connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,19 @@ export default function({ node }) {
// get tendermint RPC client from basecoin client

const state = {
node: null,
stopConnecting: false,
connected: false,
lastHeader: {
height: 0,
chain_id: ``
},
approvalRequired: null,
mocked: node.mocked
mocked: node.mocked,

externals: {
node,
config
}
}

const mutations = {
Expand All @@ -25,9 +29,6 @@ export default function({ node }) {
setConnected(state, connected) {
state.connected = connected
},
setNode(state, node) {
state.node = node
},
setNodeApprovalRequired(state, hash) {
state.approvalRequired = hash
}
Expand All @@ -43,32 +44,47 @@ export default function({ node }) {
if (rootState.user.signedIn)
await dispatch(`maybeUpdateValidators`, header)
},
async reconnect({ state, commit }) {
async connect({ state, commit, dispatch }) {
const { node, config } = state.externals

if (state.stopConnecting) return

commit(`setConnected`, false)
node.rpcConnect(config.node_rpc)
try {
await node.rpcConnect(config.node_rpc)
commit(`setConnected`, true)
dispatch(`rpcSubscribe`)
dispatch(`subscribeToBlocks`)
} catch (err) {
console.log(`Failed reconnect attempt`)
setTimeout(() => {
dispatch(`connect`)
}, 1000)
}
},
async rpcSubscribe({ commit, dispatch }) {
const { node } = state.externals
if (state.stopConnecting) return

// the rpc socket can be closed before we can even attach a listener
// so we remember if the connection is open
// we handle the reconnection here so we can attach all these listeners on reconnect
if (!node.rpcInfo.connected) {
await sleep(500)
dispatch(`reconnect`)
dispatch(`connect`)
return
}

commit(`setConnected`, true)
commit(`setNode`, node)

// TODO: get event from light-client websocket instead of RPC connection (once that exists)
node.rpc.on(`error`, error => {
if (error.message.indexOf(`disconnected`) !== -1) {
if (
error instanceof Event || // this is always a disconnect, strange that it is 2 different types
error.message.indexOf(`disconnected`) !== -1
) {
commit(`setConnected`, false)
dispatch(`reconnect`)
dispatch(`connect`)
}
})
node.rpc.status().then(status => {
Expand Down Expand Up @@ -99,30 +115,26 @@ export default function({ node }) {
}
}, nodeHaltedTimeout) // default 30s
},
nodeHasHalted({ commit }) {
console.log(`node has halted`)
clearTimeout(state.nodeHaltedTimeout)
state.nodeHaltedTimeout = undefined
commit(`setModalNodeHalted`, true)
},
pollRPCConnection({ state, dispatch }, timeout = config.block_timeout) {
if (state.nodeTimeout || state.stopConnecting) return

state.nodeTimeout = setTimeout(() => {
// clear timeout doesn't work
if (state.nodeTimeout && !state.mocked) {
state.connected = false
state.nodeTimeout = null
dispatch(`pollRPCConnection`)
}
async pollRPCConnection(
{ state, dispatch, commit },
timeout = config.block_timeout
) {
const { node } = state.externals
if (state.stopConnecting) return

try {
// TODO: replace with ping when we implement ws connection ourselves
await node.rpc.health()
} catch (err) {
console.error(`Error pinging websocket. Assuming connection dropped.`)
commit(`setConnected`, false)
dispatch(`connect`)
return
}

setTimeout(() => {
dispatch(`pollRPCConnection`)
}, timeout)
node.rpc.status().then(() => {
state.nodeTimeout = null
state.connected = true
setTimeout(() => {
dispatch(`pollRPCConnection`)
}, timeout)
})
}
}

Expand Down
5 changes: 1 addition & 4 deletions test/unit/specs/App.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,6 @@ describe(`App Start`, () => {
node_rpc: `http://localhost:12344`
})

expect(node.rpcConnect).toHaveBeenCalledWith(`http://localhost:12344`)
expect(store.dispatch).toHaveBeenCalledWith(`rpcSubscribe`)
expect(store.dispatch).toHaveBeenCalledWith(`subscribeToBlocks`)
expect(store.dispatch).toHaveBeenCalledWith(`showInitialScreen`)
expect(store.dispatch).toHaveBeenCalledWith(`connect`)
})
})
Loading