{\n\tconstructor(props) {\n\t\tsuper(props);\n\t\tthis.container = React.createRef();\n\t\tthis.content = React.createRef();\n\t}\n\n\tcomponentDidUpdate(prevProps, prevState, snapshot) {\n\t\tif (this.content && typeof this.content.didUpdate === 'function') {\n\t\t\tthis.content.didUpdate(prevProps, prevState, snapshot);\n\t\t}\n\t}\n\n\tonScroll = () => {\n\t\tif (this.content && typeof this.content.onScroll === 'function') {\n\t\t\tthis.content.onScroll();\n\t\t}\n\t};\n\n\tgetSnapshotBeforeUpdate(prevProps: Readonly, prevState: Readonly) {\n\t\tif (this.content && typeof this.content.beforeUpdate === 'function') {\n\t\t\treturn this.content.beforeUpdate();\n\t\t}\n\t\treturn null;\n\t}\n\n\trender() {\n\t\tconst {\n\t\t\tclasses, active, content, shouldUpdate,\n\t\t} = this.props;\n\n\t\tlet children = null;\n\t\tswitch (active) {\n\t\tcase MENU.get('home').id:\n\t\t\tchildren =
Work in progress.
;\n\t\t\tbreak;\n\t\tcase MENU.get('chain').id:\n\t\t\tchildren = Work in progress.
;\n\t\t\tbreak;\n\t\tcase MENU.get('txpool').id:\n\t\t\tchildren = Work in progress.
;\n\t\t\tbreak;\n\t\tcase MENU.get('network').id:\n\t\t\tchildren = ;\n\t\t\tbreak;\n\t\tcase MENU.get('system').id:\n\t\t\tchildren = Work in progress.
;\n\t\t\tbreak;\n\t\tcase MENU.get('logs').id:\n\t\t\tchildren = (\n\t\t\t\t { this.content = ref; }}\n\t\t\t\t\tcontainer={this.container}\n\t\t\t\t\tsend={this.props.send}\n\t\t\t\t\tcontent={this.props.content}\n\t\t\t\t\tshouldUpdate={shouldUpdate}\n\t\t\t\t/>\n\t\t\t);\n\t\t}\n\n\t\treturn (\n\t\t\t\n\t\t\t\t
{ this.container = ref; }}\n\t\t\t\t\tonScroll={this.onScroll}\n\t\t\t\t>\n\t\t\t\t\t{children}\n\t\t\t\t
\n\t\t\t\t
\n\t\t\t
\n\t\t);\n\t}\n}\n\nexport default withStyles(themeStyles)(Main);\n",
- "// @flow\n\n// Copyright 2018 The go-ethereum Authors\n// This file is part of the go-ethereum library.\n//\n// The go-ethereum library is free software: you can redistribute it and/or modify\n// it under the terms of the GNU Lesser General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// The go-ethereum library is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU Lesser General Public License for more details.\n//\n// You should have received a copy of the GNU Lesser General Public License\n// along with the go-ethereum library. If not, see .\n\nimport React, {Component} from 'react';\n\nimport Table from '@material-ui/core/Table';\nimport TableHead from '@material-ui/core/TableHead';\nimport TableBody from '@material-ui/core/TableBody';\nimport TableRow from '@material-ui/core/TableRow';\nimport TableCell from '@material-ui/core/TableCell';\nimport Grid from '@material-ui/core/Grid/Grid';\nimport Typography from '@material-ui/core/Typography';\nimport {AreaChart, Area, Tooltip, YAxis} from 'recharts';\nimport {FontAwesomeIcon} from '@fortawesome/react-fontawesome';\nimport {faCircle as fasCircle} from '@fortawesome/free-solid-svg-icons';\nimport {faCircle as farCircle} from '@fortawesome/free-regular-svg-icons';\nimport convert from 'color-convert';\n\nimport CustomTooltip, {bytePlotter, multiplier} from 'CustomTooltip';\nimport type {Network as NetworkType, PeerEvent} from '../types/content';\nimport {styles as commonStyles, chartStrokeWidth, hues, hueScale} from '../common';\n\n// Peer chart dimensions.\nconst trafficChartHeight = 18;\nconst trafficChartWidth = 400;\n\n// setMaxIngress adjusts the peer chart's gradient values based on the given value.\nconst setMaxIngress = (peer, value) => {\n\tpeer.maxIngress = value;\n\tpeer.ingressGradient = [];\n\tpeer.ingressGradient.push({offset: hueScale[0], color: hues[0]});\n\tlet i = 1;\n\tfor (; i < hues.length && value > hueScale[i]; i++) {\n\t\tpeer.ingressGradient.push({offset: Math.floor(hueScale[i] * 100 / value), color: hues[i]});\n\t}\n\ti--;\n\tif (i < hues.length - 1) {\n\t\t// Usually the maximum value gets between two points on the predefined\n\t\t// color scale (e.g. 123KB is somewhere between 100KB (#FFFF00) and\n\t\t// 1MB (#FF0000)), and the charts need to be comparable by the colors,\n\t\t// so we have to calculate the last hue using the maximum value and the\n\t\t// surrounding hues in order to avoid the uniformity of the top colors\n\t\t// on the charts. For this reason the two hues are translated into the\n\t\t// CIELAB color space, and the top color will be their weighted average\n\t\t// (CIELAB is perceptually uniform, meaning that any point on the line\n\t\t// between two pure color points is also a pure color, so the weighted\n\t\t// average will not lose from the saturation).\n\t\t//\n\t\t// In case the maximum value is greater than the biggest predefined\n\t\t// scale value, the top of the chart will have uniform color.\n\t\tconst lastHue = convert.hex.lab(hues[i]);\n\t\tconst proportion = (value - hueScale[i]) * 100 / (hueScale[i + 1] - hueScale[i]);\n\t\tconvert.hex.lab(hues[i + 1]).forEach((val, j) => {\n\t\t\tlastHue[j] = (lastHue[j] * proportion + val * (100 - proportion)) / 100;\n\t\t});\n\t\tpeer.ingressGradient.push({offset: 100, color: `))) + (("`" + (`#${convert.lab.hex(lastHue)}` + "`")) + (`});\n\t}\n};\n\n// setMaxEgress adjusts the peer chart's gradient values based on the given value.\n// In case of the egress the chart is upside down, so the gradients need to be\n// calculated inversely compared to the ingress.\nconst setMaxEgress = (peer, value) => {\n\tpeer.maxEgress = value;\n\tpeer.egressGradient = [];\n\tpeer.egressGradient.push({offset: 100 - hueScale[0], color: hues[0]});\n\tlet i = 1;\n\tfor (; i < hues.length && value > hueScale[i]; i++) {\n\t\tpeer.egressGradient.unshift({offset: 100 - Math.floor(hueScale[i] * 100 / value), color: hues[i]});\n\t}\n\ti--;\n\tif (i < hues.length - 1) {\n\t\t// Calculate the last hue.\n\t\tconst lastHue = convert.hex.lab(hues[i]);\n\t\tconst proportion = (value - hueScale[i]) * 100 / (hueScale[i + 1] - hueScale[i]);\n\t\tconvert.hex.lab(hues[i + 1]).forEach((val, j) => {\n\t\t\tlastHue[j] = (lastHue[j] * proportion + val * (100 - proportion)) / 100;\n\t\t});\n\t\tpeer.egressGradient.unshift({offset: 0, color: ` + ("`" + `#${convert.lab.hex(lastHue)}`)))) + ((("`" + `});\n\t}\n};\n\n\n// setIngressChartAttributes searches for the maximum value of the ingress\n// samples, and adjusts the peer chart's gradient values accordingly.\nconst setIngressChartAttributes = (peer) => {\n\tlet max = 0;\n\tpeer.ingress.forEach(({value}) => {\n\t\tif (value > max) {\n\t\t\tmax = value;\n\t\t}\n\t});\n\tsetMaxIngress(peer, max);\n};\n\n// setEgressChartAttributes searches for the maximum value of the egress\n// samples, and adjusts the peer chart's gradient values accordingly.\nconst setEgressChartAttributes = (peer) => {\n\tlet max = 0;\n\tpeer.egress.forEach(({value}) => {\n\t\tif (value > max) {\n\t\t\tmax = value;\n\t\t}\n\t});\n\tsetMaxEgress(peer, max);\n};\n\n// inserter is a state updater function for the main component, which handles the peers.\nexport const inserter = (sampleLimit: number) => (update: NetworkType, prev: NetworkType) => {\n\t// The first message contains the metered peer history.\n\tif (update.peers && update.peers.bundles) {\n\t\tprev.peers = update.peers;\n\t\tObject.values(prev.peers.bundles).forEach((bundle) => {\n\t\t\tif (bundle.knownPeers) {\n\t\t\t\tObject.values(bundle.knownPeers).forEach((peer) => {\n\t\t\t\t\tif (!peer.maxIngress) {\n\t\t\t\t\t\tsetIngressChartAttributes(peer);\n\t\t\t\t\t}\n\t\t\t\t\tif (!peer.maxEgress) {\n\t\t\t\t\t\tsetEgressChartAttributes(peer);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\t}\n\tif (Array.isArray(update.diff)) {\n\t\tupdate.diff.forEach((event: PeerEvent) => {\n\t\t\tif (!event.ip) {\n\t\t\t\tconsole.error('Peer event without IP', event);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tswitch (event.remove) {\n\t\t\tcase 'bundle': {\n\t\t\t\tdelete prev.peers.bundles[event.ip];\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase 'known': {\n\t\t\t\tif (!event.id) {\n\t\t\t\t\tconsole.error('Remove known peer event without ID', event.ip);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst bundle = prev.peers.bundles[event.ip];\n\t\t\t\tif (!bundle || !bundle.knownPeers || !bundle.knownPeers[event.id]) {\n\t\t\t\t\tconsole.error('No known peer to remove', event.ip, event.id);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tdelete bundle.knownPeers[event.id];\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase 'attempt': {\n\t\t\t\tconst bundle = prev.peers.bundles[event.ip];\n\t\t\t\tif (!bundle || !Array.isArray(bundle.attempts) || bundle.attempts.length < 1) {\n\t\t\t\t\tconsole.error('No unknown peer to remove', event.ip);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tbundle.attempts.splice(0, 1);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t}\n\t\t\tif (!prev.peers.bundles[event.ip]) {\n\t\t\t\tprev.peers.bundles[event.ip] = {\n\t\t\t\t\tlocation: {\n\t\t\t\t\t\tcountry: '',\n\t\t\t\t\t\tcity: '',\n\t\t\t\t\t\tlatitude: 0,\n\t\t\t\t\t\tlongitude: 0,\n\t\t\t\t\t},\n\t\t\t\t\tknownPeers: {},\n\t\t\t\t\tattempts: [],\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst bundle = prev.peers.bundles[event.ip];\n\t\t\tif (event.location) {\n\t\t\t\tbundle.location = event.location;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (!event.id) {\n\t\t\t\tif (!bundle.attempts) {\n\t\t\t\t\tbundle.attempts = [];\n\t\t\t\t}\n\t\t\t\tbundle.attempts.push({\n\t\t\t\t\tconnected: event.connected,\n\t\t\t\t\tdisconnected: event.disconnected,\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (!bundle.knownPeers) {\n\t\t\t\tbundle.knownPeers = {};\n\t\t\t}\n\t\t\tif (!bundle.knownPeers[event.id]) {\n\t\t\t\tbundle.knownPeers[event.id] = {\n\t\t\t\t\tconnected: [],\n\t\t\t\t\tdisconnected: [],\n\t\t\t\t\tingress: [],\n\t\t\t\t\tegress: [],\n\t\t\t\t\tactive: false,\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst peer = bundle.knownPeers[event.id];\n\t\t\tif (!peer.maxIngress) {\n\t\t\t\tsetIngressChartAttributes(peer);\n\t\t\t}\n\t\t\tif (!peer.maxEgress) {\n\t\t\t\tsetEgressChartAttributes(peer);\n\t\t\t}\n\t\t\tif (event.connected) {\n\t\t\t\tif (!peer.connected) {\n\t\t\t\t\tconsole.warn('peer.connected should exist');\n\t\t\t\t\tpeer.connected = [];\n\t\t\t\t}\n\t\t\t\tpeer.connected.push(event.connected);\n\t\t\t}\n\t\t\tif (event.disconnected) {\n\t\t\t\tif (!peer.disconnected) {\n\t\t\t\t\tconsole.warn('peer.disconnected should exist');\n\t\t\t\t\tpeer.disconnected = [];\n\t\t\t\t}\n\t\t\t\tpeer.disconnected.push(event.disconnected);\n\t\t\t}\n\t\t\tswitch (event.activity) {\n\t\t\tcase 'active':\n\t\t\t\tpeer.active = true;\n\t\t\t\tbreak;\n\t\t\tcase 'inactive':\n\t\t\t\tpeer.active = false;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (Array.isArray(event.ingress) && Array.isArray(event.egress)) {\n\t\t\t\tif (event.ingress.length !== event.egress.length) {\n\t\t\t\t\tconsole.error('Different traffic sample length', event);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t// Check if there is a new maximum value, and reset the colors in case.\n\t\t\t\tlet maxIngress = peer.maxIngress;\n\t\t\t\tevent.ingress.forEach(({value}) => {\n\t\t\t\t\tif (value > maxIngress) {\n\t\t\t\t\t\tmaxIngress = value;\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tif (maxIngress > peer.maxIngress) {\n\t\t\t\t\tsetMaxIngress(peer, maxIngress);\n\t\t\t\t}\n\t\t\t\t// Push the new values.\n\t\t\t\tpeer.ingress.splice(peer.ingress.length, 0, ...event.ingress);\n\t\t\t\tconst ingressDiff = peer.ingress.length - sampleLimit;\n\t\t\t\tif (ingressDiff > 0) {\n\t\t\t\t\t// Check if the maximum value is in the beginning.\n\t\t\t\t\tlet i = 0;\n\t\t\t\t\twhile (i < ingressDiff && peer.ingress[i].value < peer.maxIngress) {\n\t\t\t\t\t\ti++;\n\t\t\t\t\t}\n\t\t\t\t\t// Remove the old values from the beginning.\n\t\t\t\t\tpeer.ingress.splice(0, ingressDiff);\n\t\t\t\t\tif (i < ingressDiff) {\n\t\t\t\t\t\t// Reset the colors if the maximum value leaves the chart.\n\t\t\t\t\t\tsetIngressChartAttributes(peer);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// Check if there is a new maximum value, and reset the colors in case.\n\t\t\t\tlet maxEgress = peer.maxEgress;\n\t\t\t\tevent.egress.forEach(({value}) => {\n\t\t\t\t\tif (value > maxEgress) {\n\t\t\t\t\t\tmaxEgress = value;\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tif (maxEgress > peer.maxEgress) {\n\t\t\t\t\tsetMaxEgress(peer, maxEgress);\n\t\t\t\t}\n\t\t\t\t// Push the new values.\n\t\t\t\tpeer.egress.splice(peer.egress.length, 0, ...event.egress);\n\t\t\t\tconst egressDiff = peer.egress.length - sampleLimit;\n\t\t\t\tif (egressDiff > 0) {\n\t\t\t\t\t// Check if the maximum value is in the beginning.\n\t\t\t\t\tlet i = 0;\n\t\t\t\t\twhile (i < egressDiff && peer.egress[i].value < peer.maxEgress) {\n\t\t\t\t\t\ti++;\n\t\t\t\t\t}\n\t\t\t\t\t// Remove the old values from the beginning.\n\t\t\t\t\tpeer.egress.splice(0, egressDiff);\n\t\t\t\t\tif (i < egressDiff) {\n\t\t\t\t\t\t// Reset the colors if the maximum value leaves the chart.\n\t\t\t\t\t\tsetEgressChartAttributes(peer);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n\treturn prev;\n};\n\n// styles contains the constant styles of the component.\nconst styles = {\n\ttableHead: {\n\t\theight: 'auto',\n\t},\n\ttableRow: {\n\t\theight: 'auto',\n\t},\n\ttableCell: {\n\t\tpaddingTop: 0,\n\t\tpaddingRight: 5,\n\t\tpaddingBottom: 0,\n\t\tpaddingLeft: 5,\n\t\tborder: 'none',\n\t},\n};\n\nexport type Props = {\n container: Object,\n content: NetworkType,\n shouldUpdate: Object,\n};\n\ntype State = {};\n\n// Network renders the network page.\nclass Network extends Component {\n\tcomponentDidMount() {\n\t\tconst {container} = this.props;\n\t\tif (typeof container === 'undefined') {\n\t\t\treturn;\n\t\t}\n\t\tcontainer.scrollTop = 0;\n\t}\n\n\tformatTime = (t: string) => {\n\t\tconst time = new Date(t);\n\t\tif (isNaN(time)) {\n\t\t\treturn '';\n\t\t}\n\t\tconst month = `) + ("`" + (`0${time.getMonth() + 1}` + "`"))) + ((`.slice(-2);\n\t\tconst date = ` + ("`" + `0${time.getDate()}`)) + ("`" + (`.slice(-2);\n\t\tconst hours = ` + "`"))))) + ((((`0${time.getHours()}` + "`") + (`.slice(-2);\n\t\tconst minutes = ` + ("`" + `0${time.getMinutes()}`))) + (("`" + (`.slice(-2);\n\t\tconst seconds = ` + "`")) + (`0${time.getSeconds()}` + ("`" + `.slice(-2);\n\t\treturn `)))) + ((("`" + `${month}/${date}/${hours}:${minutes}:${seconds}`) + ("`" + (`;\n\t};\n\n\tcopyToClipboard = (id) => (event) => {\n\t\tevent.preventDefault();\n\t\tnavigator.clipboard.writeText(id).then(() => {}, () => {\n\t\t\tconsole.error(\"Failed to copy node id\", id);\n\t\t});\n\t};\n\n\tpeerTableRow = (ip, id, bundle, peer) => {\n\t\tconst ingressValues = peer.ingress.map(({value}) => ({ingress: value || 0.001}));\n\t\tconst egressValues = peer.egress.map(({value}) => ({egress: -value || -0.001}));\n\n\t\treturn (\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t{peer.active\n\t\t\t\t\t\t? \n\t\t\t\t\t\t: \n\t\t\t\t\t}\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t{id.substring(0, 10)}\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t{bundle.location ? (() => {\n\t\t\t\t\t\tconst l = bundle.location;\n\t\t\t\t\t\treturn `)) + ("`" + (`${l.country ? l.country : ''}${l.city ? ` + "`"))))))) + ((((((`/${l.city}` + "`") + (` : ''}` + ("`" + `;\n\t\t\t\t\t})() : ''}\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{peer.ingressGradient\n\t\t\t\t\t\t\t\t&& peer.ingressGradient.map(({offset, color}, i) => (\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t} />\n\t\t\t\t\t\t Math.max(dataMax, 0)]} />\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{peer.egressGradient\n\t\t\t\t\t\t\t\t&& peer.egressGradient.map(({offset, color}, i) => (\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t} />\n\t\t\t\t\t\t Math.min(dataMin, 0), -0.001]} />\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t);\n\t};\n\n\trender() {\n\t\treturn (\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\tNode ID\n\t\t\t\t\t\t\t\tLocation\n\t\t\t\t\t\t\t\tTraffic\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t{Object.entries(this.props.content.peers.bundles).map(([ip, bundle]) => {\n\t\t\t\t\t\t\t\tif (!bundle.knownPeers || Object.keys(bundle.knownPeers).length < 1) {\n\t\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn Object.entries(bundle.knownPeers).map(([id, peer]) => {\n\t\t\t\t\t\t\t\t\tif (peer.active === false) {\n\t\t\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\treturn this.peerTableRow(ip, id, bundle, peer);\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t{Object.entries(this.props.content.peers.bundles).map(([ip, bundle]) => {\n\t\t\t\t\t\t\t\tif (!bundle.knownPeers || Object.keys(bundle.knownPeers).length < 1) {\n\t\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn Object.entries(bundle.knownPeers).map(([id, peer]) => {\n\t\t\t\t\t\t\t\t\tif (peer.active === true) {\n\t\t\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\treturn this.peerTableRow(ip, id, bundle, peer);\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\tConnection attempts\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\tIP\n\t\t\t\t\t\t\t\tLocation\n\t\t\t\t\t\t\t\tNr\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t{Object.entries(this.props.content.peers.bundles).map(([ip, bundle]) => {\n\t\t\t\t\t\t\t\tif (!bundle.attempts || bundle.attempts.length < 1) {\n\t\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t{ip}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{bundle.location ? (() => {\n\t\t\t\t\t\t\t\t\t\t\t\tconst l = bundle.location;\n\t\t\t\t\t\t\t\t\t\t\t\treturn `))) + (("`" + (`${l.country ? l.country : ''}${l.city ? ` + "`")) + (`/${l.city}` + ("`" + ` : ''}`)))) + ((("`" + `;\n\t\t\t\t\t\t\t\t\t\t\t})() : ''}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{Object.values(bundle.attempts).length}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\n\t\t\t\n\t\t);\n\t}\n}\n\nexport default Network;\n",
+ "// @flow\n\n// Copyright 2018 The go-ethereum Authors\n// This file is part of the go-ethereum library.\n//\n// The go-ethereum library is free software: you can redistribute it and/or modify\n// it under the terms of the GNU Lesser General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// The go-ethereum library is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU Lesser General Public License for more details.\n//\n// You should have received a copy of the GNU Lesser General Public License\n// along with the go-ethereum library. If not, see .\n\nimport React, {Component} from 'react';\n\nimport Table from '@material-ui/core/Table';\nimport TableHead from '@material-ui/core/TableHead';\nimport TableBody from '@material-ui/core/TableBody';\nimport TableRow from '@material-ui/core/TableRow';\nimport TableCell from '@material-ui/core/TableCell';\nimport Grid from '@material-ui/core/Grid/Grid';\nimport Typography from '@material-ui/core/Typography';\nimport {AreaChart, Area, Tooltip, YAxis} from 'recharts';\nimport {FontAwesomeIcon} from '@fortawesome/react-fontawesome';\nimport {faCircle as fasCircle} from '@fortawesome/free-solid-svg-icons';\nimport {faCircle as farCircle} from '@fortawesome/free-regular-svg-icons';\nimport convert from 'color-convert';\n\nimport CustomTooltip, {bytePlotter, multiplier} from 'CustomTooltip';\nimport type {Network as NetworkType, PeerEvent} from '../types/content';\nimport {styles as commonStyles, chartStrokeWidth, hues, hueScale} from '../common';\n\n// Peer chart dimensions.\nconst trafficChartHeight = 15;\nconst trafficChartWidth = 300;\n\n// attemptSeparator separates the peer connection attempts\n// such as the peers from the addresses with more attempts\n// go to the beginning of the table, and the rest go to the end.\nconst attemptSeparator = 9;\n\n// setMaxIngress adjusts the peer chart's gradient values based on the given value.\nconst setMaxIngress = (peer, value) => {\n\tpeer.maxIngress = value;\n\tpeer.ingressGradient = [];\n\tpeer.ingressGradient.push({offset: hueScale[0], color: hues[0]});\n\tlet i = 1;\n\tfor (; i < hues.length && value > hueScale[i]; i++) {\n\t\tpeer.ingressGradient.push({offset: Math.floor(hueScale[i] * 100 / value), color: hues[i]});\n\t}\n\ti--;\n\tif (i < hues.length - 1) {\n\t\t// Usually the maximum value gets between two points on the predefined\n\t\t// color scale (e.g. 123KB is somewhere between 100KB (#FFFF00) and\n\t\t// 1MB (#FF0000)), and the charts need to be comparable by the colors,\n\t\t// so we have to calculate the last hue using the maximum value and the\n\t\t// surrounding hues in order to avoid the uniformity of the top colors\n\t\t// on the charts. For this reason the two hues are translated into the\n\t\t// CIELAB color space, and the top color will be their weighted average\n\t\t// (CIELAB is perceptually uniform, meaning that any point on the line\n\t\t// between two pure color points is also a pure color, so the weighted\n\t\t// average will not lose from the saturation).\n\t\t//\n\t\t// In case the maximum value is greater than the biggest predefined\n\t\t// scale value, the top of the chart will have uniform color.\n\t\tconst lastHue = convert.hex.lab(hues[i]);\n\t\tconst proportion = (value - hueScale[i]) * 100 / (hueScale[i + 1] - hueScale[i]);\n\t\tconvert.hex.lab(hues[i + 1]).forEach((val, j) => {\n\t\t\tlastHue[j] = (lastHue[j] * proportion + val * (100 - proportion)) / 100;\n\t\t});\n\t\tpeer.ingressGradient.push({offset: 100, color: `))) + (("`" + (`#${convert.lab.hex(lastHue)}` + "`")) + (`});\n\t}\n};\n\n// setMaxEgress adjusts the peer chart's gradient values based on the given value.\n// In case of the egress the chart is upside down, so the gradients need to be\n// calculated inversely compared to the ingress.\nconst setMaxEgress = (peer, value) => {\n\tpeer.maxEgress = value;\n\tpeer.egressGradient = [];\n\tpeer.egressGradient.push({offset: 100 - hueScale[0], color: hues[0]});\n\tlet i = 1;\n\tfor (; i < hues.length && value > hueScale[i]; i++) {\n\t\tpeer.egressGradient.unshift({offset: 100 - Math.floor(hueScale[i] * 100 / value), color: hues[i]});\n\t}\n\ti--;\n\tif (i < hues.length - 1) {\n\t\t// Calculate the last hue.\n\t\tconst lastHue = convert.hex.lab(hues[i]);\n\t\tconst proportion = (value - hueScale[i]) * 100 / (hueScale[i + 1] - hueScale[i]);\n\t\tconvert.hex.lab(hues[i + 1]).forEach((val, j) => {\n\t\t\tlastHue[j] = (lastHue[j] * proportion + val * (100 - proportion)) / 100;\n\t\t});\n\t\tpeer.egressGradient.unshift({offset: 0, color: ` + ("`" + `#${convert.lab.hex(lastHue)}`)))) + ((("`" + `});\n\t}\n};\n\n\n// setIngressChartAttributes searches for the maximum value of the ingress\n// samples, and adjusts the peer chart's gradient values accordingly.\nconst setIngressChartAttributes = (peer) => {\n\tlet max = 0;\n\tpeer.ingress.forEach(({value}) => {\n\t\tif (value > max) {\n\t\t\tmax = value;\n\t\t}\n\t});\n\tsetMaxIngress(peer, max);\n};\n\n// setEgressChartAttributes searches for the maximum value of the egress\n// samples, and adjusts the peer chart's gradient values accordingly.\nconst setEgressChartAttributes = (peer) => {\n\tlet max = 0;\n\tpeer.egress.forEach(({value}) => {\n\t\tif (value > max) {\n\t\t\tmax = value;\n\t\t}\n\t});\n\tsetMaxEgress(peer, max);\n};\n\n// shortName adds some heuristics to the node name in order to make it look meaningful.\nconst shortName = (name: string) => {\n\tconst parts = name.split('/');\n\tif (parts[0].substring(0, 'parity'.length).toLowerCase() === 'parity') {\n\t\t// Merge Parity and Parity-Ethereum under the same name.\n\t\tparts[0] = 'Parity';\n\t}\n\t// Cutting anything from the version after the first - or +.\n\tparts[1] = parts[1].split('-')[0].split('+')[0];\n\treturn `) + ("`" + (`${parts[0]}/${parts[1]}` + "`"))) + ((`;\n};\n\n// shortLocation returns a shortened version of the given location object.\nconst shortLocation = (location: Object) => {\n\tif (!location) {\n\t\treturn '';\n\t}\n\treturn ` + ("`" + `${location.city ? `)) + ("`" + (`${location.city}/` + "`"))))) + ((((` : ''}${location.country ? location.country : ''}` + "`") + (`;\n};\n\n// ethProtocol returns a shortened version of the eth protocol values.\nconst ethProtocol = (protocols: Object) => {\n\tconst {eth} = protocols;\n\tif (typeof eth === 'string') {\n\t\treturn eth;\n\t}\n\tif (!(eth instanceof Object)) {\n\t\tconsole.error('Wrong protocol type', eth, typeof eth);\n\t\treturn '';\n\t}\n\tif (!eth.hasOwnProperty('version') || !eth.hasOwnProperty('difficulty') || !eth.hasOwnProperty('head')) {\n\t\tconsole.error('Missing protocol attributes', eth);\n\t\treturn '';\n\t}\n\treturn ` + ("`" + `h=${eth.head.substring(0, 10)} v=${eth.version} td=${eth.difficulty}`))) + (("`" + (`;\n};\n\n// inserter is a state updater function for the main component, which handles the peers.\nexport const inserter = (sampleLimit: number) => (update: NetworkType, prev: NetworkType) => {\n\t// The first message contains the metered peer history.\n\tif (update.peers && update.peers.bundles) {\n\t\tprev.peers = update.peers;\n\t\tObject.values(prev.peers.bundles).forEach((bundle) => {\n\t\t\tif (bundle.knownPeers) {\n\t\t\t\tObject.values(bundle.knownPeers).forEach((peer) => {\n\t\t\t\t\tif (!peer.maxIngress) {\n\t\t\t\t\t\tsetIngressChartAttributes(peer);\n\t\t\t\t\t}\n\t\t\t\t\tif (!peer.maxEgress) {\n\t\t\t\t\t\tsetEgressChartAttributes(peer);\n\t\t\t\t\t}\n\t\t\t\t\tif (!peer.name) {\n\t\t\t\t\t\tpeer.name = '';\n\t\t\t\t\t\tpeer.shortName = '';\n\t\t\t\t\t} else if (!peer.shortName) {\n\t\t\t\t\t\tpeer.shortName = shortName(peer.name);\n\t\t\t\t\t}\n\t\t\t\t\tif (!peer.enode) {\n\t\t\t\t\t\tpeer.enode = '';\n\t\t\t\t\t}\n\t\t\t\t\tif (!peer.protocols) {\n\t\t\t\t\t\tpeer.protocols = {};\n\t\t\t\t\t}\n\t\t\t\t\tpeer.eth = ethProtocol(peer.protocols);\n\t\t\t\t});\n\t\t\t}\n\t\t\tbundle.shortLocation = shortLocation(bundle.location);\n\t\t});\n\t}\n\tif (Array.isArray(update.diff)) {\n\t\tupdate.diff.forEach((event: PeerEvent) => {\n\t\t\tif (!event.addr) {\n\t\t\t\tconsole.error('Peer event without TCP address', event);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tswitch (event.remove) {\n\t\t\tcase 'bundle': {\n\t\t\t\tdelete prev.peers.bundles[event.addr];\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase 'known': {\n\t\t\t\tif (!event.enode) {\n\t\t\t\t\tconsole.error('Remove known peer event without node URL', event.addr);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst bundle = prev.peers.bundles[event.addr];\n\t\t\t\tif (!bundle || !bundle.knownPeers || !bundle.knownPeers[event.enode]) {\n\t\t\t\t\tconsole.error('No known peer to remove', event.addr, event.enode);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tdelete bundle.knownPeers[event.enode];\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t}\n\t\t\tif (!prev.peers.bundles[event.addr]) {\n\t\t\t\tprev.peers.bundles[event.addr] = {\n\t\t\t\t\tlocation: {\n\t\t\t\t\t\tcountry: '',\n\t\t\t\t\t\tcity: '',\n\t\t\t\t\t\tlatitude: 0,\n\t\t\t\t\t\tlongitude: 0,\n\t\t\t\t\t},\n\t\t\t\t\tshortLocation: '',\n\t\t\t\t\tknownPeers: {},\n\t\t\t\t\tattempts: 0,\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst bundle = prev.peers.bundles[event.addr];\n\t\t\tif (event.location) {\n\t\t\t\tbundle.location = event.location;\n\t\t\t\tbundle.shortLocation = shortLocation(bundle.location);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (!event.enode) {\n\t\t\t\tbundle.attempts++;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (!bundle.knownPeers) {\n\t\t\t\tbundle.knownPeers = {};\n\t\t\t}\n\t\t\tif (!bundle.knownPeers[event.enode]) {\n\t\t\t\tbundle.knownPeers[event.enode] = {\n\t\t\t\t\tconnected: [],\n\t\t\t\t\tdisconnected: [],\n\t\t\t\t\tingress: [],\n\t\t\t\t\tegress: [],\n\t\t\t\t\tactive: false,\n\t\t\t\t\tname: '',\n\t\t\t\t\tshortName: '',\n\t\t\t\t\tenode: '',\n\t\t\t\t\tprotocols: {},\n\t\t\t\t\teth: '',\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst peer = bundle.knownPeers[event.enode];\n\t\t\tif (event.name) {\n\t\t\t\tpeer.name = event.name;\n\t\t\t\tpeer.shortName = shortName(event.name);\n\t\t\t}\n\t\t\tif (event.enode) {\n\t\t\t\tpeer.enode = event.enode;\n\t\t\t}\n\t\t\tif (event.protocols) {\n\t\t\t\tpeer.protocols = event.protocols;\n\t\t\t\tpeer.eth = ethProtocol(peer.protocols);\n\t\t\t}\n\t\t\tif (!peer.maxIngress) {\n\t\t\t\tsetIngressChartAttributes(peer);\n\t\t\t}\n\t\t\tif (!peer.maxEgress) {\n\t\t\t\tsetEgressChartAttributes(peer);\n\t\t\t}\n\t\t\tif (event.connected) {\n\t\t\t\tif (!peer.connected) {\n\t\t\t\t\tconsole.warn('peer.connected should exist');\n\t\t\t\t\tpeer.connected = [];\n\t\t\t\t}\n\t\t\t\tpeer.connected.push(event.connected);\n\t\t\t}\n\t\t\tif (event.disconnected) {\n\t\t\t\tif (!peer.disconnected) {\n\t\t\t\t\tconsole.warn('peer.disconnected should exist');\n\t\t\t\t\tpeer.disconnected = [];\n\t\t\t\t}\n\t\t\t\tpeer.disconnected.push(event.disconnected);\n\t\t\t}\n\t\t\tswitch (event.activity) {\n\t\t\tcase 'active':\n\t\t\t\tpeer.active = true;\n\t\t\t\tbreak;\n\t\t\tcase 'inactive':\n\t\t\t\tpeer.active = false;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (Array.isArray(event.ingress) && Array.isArray(event.egress)) {\n\t\t\t\tif (event.ingress.length !== event.egress.length) {\n\t\t\t\t\tconsole.error('Different traffic sample length', event);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t// Check if there is a new maximum value, and reset the colors in case.\n\t\t\t\tlet maxIngress = peer.maxIngress;\n\t\t\t\tevent.ingress.forEach(({value}) => {\n\t\t\t\t\tif (value > maxIngress) {\n\t\t\t\t\t\tmaxIngress = value;\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tif (maxIngress > peer.maxIngress) {\n\t\t\t\t\tsetMaxIngress(peer, maxIngress);\n\t\t\t\t}\n\t\t\t\t// Push the new values.\n\t\t\t\tpeer.ingress.splice(peer.ingress.length, 0, ...event.ingress);\n\t\t\t\tconst ingressDiff = peer.ingress.length - sampleLimit;\n\t\t\t\tif (ingressDiff > 0) {\n\t\t\t\t\t// Check if the maximum value is in the beginning.\n\t\t\t\t\tlet i = 0;\n\t\t\t\t\twhile (i < ingressDiff && peer.ingress[i].value < peer.maxIngress) {\n\t\t\t\t\t\ti++;\n\t\t\t\t\t}\n\t\t\t\t\t// Remove the old values from the beginning.\n\t\t\t\t\tpeer.ingress.splice(0, ingressDiff);\n\t\t\t\t\tif (i < ingressDiff) {\n\t\t\t\t\t\t// Reset the colors if the maximum value leaves the chart.\n\t\t\t\t\t\tsetIngressChartAttributes(peer);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// Check if there is a new maximum value, and reset the colors in case.\n\t\t\t\tlet maxEgress = peer.maxEgress;\n\t\t\t\tevent.egress.forEach(({value}) => {\n\t\t\t\t\tif (value > maxEgress) {\n\t\t\t\t\t\tmaxEgress = value;\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tif (maxEgress > peer.maxEgress) {\n\t\t\t\t\tsetMaxEgress(peer, maxEgress);\n\t\t\t\t}\n\t\t\t\t// Push the new values.\n\t\t\t\tpeer.egress.splice(peer.egress.length, 0, ...event.egress);\n\t\t\t\tconst egressDiff = peer.egress.length - sampleLimit;\n\t\t\t\tif (egressDiff > 0) {\n\t\t\t\t\t// Check if the maximum value is in the beginning.\n\t\t\t\t\tlet i = 0;\n\t\t\t\t\twhile (i < egressDiff && peer.egress[i].value < peer.maxEgress) {\n\t\t\t\t\t\ti++;\n\t\t\t\t\t}\n\t\t\t\t\t// Remove the old values from the beginning.\n\t\t\t\t\tpeer.egress.splice(0, egressDiff);\n\t\t\t\t\tif (i < egressDiff) {\n\t\t\t\t\t\t// Reset the colors if the maximum value leaves the chart.\n\t\t\t\t\t\tsetEgressChartAttributes(peer);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n\treturn prev;\n};\n\n// styles contains the constant styles of the component.\nconst styles = {\n\ttable: {\n\t\tbackground: '#212121',\n\t\tborderCollapse: 'unset',\n\t\tpadding: 5,\n\t},\n\ttableHead: {\n\t\theight: 'auto',\n\t},\n\ttableRow: {\n\t\theight: 'auto',\n\t},\n\ttableCell: {\n\t\tpaddingTop: 0,\n\t\tpaddingRight: 5,\n\t\tpaddingBottom: 0,\n\t\tpaddingLeft: 5,\n\t\tborder: 'none',\n\t\tfontFamily: 'monospace',\n\t\tfontSize: 10,\n\t},\n};\n\n// limitedWidthStyle returns a style object which cuts the long text with three dots.\nconst limitedWidthStyle = (width) => {\n\treturn {\n\t\ttextOverflow: 'ellipsis',\n\t\tmaxWidth: width,\n\t\toverflow: 'hidden',\n\t\twhiteSpace: 'nowrap',\n\t};\n};\n\nexport type Props = {\n container: Object,\n content: NetworkType,\n shouldUpdate: Object,\n};\n\ntype State = {};\n\n// Network renders the network page.\nclass Network extends Component {\n\tcomponentDidMount() {\n\t\tconst {container} = this.props;\n\t\tif (typeof container === 'undefined') {\n\t\t\treturn;\n\t\t}\n\t\tcontainer.scrollTop = 0;\n\t}\n\n\tformatTime = (t: string) => {\n\t\tconst time = new Date(t);\n\t\tif (isNaN(time)) {\n\t\t\treturn '';\n\t\t}\n\t\tconst month = ` + "`")) + (`0${time.getMonth() + 1}` + ("`" + `.slice(-2);\n\t\tconst date = `)))) + ((("`" + `0${time.getDate()}`) + ("`" + (`.slice(-2);\n\t\tconst hours = ` + "`"))) + ((`0${time.getHours()}` + ("`" + `.slice(-2);\n\t\tconst minutes = `)) + ("`" + (`0${time.getMinutes()}` + "`"))))))) + ((((((`.slice(-2);\n\t\tconst seconds = ` + "`") + (`0${time.getSeconds()}` + ("`" + `.slice(-2);\n\t\treturn `))) + (("`" + (`${month}/${date}/${hours}:${minutes}:${seconds}` + "`")) + (`;\n\t};\n\n\tcopyToClipboard = (text: string) => (event) => {\n\t\tevent.preventDefault();\n\t\tnavigator.clipboard.writeText(text).then(() => {}, () => {\n\t\t\tconsole.error(\"Failed to copy\", text);\n\t\t});\n\t};\n\n\tknownPeerTableRow = (addr, enode, bundle, peer) => {\n\t\tconst ingressValues = peer.ingress.map(({value}) => ({ingress: value || 0.001}));\n\t\tconst egressValues = peer.egress.map(({value}) => ({egress: -value || -0.001}));\n\t\treturn (\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t{peer.active\n\t\t\t\t\t\t? \n\t\t\t\t\t\t: \n\t\t\t\t\t}\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t{enode.substring(8, 16)}\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t{peer.shortName}\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t{bundle.shortLocation}\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{peer.ingressGradient\n\t\t\t\t\t\t\t\t&& peer.ingressGradient.map(({offset, color}, i) => (\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t} />\n\t\t\t\t\t\t Math.max(dataMax, 0)]} />\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{peer.egressGradient\n\t\t\t\t\t\t\t\t&& peer.egressGradient.map(({offset, color}, i) => (\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t} />\n\t\t\t\t\t\t Math.min(dataMin, 0), -0.001]} />\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t{peer.eth}\n\t\t\t\t\n\t\t\t\n\t\t);\n\t};\n\n\tconnectionAttemptTableRow = (addr, bundle) => (\n\t\t\n\t\t\t\n\t\t\t\t{addr}\n\t\t\t\n\t\t\t\n\t\t\t\t{bundle.shortLocation}\n\t\t\t\n\t\t\t\n\t\t\t\t{bundle.attempts}\n\t\t\t\n\t\t\n\t);\n\n\trender() {\n\t\treturn (\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\tNode URL\n\t\t\t\t\t\t\t\tName\n\t\t\t\t\t\t\t\tLocation\n\t\t\t\t\t\t\t\tTraffic\n\t\t\t\t\t\t\t\tETH protocol\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t{Object.entries(this.props.content.peers.bundles).map(([addr, bundle]) => {\n\t\t\t\t\t\t\t\tif (!bundle.knownPeers || Object.keys(bundle.knownPeers).length < 1) {\n\t\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn Object.entries(bundle.knownPeers).map(([enode, peer]) => {\n\t\t\t\t\t\t\t\t\tif (peer.active === false) {\n\t\t\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\treturn this.knownPeerTableRow(addr, enode, bundle, peer);\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t{Object.entries(this.props.content.peers.bundles).map(([addr, bundle]) => {\n\t\t\t\t\t\t\t\tif (!bundle.knownPeers || Object.keys(bundle.knownPeers).length < 1) {\n\t\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn Object.entries(bundle.knownPeers).map(([enode, peer]) => {\n\t\t\t\t\t\t\t\t\tif (peer.active === true) {\n\t\t\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\treturn this.knownPeerTableRow(addr, enode, bundle, peer);\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\tConnection attempts\n\t\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\tTCP address\n\t\t\t\t\t\t\t\t\tLocation\n\t\t\t\t\t\t\t\t\tNr\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{Object.entries(this.props.content.peers.bundles).map(([addr, bundle]) => {\n\t\t\t\t\t\t\t\t\tif (!bundle.attempts || bundle.attempts <= attemptSeparator) {\n\t\t\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\treturn this.connectionAttemptTableRow(addr, bundle);\n\t\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{Object.entries(this.props.content.peers.bundles).map(([addr, bundle]) => {\n\t\t\t\t\t\t\t\t\tif (!bundle.attempts || bundle.attempts < 1 || bundle.attempts > attemptSeparator) {\n\t\t\t\t\t\t\t\t\t\treturn null;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\treturn this.connectionAttemptTableRow(addr, bundle);\n\t\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\n\t\t\t\n\t\t);\n\t}\n}\n\nexport default Network;\n",
"// @flow\n\n// Copyright 2017 The go-ethereum Authors\n// This file is part of the go-ethereum library.\n//\n// The go-ethereum library is free software: you can redistribute it and/or modify\n// it under the terms of the GNU Lesser General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// The go-ethereum library is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU Lesser General Public License for more details.\n//\n// You should have received a copy of the GNU Lesser General Public License\n// along with the go-ethereum library. If not, see .\n\nimport React, {Component} from 'react';\n\nimport withStyles from '@material-ui/core/styles/withStyles';\nimport List from '@material-ui/core/List';\nimport ListItem from '@material-ui/core/ListItem';\nimport ListItemIcon from '@material-ui/core/ListItemIcon';\nimport ListItemText from '@material-ui/core/ListItemText';\nimport Icon from '@material-ui/core/Icon';\nimport Transition from 'react-transition-group/Transition';\nimport {FontAwesomeIcon} from '@fortawesome/react-fontawesome';\n\nimport {MENU, DURATION} from '../common';\n\n// styles contains the constant styles of the component.\nconst styles = {\n\tmenu: {\n\t\tdefault: {\n\t\t\ttransition: `) + ("`" + (`margin-left ${DURATION}ms` + "`"))) + ((`,\n\t\t},\n\t\ttransition: {\n\t\t\tentered: {marginLeft: -200},\n\t\t},\n\t},\n};\n\n// themeStyles returns the styles generated from the theme for the component.\nconst themeStyles = theme => ({\n\tlist: {\n\t\tbackground: theme.palette.grey[900],\n\t},\n\tlistItem: {\n\t\tminWidth: theme.spacing.unit * 7,\n\t},\n\ticon: {\n\t\tfontSize: theme.spacing.unit * 3,\n\t\toverflow: 'unset',\n\t},\n});\n\nexport type Props = {\n\tclasses: Object, // injected by withStyles()\n\topened: boolean,\n\tchangeContent: string => void,\n};\n\ntype State = {}\n\n// SideBar renders the sidebar of the dashboard.\nclass SideBar extends Component {\n\tshouldComponentUpdate(nextProps: Readonly, nextState: Readonly, nextContext: any) {\n\t\treturn nextProps.opened !== this.props.opened;\n\t}\n\n\t// clickOn returns a click event handler function for the given menu item.\n\tclickOn = menu => (event) => {\n\t\tevent.preventDefault();\n\t\tthis.props.changeContent(menu);\n\t};\n\n\t// menuItems returns the menu items corresponding to the sidebar state.\n\tmenuItems = (transitionState) => {\n\t\tconst {classes} = this.props;\n\t\tconst children = [];\n\t\tMENU.forEach((menu) => {\n\t\t\tchildren.push((\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t));\n\t\t});\n\t\treturn children;\n\t};\n\n\t// menu renders the list of the menu items.\n\tmenu = (transitionState: Object) => (\n\t\t\n\t\t\t\n\t\t\t\t{this.menuItems(transitionState)}\n\t\t\t
\n\t\t
\n\t);\n\n\trender() {\n\t\treturn (\n\t\t\t\n\t\t\t\t{this.menu}\n\t\t\t\n\t\t);\n\t}\n}\n\nexport default withStyles(themeStyles)(SideBar);\n",
"// @flow\n\n// Copyright 2017 The go-ethereum Authors\n// This file is part of the go-ethereum library.\n//\n// The go-ethereum library is free software: you can redistribute it and/or modify\n// it under the terms of the GNU Lesser General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// The go-ethereum library is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU Lesser General Public License for more details.\n//\n// You should have received a copy of the GNU Lesser General Public License\n// along with the go-ethereum library. If not, see .\n\nimport React from 'react';\nimport {render} from 'react-dom';\n\nimport MuiThemeProvider from '@material-ui/core/styles/MuiThemeProvider';\nimport createMuiTheme from '@material-ui/core/styles/createMuiTheme';\n\nimport Dashboard from './components/Dashboard';\n\nconst theme: Object = createMuiTheme({\n\t// typography: {\n\t// \tuseNextVariants: true,\n\t// },\n\tpalette: {\n\t\ttype: 'dark',\n\t},\n});\nconst dashboard = document.getElementById('dashboard');\nif (dashboard) {\n\t// Renders the whole dashboard.\n\trender(\n\t\t\n\t\t\t\n\t\t,\n\t\tdashboard,\n\t);\n}\n",
"function _arrayWithoutHoles(arr) {\n if (Array.isArray(arr)) {\n for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) {\n arr2[i] = arr[i];\n }\n\n return arr2;\n }\n}\n\nmodule.exports = _arrayWithoutHoles;",
@@ -37949,7 +38027,7 @@ func bundleJsMap() (*asset, error) {
}
info := bindataFileInfo{name: "bundle.js.map", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
- a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xb6, 0x68, 0x46, 0x48, 0x3, 0x7c, 0x38, 0x69, 0xe7, 0xca, 0x1d, 0x61, 0xd8, 0x6c, 0x20, 0x6a, 0xf5, 0xe3, 0x9c, 0xa5, 0x35, 0xd7, 0xd9, 0x4d, 0x63, 0xd3, 0xe3, 0xdc, 0x80, 0x20, 0x6a, 0xe1}}
+ a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x8a, 0xcd, 0x5a, 0x29, 0xe7, 0xc6, 0xaa, 0x33, 0xee, 0x73, 0xf4, 0x9b, 0xd2, 0xc6, 0xfb, 0x57, 0x12, 0x19, 0x6b, 0xdc, 0x64, 0xb9, 0x10, 0x83, 0xf, 0x2, 0xfa, 0x4, 0xee, 0x75, 0x43, 0x2b}}
return a, nil
}
diff --git a/dashboard/assets/components/Dashboard.jsx b/dashboard/assets/components/Dashboard.jsx
index 944d500907..a88162c355 100644
--- a/dashboard/assets/components/Dashboard.jsx
+++ b/dashboard/assets/components/Dashboard.jsx
@@ -83,8 +83,9 @@ const appender = (limit: number, mapper = replacer) => (update: Array, pre
// the execution of unnecessary operations (e.g. copy of the log array).
const defaultContent: () => Content = () => ({
general: {
- version: null,
- commit: null,
+ version: null,
+ commit: null,
+ syncMode: '',
},
home: {},
chain: {},
@@ -119,8 +120,9 @@ const defaultContent: () => Content = () => ({
// TODO (kurkomisi): Define a tricky type which embraces the content and the updaters.
const updaters = {
general: {
- version: replacer,
- commit: replacer,
+ version: replacer,
+ commit: replacer,
+ syncMode: replacer,
},
home: null,
chain: null,
@@ -241,6 +243,7 @@ class Dashboard extends Component {
({
});
export type Props = {
- classes: Object, // injected by withStyles()
+ classes: Object, // injected by withStyles()
switchSideBar: () => void,
+ syncMode: string,
};
// Header renders the header of the dashboard.
diff --git a/dashboard/assets/components/Network.jsx b/dashboard/assets/components/Network.jsx
index 3a52892eed..ac9a7df8ff 100644
--- a/dashboard/assets/components/Network.jsx
+++ b/dashboard/assets/components/Network.jsx
@@ -36,8 +36,13 @@ import type {Network as NetworkType, PeerEvent} from '../types/content';
import {styles as commonStyles, chartStrokeWidth, hues, hueScale} from '../common';
// Peer chart dimensions.
-const trafficChartHeight = 18;
-const trafficChartWidth = 400;
+const trafficChartHeight = 15;
+const trafficChartWidth = 300;
+
+// attemptSeparator separates the peer connection attempts
+// such as the peers from the addresses with more attempts
+// go to the beginning of the table, and the rest go to the end.
+const attemptSeparator = 9;
// setMaxIngress adjusts the peer chart's gradient values based on the given value.
const setMaxIngress = (peer, value) => {
@@ -120,6 +125,43 @@ const setEgressChartAttributes = (peer) => {
setMaxEgress(peer, max);
};
+// shortName adds some heuristics to the node name in order to make it look meaningful.
+const shortName = (name: string) => {
+ const parts = name.split('/');
+ if (parts[0].substring(0, 'parity'.length).toLowerCase() === 'parity') {
+ // Merge Parity and Parity-Ethereum under the same name.
+ parts[0] = 'Parity';
+ }
+ // Cutting anything from the version after the first - or +.
+ parts[1] = parts[1].split('-')[0].split('+')[0];
+ return `${parts[0]}/${parts[1]}`;
+};
+
+// shortLocation returns a shortened version of the given location object.
+const shortLocation = (location: Object) => {
+ if (!location) {
+ return '';
+ }
+ return `${location.city ? `${location.city}/` : ''}${location.country ? location.country : ''}`;
+};
+
+// ethProtocol returns a shortened version of the eth protocol values.
+const ethProtocol = (protocols: Object) => {
+ const {eth} = protocols;
+ if (typeof eth === 'string') {
+ return eth;
+ }
+ if (!(eth instanceof Object)) {
+ console.error('Wrong protocol type', eth, typeof eth);
+ return '';
+ }
+ if (!eth.hasOwnProperty('version') || !eth.hasOwnProperty('difficulty') || !eth.hasOwnProperty('head')) {
+ console.error('Missing protocol attributes', eth);
+ return '';
+ }
+ return `h=${eth.head.substring(0, 10)} v=${eth.version} td=${eth.difficulty}`;
+};
+
// inserter is a state updater function for the main component, which handles the peers.
export const inserter = (sampleLimit: number) => (update: NetworkType, prev: NetworkType) => {
// The first message contains the metered peer history.
@@ -134,84 +176,101 @@ export const inserter = (sampleLimit: number) => (update: NetworkType, prev: Net
if (!peer.maxEgress) {
setEgressChartAttributes(peer);
}
+ if (!peer.name) {
+ peer.name = '';
+ peer.shortName = '';
+ } else if (!peer.shortName) {
+ peer.shortName = shortName(peer.name);
+ }
+ if (!peer.enode) {
+ peer.enode = '';
+ }
+ if (!peer.protocols) {
+ peer.protocols = {};
+ }
+ peer.eth = ethProtocol(peer.protocols);
});
}
+ bundle.shortLocation = shortLocation(bundle.location);
});
}
if (Array.isArray(update.diff)) {
update.diff.forEach((event: PeerEvent) => {
- if (!event.ip) {
- console.error('Peer event without IP', event);
+ if (!event.addr) {
+ console.error('Peer event without TCP address', event);
return;
}
switch (event.remove) {
case 'bundle': {
- delete prev.peers.bundles[event.ip];
+ delete prev.peers.bundles[event.addr];
return;
}
case 'known': {
- if (!event.id) {
- console.error('Remove known peer event without ID', event.ip);
+ if (!event.enode) {
+ console.error('Remove known peer event without node URL', event.addr);
return;
}
- const bundle = prev.peers.bundles[event.ip];
- if (!bundle || !bundle.knownPeers || !bundle.knownPeers[event.id]) {
- console.error('No known peer to remove', event.ip, event.id);
+ const bundle = prev.peers.bundles[event.addr];
+ if (!bundle || !bundle.knownPeers || !bundle.knownPeers[event.enode]) {
+ console.error('No known peer to remove', event.addr, event.enode);
return;
}
- delete bundle.knownPeers[event.id];
- return;
- }
- case 'attempt': {
- const bundle = prev.peers.bundles[event.ip];
- if (!bundle || !Array.isArray(bundle.attempts) || bundle.attempts.length < 1) {
- console.error('No unknown peer to remove', event.ip);
- return;
- }
- bundle.attempts.splice(0, 1);
+ delete bundle.knownPeers[event.enode];
return;
}
}
- if (!prev.peers.bundles[event.ip]) {
- prev.peers.bundles[event.ip] = {
+ if (!prev.peers.bundles[event.addr]) {
+ prev.peers.bundles[event.addr] = {
location: {
country: '',
city: '',
latitude: 0,
longitude: 0,
},
+ shortLocation: '',
knownPeers: {},
- attempts: [],
+ attempts: 0,
};
}
- const bundle = prev.peers.bundles[event.ip];
+ const bundle = prev.peers.bundles[event.addr];
if (event.location) {
bundle.location = event.location;
+ bundle.shortLocation = shortLocation(bundle.location);
return;
}
- if (!event.id) {
- if (!bundle.attempts) {
- bundle.attempts = [];
- }
- bundle.attempts.push({
- connected: event.connected,
- disconnected: event.disconnected,
- });
+ if (!event.enode) {
+ bundle.attempts++;
return;
}
if (!bundle.knownPeers) {
bundle.knownPeers = {};
}
- if (!bundle.knownPeers[event.id]) {
- bundle.knownPeers[event.id] = {
+ if (!bundle.knownPeers[event.enode]) {
+ bundle.knownPeers[event.enode] = {
connected: [],
disconnected: [],
ingress: [],
egress: [],
active: false,
+ name: '',
+ shortName: '',
+ enode: '',
+ protocols: {},
+ eth: '',
};
}
- const peer = bundle.knownPeers[event.id];
+ const peer = bundle.knownPeers[event.enode];
+ if (event.name) {
+ peer.name = event.name;
+ peer.shortName = shortName(event.name);
+ }
+ if (event.enode) {
+ peer.enode = event.enode;
+ }
+ if (event.protocols) {
+ peer.protocols = event.protocols;
+ peer.eth = ethProtocol(peer.protocols);
+ }
if (!peer.maxIngress) {
setIngressChartAttributes(peer);
}
@@ -305,6 +364,11 @@ export const inserter = (sampleLimit: number) => (update: NetworkType, prev: Net
// styles contains the constant styles of the component.
const styles = {
+ table: {
+ background: '#212121',
+ borderCollapse: 'unset',
+ padding: 5,
+ },
tableHead: {
height: 'auto',
},
@@ -317,9 +381,21 @@ const styles = {
paddingBottom: 0,
paddingLeft: 5,
border: 'none',
+ fontFamily: 'monospace',
+ fontSize: 10,
},
};
+// limitedWidthStyle returns a style object which cuts the long text with three dots.
+const limitedWidthStyle = (width) => {
+ return {
+ textOverflow: 'ellipsis',
+ maxWidth: width,
+ overflow: 'hidden',
+ whiteSpace: 'nowrap',
+ };
+};
+
export type Props = {
container: Object,
content: NetworkType,
@@ -351,33 +427,53 @@ class Network extends Component
{
return `${month}/${date}/${hours}:${minutes}:${seconds}`;
};
- copyToClipboard = (id) => (event) => {
+ copyToClipboard = (text: string) => (event) => {
event.preventDefault();
- navigator.clipboard.writeText(id).then(() => {}, () => {
- console.error("Failed to copy node id", id);
+ navigator.clipboard.writeText(text).then(() => {}, () => {
+ console.error("Failed to copy", text);
});
};
- peerTableRow = (ip, id, bundle, peer) => {
+ knownPeerTableRow = (addr, enode, bundle, peer) => {
const ingressValues = peer.ingress.map(({value}) => ({ingress: value || 0.001}));
const egressValues = peer.egress.map(({value}) => ({egress: -value || -0.001}));
-
return (
-
+
{peer.active
?
:
}
-
- {id.substring(0, 10)}
+
+ {enode.substring(8, 16)}
-
- {bundle.location ? (() => {
- const l = bundle.location;
- return `${l.country ? l.country : ''}${l.city ? `/${l.city}` : ''}`;
- })() : ''}
+
+ {peer.shortName}
+
+
+ {bundle.shortLocation}
{
height={trafficChartHeight}
data={ingressValues}
margin={{top: 5, right: 5, bottom: 0, left: 5}}
- syncId={`peerIngress_${ip}_${id}`}
+ syncId={`peerIngress_${addr}_${enode}`}
>
-
+
{peer.ingressGradient
&& peer.ingressGradient.map(({offset, color}, i) => (
@@ -405,7 +501,7 @@ class Network extends Component {
dataKey='ingress'
isAnimationActive={false}
type='monotone'
- fill={`url(#ingressGradient_${ip}_${id})`}
+ fill={`url(#ingressGradient_${addr}_${enode})`}
stroke={peer.ingressGradient[peer.ingressGradient.length - 1].color}
strokeWidth={chartStrokeWidth}
/>
@@ -415,14 +511,14 @@ class Network extends Component {
height={trafficChartHeight}
data={egressValues}
margin={{top: 0, right: 5, bottom: 5, left: 5}}
- syncId={`peerIngress_${ip}_${id}`}
+ syncId={`peerIngress_${addr}_${enode}`}
>
-
+
{peer.egressGradient
&& peer.egressGradient.map(({offset, color}, i) => (
@@ -435,91 +531,116 @@ class Network extends Component {
dataKey='egress'
isAnimationActive={false}
type='monotone'
- fill={`url(#egressGradient_${ip}_${id})`}
+ fill={`url(#egressGradient_${addr}_${enode})`}
stroke={peer.egressGradient[0].color}
strokeWidth={chartStrokeWidth}
/>
+
+ {peer.eth}
+
);
};
+ connectionAttemptTableRow = (addr, bundle) => (
+
+
+ {addr}
+
+
+ {bundle.shortLocation}
+
+
+ {bundle.attempts}
+
+
+ );
+
render() {
return (
-
+
- Node ID
+ Node URL
+ Name
Location
Traffic
+ ETH protocol
- {Object.entries(this.props.content.peers.bundles).map(([ip, bundle]) => {
+ {Object.entries(this.props.content.peers.bundles).map(([addr, bundle]) => {
if (!bundle.knownPeers || Object.keys(bundle.knownPeers).length < 1) {
return null;
}
- return Object.entries(bundle.knownPeers).map(([id, peer]) => {
+ return Object.entries(bundle.knownPeers).map(([enode, peer]) => {
if (peer.active === false) {
return null;
}
- return this.peerTableRow(ip, id, bundle, peer);
+ return this.knownPeerTableRow(addr, enode, bundle, peer);
});
})}
- {Object.entries(this.props.content.peers.bundles).map(([ip, bundle]) => {
+ {Object.entries(this.props.content.peers.bundles).map(([addr, bundle]) => {
if (!bundle.knownPeers || Object.keys(bundle.knownPeers).length < 1) {
return null;
}
- return Object.entries(bundle.knownPeers).map(([id, peer]) => {
+ return Object.entries(bundle.knownPeers).map(([enode, peer]) => {
if (peer.active === true) {
return null;
}
- return this.peerTableRow(ip, id, bundle, peer);
+ return this.knownPeerTableRow(addr, enode, bundle, peer);
});
})}
-
- Connection attempts
-
-
-
-
- IP
- Location
- Nr
-
-
-
- {Object.entries(this.props.content.peers.bundles).map(([ip, bundle]) => {
- if (!bundle.attempts || bundle.attempts.length < 1) {
- return null;
- }
- return (
-
- {ip}
-
- {bundle.location ? (() => {
- const l = bundle.location;
- return `${l.country ? l.country : ''}${l.city ? `/${l.city}` : ''}`;
- })() : ''}
-
-
- {Object.values(bundle.attempts).length}
-
-
- );
- })}
-
-
+
+
+ Connection attempts
+
+
+
+
+ TCP address
+ Location
+ Nr
+
+
+
+ {Object.entries(this.props.content.peers.bundles).map(([addr, bundle]) => {
+ if (!bundle.attempts || bundle.attempts <= attemptSeparator) {
+ return null;
+ }
+ return this.connectionAttemptTableRow(addr, bundle);
+ })}
+
+
+ {Object.entries(this.props.content.peers.bundles).map(([addr, bundle]) => {
+ if (!bundle.attempts || bundle.attempts < 1 || bundle.attempts > attemptSeparator) {
+ return null;
+ }
+ return this.connectionAttemptTableRow(addr, bundle);
+ })}
+
+
+
);
diff --git a/dashboard/assets/types/content.jsx b/dashboard/assets/types/content.jsx
index 3086d44244..99bc71b51d 100644
--- a/dashboard/assets/types/content.jsx
+++ b/dashboard/assets/types/content.jsx
@@ -33,8 +33,9 @@ export type ChartEntry = {
};
export type General = {
- version: ?string,
- commit: ?string,
+ version: ?string,
+ commit: ?string,
+ syncMode: string,
};
export type Home = {
@@ -55,8 +56,10 @@ export type Network = {
};
export type PeerEvent = {
- ip: string,
- id: string,
+ name: string,
+ addr: string,
+ enode: string,
+ protocols: {[string]: Object},
remove: string,
location: GeoLocation,
connected: Date,
@@ -71,9 +74,9 @@ export type Peers = {
};
export type PeerBundle = {
- location: GeoLocation,
- knownPeers: {[string]: KnownPeer},
- attempts: Array,
+ location: GeoLocation,
+ knownPeers: {[string]: KnownPeer},
+ attempts: number,
};
export type KnownPeer = {
@@ -81,14 +84,12 @@ export type KnownPeer = {
disconnected: Array,
ingress: Array,
egress: Array,
+ name: string,
+ enode: string,
+ protocols: {[string]: Object},
active: boolean,
};
-export type UnknownPeer = {
- connected: Date,
- disconnected: Date,
-};
-
export type GeoLocation = {
country: string,
city: string,
diff --git a/dashboard/dashboard.go b/dashboard/dashboard.go
index d69a750f10..b63d5db46b 100644
--- a/dashboard/dashboard.go
+++ b/dashboard/dashboard.go
@@ -27,13 +27,14 @@ package dashboard
import (
"fmt"
+ "io"
"net"
"net/http"
"sync"
"sync/atomic"
- "time"
- "io"
+ "github.com/ethereum/go-ethereum/eth/downloader"
+ "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p"
@@ -67,6 +68,11 @@ type Dashboard struct {
quit chan chan error // Channel used for graceful exit
wg sync.WaitGroup // Wait group used to close the data collector threads
+
+ peerCh chan p2p.MeteredPeerEvent // Peer event channel.
+ subPeer event.Subscription // Peer event subscription.
+
+ syncMode downloader.SyncMode
}
// client represents active websocket connection with a remote browser.
@@ -77,8 +83,13 @@ type client struct {
}
// New creates a new dashboard instance with the given configuration.
-func New(config *Config, commit string, logdir string) *Dashboard {
- now := time.Now()
+func New(config *Config, syncMode downloader.SyncMode, commit string, logdir string) *Dashboard {
+ // There is a data race between the network layer and the dashboard, which
+ // can cause some lost peer events, therefore some peers might not appear
+ // on the dashboard.
+ // In order to solve this problem, the peer event subscription is registered
+ // here, before the network layer starts.
+ peerCh := make(chan p2p.MeteredPeerEvent, p2p.MeteredPeerLimit)
versionMeta := ""
if len(params.VersionMeta) > 0 {
versionMeta = fmt.Sprintf(" (%s)", params.VersionMeta)
@@ -89,26 +100,30 @@ func New(config *Config, commit string, logdir string) *Dashboard {
quit: make(chan chan error),
history: &Message{
General: &GeneralMessage{
- Commit: commit,
- Version: fmt.Sprintf("v%d.%d.%d%s", params.VersionMajor, params.VersionMinor, params.VersionPatch, versionMeta),
+ Commit: commit,
+ Version: fmt.Sprintf("v%d.%d.%d%s", params.VersionMajor, params.VersionMinor, params.VersionPatch, versionMeta),
+ SyncMode: syncMode.String(),
},
System: &SystemMessage{
- ActiveMemory: emptyChartEntries(now, sampleLimit),
- VirtualMemory: emptyChartEntries(now, sampleLimit),
- NetworkIngress: emptyChartEntries(now, sampleLimit),
- NetworkEgress: emptyChartEntries(now, sampleLimit),
- ProcessCPU: emptyChartEntries(now, sampleLimit),
- SystemCPU: emptyChartEntries(now, sampleLimit),
- DiskRead: emptyChartEntries(now, sampleLimit),
- DiskWrite: emptyChartEntries(now, sampleLimit),
+ ActiveMemory: emptyChartEntries(sampleLimit),
+ VirtualMemory: emptyChartEntries(sampleLimit),
+ NetworkIngress: emptyChartEntries(sampleLimit),
+ NetworkEgress: emptyChartEntries(sampleLimit),
+ ProcessCPU: emptyChartEntries(sampleLimit),
+ SystemCPU: emptyChartEntries(sampleLimit),
+ DiskRead: emptyChartEntries(sampleLimit),
+ DiskWrite: emptyChartEntries(sampleLimit),
},
},
- logdir: logdir,
+ logdir: logdir,
+ peerCh: peerCh,
+ subPeer: p2p.SubscribeMeteredPeerEvent(peerCh),
+ syncMode: syncMode,
}
}
// emptyChartEntries returns a ChartEntry array containing limit number of empty samples.
-func emptyChartEntries(t time.Time, limit int) ChartEntries {
+func emptyChartEntries(limit int) ChartEntries {
ce := make(ChartEntries, limit)
for i := 0; i < limit; i++ {
ce[i] = new(ChartEntry)
diff --git a/dashboard/message.go b/dashboard/message.go
index 797b210fa8..5ee8ad0ed6 100644
--- a/dashboard/message.go
+++ b/dashboard/message.go
@@ -37,8 +37,9 @@ type ChartEntry struct {
}
type GeneralMessage struct {
- Version string `json:"version,omitempty"`
- Commit string `json:"commit,omitempty"`
+ Version string `json:"version,omitempty"`
+ Commit string `json:"commit,omitempty"`
+ SyncMode string `json:"syncMode,omitempty"`
}
type HomeMessage struct {
diff --git a/dashboard/peers.go b/dashboard/peers.go
index e591bda779..ee40ae1d93 100644
--- a/dashboard/peers.go
+++ b/dashboard/peers.go
@@ -18,6 +18,7 @@ package dashboard
import (
"container/list"
+ "reflect"
"strings"
"time"
@@ -28,9 +29,7 @@ import (
)
const (
- eventBufferLimit = 128 // Maximum number of buffered peer events.
- knownPeerLimit = 100 // Maximum number of stored peers, which successfully made the handshake.
- attemptLimit = 200 // Maximum number of stored peers, which failed to make the handshake.
+ knownPeerLimit = 100 // Maximum number of stored peers, which successfully made the handshake.
// eventLimit is the maximum number of the dashboard's custom peer events,
// that are collected between two metering period and sent to the clients
@@ -83,14 +82,6 @@ type peerContainer struct {
// inactivePeers contains the peers with closed connection in chronological order.
inactivePeers *list.List
- // attemptOrder is the super array containing the IP addresses, from which
- // the peers attempted to connect then failed before/during the handshake.
- // Its values are appended in chronological order, which means that the
- // oldest attempt is at the beginning of the array. When the first element
- // is removed, the first element of the related bundle's attempt array is
- // removed too, ensuring that always the latest attempts are stored.
- attemptOrder []string
-
// geodb is the geoip database used to retrieve the peers' geographical location.
geodb *geoDB
}
@@ -100,7 +91,6 @@ func newPeerContainer(geodb *geoDB) *peerContainer {
return &peerContainer{
Bundles: make(map[string]*peerBundle),
inactivePeers: list.New(),
- attemptOrder: make([]string, 0, attemptLimit),
geodb: geodb,
}
}
@@ -110,48 +100,62 @@ func newPeerContainer(geodb *geoDB) *peerContainer {
// the IP address from the database and creates a corresponding peer event.
// Returns the bundle belonging to the given IP and the events occurring during
// the initialization.
-func (pc *peerContainer) bundle(ip string) (*peerBundle, []*peerEvent) {
+func (pc *peerContainer) bundle(addr string) (*peerBundle, []*peerEvent) {
var events []*peerEvent
- if _, ok := pc.Bundles[ip]; !ok {
- location := pc.geodb.location(ip)
+ if _, ok := pc.Bundles[addr]; !ok {
+ i := strings.IndexByte(addr, ':')
+ if i < 0 {
+ i = len(addr)
+ }
+ location := pc.geodb.location(addr[:i])
events = append(events, &peerEvent{
- IP: ip,
+ Addr: addr,
Location: location,
})
- pc.Bundles[ip] = &peerBundle{
+ pc.Bundles[addr] = &peerBundle{
Location: location,
KnownPeers: make(map[string]*knownPeer),
}
}
- return pc.Bundles[ip], events
+ return pc.Bundles[addr], events
}
// extendKnown handles the events of the successfully connected peers.
// Returns the events occurring during the extension.
func (pc *peerContainer) extendKnown(event *peerEvent) []*peerEvent {
- bundle, events := pc.bundle(event.IP)
- peer, peerEvents := bundle.knownPeer(event.IP, event.ID)
+ bundle, events := pc.bundle(event.Addr)
+ peer, peerEvents := bundle.knownPeer(event.Addr, event.Enode)
events = append(events, peerEvents...)
// Append the connect and the disconnect events to
// the corresponding arrays keeping the limit.
switch {
- case event.Connected != nil:
+ case event.Connected != nil: // Handshake succeeded
peer.Connected = append(peer.Connected, event.Connected)
if first := len(peer.Connected) - sampleLimit; first > 0 {
peer.Connected = peer.Connected[first:]
}
+ if event.peer == nil {
+ log.Warn("Peer handshake succeeded event without peer instance", "addr", event.Addr, "enode", event.Enode)
+ }
+ peer.peer = event.peer
+ info := event.peer.Info()
+ peer.Name = info.Name
+ peer.Protocols = info.Protocols
peer.Active = true
- events = append(events, &peerEvent{
- Activity: Active,
- IP: peer.ip,
- ID: peer.id,
- })
+ e := &peerEvent{
+ Activity: Active,
+ Name: info.Name,
+ Addr: peer.addr,
+ Enode: peer.enode,
+ Protocols: peer.Protocols,
+ }
+ events = append(events, e)
pc.activeCount++
if peer.listElement != nil {
_ = pc.inactivePeers.Remove(peer.listElement)
peer.listElement = nil
}
- case event.Disconnected != nil:
+ case event.Disconnected != nil: // Peer disconnected
peer.Disconnected = append(peer.Disconnected, event.Disconnected)
if first := len(peer.Disconnected) - sampleLimit; first > 0 {
peer.Disconnected = peer.Disconnected[first:]
@@ -159,8 +163,8 @@ func (pc *peerContainer) extendKnown(event *peerEvent) []*peerEvent {
peer.Active = false
events = append(events, &peerEvent{
Activity: Inactive,
- IP: peer.ip,
- ID: peer.id,
+ Addr: peer.addr,
+ Enode: peer.enode,
})
pc.activeCount--
if peer.listElement != nil {
@@ -169,12 +173,14 @@ func (pc *peerContainer) extendKnown(event *peerEvent) []*peerEvent {
}
// Insert the peer into the list.
peer.listElement = pc.inactivePeers.PushBack(peer)
+ default:
+ log.Warn("Unexpected known peer event", "event", *event)
}
for pc.inactivePeers.Len() > 0 && pc.activeCount+pc.inactivePeers.Len() > knownPeerLimit {
// While the count of the known peers is greater than the limit,
// remove the first element from the inactive peer list and from the map.
if removedPeer, ok := pc.inactivePeers.Remove(pc.inactivePeers.Front()).(*knownPeer); ok {
- events = append(events, pc.removeKnown(removedPeer.ip, removedPeer.id)...)
+ events = append(events, pc.removeKnown(removedPeer.addr, removedPeer.enode)...)
} else {
log.Warn("Failed to parse the removed peer")
}
@@ -185,25 +191,6 @@ func (pc *peerContainer) extendKnown(event *peerEvent) []*peerEvent {
return events
}
-// handleAttempt handles the events of the peers failing before/during the handshake.
-// Returns the events occurring during the extension.
-func (pc *peerContainer) handleAttempt(event *peerEvent) []*peerEvent {
- bundle, events := pc.bundle(event.IP)
- bundle.Attempts = append(bundle.Attempts, &peerAttempt{
- Connected: *event.Connected,
- Disconnected: *event.Disconnected,
- })
- pc.attemptOrder = append(pc.attemptOrder, event.IP)
- for len(pc.attemptOrder) > attemptLimit {
- // While the length of the connection attempt order array is greater
- // than the limit, remove the first element from the involved peer's
- // array and also from the super array.
- events = append(events, pc.removeAttempt(pc.attemptOrder[0])...)
- pc.attemptOrder = pc.attemptOrder[1:]
- }
- return events
-}
-
// peerBundle contains the peers belonging to a given IP address.
type peerBundle struct {
// Location contains the geographical location based on the bundle's IP address.
@@ -213,57 +200,35 @@ type peerBundle struct {
// maintainer data structure using the node ID as key.
KnownPeers map[string]*knownPeer `json:"knownPeers,omitempty"`
- // Attempts contains the failed connection attempts of the
- // peers belonging to a given IP address in chronological order.
- Attempts []*peerAttempt `json:"attempts,omitempty"`
+ // Attempts contains the count of the failed connection
+ // attempts of the peers belonging to a given IP address.
+ Attempts uint `json:"attempts,omitempty"`
}
// removeKnown removes the known peer belonging to the
// given IP address and node ID from the peer tree.
-func (pc *peerContainer) removeKnown(ip, id string) (events []*peerEvent) {
+func (pc *peerContainer) removeKnown(addr, enode string) (events []*peerEvent) {
// TODO (kurkomisi): Remove peers that don't have traffic samples anymore.
- if bundle, ok := pc.Bundles[ip]; ok {
- if _, ok := bundle.KnownPeers[id]; ok {
+ if bundle, ok := pc.Bundles[addr]; ok {
+ if _, ok := bundle.KnownPeers[enode]; ok {
events = append(events, &peerEvent{
Remove: RemoveKnown,
- IP: ip,
- ID: id,
+ Addr: addr,
+ Enode: enode,
})
- delete(bundle.KnownPeers, id)
+ delete(bundle.KnownPeers, enode)
} else {
- log.Warn("No peer to remove", "ip", ip, "id", id)
+ log.Warn("No peer to remove", "addr", addr, "enode", enode)
}
- if len(bundle.KnownPeers) < 1 && len(bundle.Attempts) < 1 {
+ if len(bundle.KnownPeers) < 1 && bundle.Attempts < 1 {
events = append(events, &peerEvent{
Remove: RemoveBundle,
- IP: ip,
+ Addr: addr,
})
- delete(pc.Bundles, ip)
+ delete(pc.Bundles, addr)
}
} else {
- log.Warn("No bundle to remove", "ip", ip)
- }
- return events
-}
-
-// removeAttempt removes the peer attempt belonging to the
-// given IP address and node ID from the peer tree.
-func (pc *peerContainer) removeAttempt(ip string) (events []*peerEvent) {
- if bundle, ok := pc.Bundles[ip]; ok {
- if len(bundle.Attempts) > 0 {
- events = append(events, &peerEvent{
- Remove: RemoveAttempt,
- IP: ip,
- })
- bundle.Attempts = bundle.Attempts[1:]
- }
- if len(bundle.Attempts) < 1 && len(bundle.KnownPeers) < 1 {
- events = append(events, &peerEvent{
- Remove: RemoveBundle,
- IP: ip,
- })
- delete(pc.Bundles, ip)
- }
+ log.Warn("No bundle to remove", "addr", addr)
}
return events
}
@@ -272,26 +237,25 @@ func (pc *peerContainer) removeAttempt(ip string) (events []*peerEvent) {
// to the given IP address and node ID wasn't metered so far. Returns the peer
// belonging to the given IP and ID as well as the events occurring during the
// initialization.
-func (bundle *peerBundle) knownPeer(ip, id string) (*knownPeer, []*peerEvent) {
+func (bundle *peerBundle) knownPeer(addr, enode string) (*knownPeer, []*peerEvent) {
var events []*peerEvent
- if _, ok := bundle.KnownPeers[id]; !ok {
- now := time.Now()
- ingress := emptyChartEntries(now, sampleLimit)
- egress := emptyChartEntries(now, sampleLimit)
+ if _, ok := bundle.KnownPeers[enode]; !ok {
+ ingress := emptyChartEntries(sampleLimit)
+ egress := emptyChartEntries(sampleLimit)
events = append(events, &peerEvent{
- IP: ip,
- ID: id,
+ Addr: addr,
+ Enode: enode,
Ingress: append([]*ChartEntry{}, ingress...),
Egress: append([]*ChartEntry{}, egress...),
})
- bundle.KnownPeers[id] = &knownPeer{
- ip: ip,
- id: id,
+ bundle.KnownPeers[enode] = &knownPeer{
+ addr: addr,
+ enode: enode,
Ingress: ingress,
Egress: egress,
}
}
- return bundle.KnownPeers[id], events
+ return bundle.KnownPeers[enode], events
}
// knownPeer contains the metered data of a particular peer.
@@ -312,31 +276,26 @@ type knownPeer struct {
Ingress ChartEntries `json:"ingress,omitempty"`
Egress ChartEntries `json:"egress,omitempty"`
+ Name string `json:"name,omitempty"` // Name of the node, including client type, version, OS, custom data
+ Enode string `json:"enode,omitempty"` // Node URL
+ Protocols map[string]interface{} `json:"protocols,omitempty"` // Sub-protocol specific metadata fields
+
Active bool `json:"active"` // Denotes if the peer is still connected.
listElement *list.Element // Pointer to the peer element in the list.
- ip, id string // The IP and the ID by which the peer can be accessed in the tree.
+ addr, enode string // The IP and the ID by which the peer can be accessed in the tree.
prevIngress float64
prevEgress float64
-}
-// peerAttempt contains a failed peer connection attempt's attributes.
-type peerAttempt struct {
- // Connected contains the timestamp of the connection attempt's moment.
- Connected time.Time `json:"connected"`
-
- // Disconnected contains the timestamp of the
- // moment when the connection attempt failed.
- Disconnected time.Time `json:"disconnected"`
+ peer *p2p.Peer // Connected remote node instance
}
type RemovedPeerType string
type ActivityType string
const (
- RemoveKnown RemovedPeerType = "known"
- RemoveAttempt RemovedPeerType = "attempt"
- RemoveBundle RemovedPeerType = "bundle"
+ RemoveKnown RemovedPeerType = "known"
+ RemoveBundle RemovedPeerType = "bundle"
Active ActivityType = "active"
Inactive ActivityType = "inactive"
@@ -344,15 +303,19 @@ const (
// peerEvent contains the attributes of a peer event.
type peerEvent struct {
- IP string `json:"ip,omitempty"` // IP address of the peer.
- ID string `json:"id,omitempty"` // Node ID of the peer.
- Remove RemovedPeerType `json:"remove,omitempty"` // Type of the peer that is to be removed.
- Location *geoLocation `json:"location,omitempty"` // Geographical location of the peer.
- Connected *time.Time `json:"connected,omitempty"` // Timestamp of the connection moment.
- Disconnected *time.Time `json:"disconnected,omitempty"` // Timestamp of the disonnection moment.
- Ingress ChartEntries `json:"ingress,omitempty"` // Ingress samples.
- Egress ChartEntries `json:"egress,omitempty"` // Egress samples.
- Activity ActivityType `json:"activity,omitempty"` // Connection status change.
+ Name string `json:"name,omitempty"` // Name of the node, including client type, version, OS, custom data
+ Addr string `json:"addr,omitempty"` // TCP address of the peer.
+ Enode string `json:"enode,omitempty"` // Node URL
+ Protocols map[string]interface{} `json:"protocols,omitempty"` // Sub-protocol specific metadata fields
+ Remove RemovedPeerType `json:"remove,omitempty"` // Type of the peer that is to be removed.
+ Location *geoLocation `json:"location,omitempty"` // Geographical location of the peer.
+ Connected *time.Time `json:"connected,omitempty"` // Timestamp of the connection moment.
+ Disconnected *time.Time `json:"disconnected,omitempty"` // Timestamp of the disonnection moment.
+ Ingress ChartEntries `json:"ingress,omitempty"` // Ingress samples.
+ Egress ChartEntries `json:"egress,omitempty"` // Egress samples.
+ Activity ActivityType `json:"activity,omitempty"` // Connection status change.
+
+ peer *p2p.Peer // Connected remote node instance.
}
// trafficMap is a container for the periodically collected peer traffic.
@@ -380,10 +343,6 @@ func (db *Dashboard) collectPeerData() {
}
defer db.geodb.close()
- peerCh := make(chan p2p.MeteredPeerEvent, eventBufferLimit) // Peer event channel.
- subPeer := p2p.SubscribeMeteredPeerEvent(peerCh) // Subscribe to peer events.
- defer subPeer.Unsubscribe() // Unsubscribe at the end.
-
ticker := time.NewTicker(db.config.Refresh)
defer ticker.Stop()
@@ -400,11 +359,11 @@ func (db *Dashboard) collectPeerData() {
// The function which can be passed to the registry.
return func(name string, i interface{}) {
if m, ok := i.(metrics.Meter); ok {
- // The name of the meter has the format: /
- if k := strings.Split(strings.TrimPrefix(name, prefix), "/"); len(k) == 2 {
- traffic.insert(k[0], k[1], float64(m.Count()))
+ enode := strings.TrimPrefix(name, prefix)
+ if addr := strings.Split(enode, "@"); len(addr) == 2 {
+ traffic.insert(addr[1], enode, float64(m.Count()))
} else {
- log.Warn("Invalid meter name", "name", name, "prefix", prefix)
+ log.Warn("Invalid enode", "enode", enode)
}
} else {
log.Warn("Invalid meter type", "name", name)
@@ -428,23 +387,32 @@ func (db *Dashboard) collectPeerData() {
ingress, egress := new(trafficMap), new(trafficMap)
*ingress, *egress = make(trafficMap), make(trafficMap)
+ defer db.subPeer.Unsubscribe()
for {
select {
- case event := <-peerCh:
+ case event := <-db.peerCh:
now := time.Now()
switch event.Type {
- case p2p.PeerConnected:
+ case p2p.PeerHandshakeFailed:
connected := now.Add(-event.Elapsed)
newPeerEvents = append(newPeerEvents, &peerEvent{
- IP: event.IP.String(),
- ID: event.ID.String(),
+ Addr: event.Addr,
+ Connected: &connected,
+ Disconnected: &now,
+ })
+ case p2p.PeerHandshakeSucceeded:
+ connected := now.Add(-event.Elapsed)
+ newPeerEvents = append(newPeerEvents, &peerEvent{
+ Addr: event.Addr,
+ Enode: event.Enode,
+ peer: event.Peer,
Connected: &connected,
})
case p2p.PeerDisconnected:
- ip, id := event.IP.String(), event.ID.String()
+ addr, enode := event.Addr, event.Enode
newPeerEvents = append(newPeerEvents, &peerEvent{
- IP: ip,
- ID: id,
+ Addr: addr,
+ Enode: enode,
Disconnected: &now,
})
// The disconnect event comes with the last metered traffic count,
@@ -453,15 +421,8 @@ func (db *Dashboard) collectPeerData() {
// period the same peer disconnects multiple times, and appending
// all the samples to the traffic arrays would shift the metering,
// so only the last metering is stored, overwriting the previous one.
- ingress.insert(ip, id, float64(event.Ingress))
- egress.insert(ip, id, float64(event.Egress))
- case p2p.PeerHandshakeFailed:
- connected := now.Add(-event.Elapsed)
- newPeerEvents = append(newPeerEvents, &peerEvent{
- IP: event.IP.String(),
- Connected: &connected,
- Disconnected: &now,
- })
+ ingress.insert(addr, enode, float64(event.Ingress))
+ egress.insert(addr, enode, float64(event.Egress))
default:
log.Error("Unknown metered peer event type", "type", event.Type)
}
@@ -475,7 +436,7 @@ func (db *Dashboard) collectPeerData() {
var diff []*peerEvent
for i := 0; i < len(newPeerEvents); i++ {
- if newPeerEvents[i].IP == "" {
+ if newPeerEvents[i].Addr == "" {
log.Warn("Peer event without IP", "event", *newPeerEvents[i])
continue
}
@@ -487,18 +448,20 @@ func (db *Dashboard) collectPeerData() {
//
// The extension can produce additional peer events, such
// as remove, location and initial samples events.
- if newPeerEvents[i].ID == "" {
- diff = append(diff, peers.handleAttempt(newPeerEvents[i])...)
+ if newPeerEvents[i].Enode == "" {
+ bundle, events := peers.bundle(newPeerEvents[i].Addr)
+ bundle.Attempts++
+ diff = append(diff, events...)
continue
}
diff = append(diff, peers.extendKnown(newPeerEvents[i])...)
}
// Update the peer tree using the traffic maps.
- for ip, bundle := range peers.Bundles {
- for id, peer := range bundle.KnownPeers {
+ for addr, bundle := range peers.Bundles {
+ for enode, peer := range bundle.KnownPeers {
// Value is 0 if the traffic map doesn't have the
// entry corresponding to the given IP and ID.
- curIngress, curEgress := (*ingress)[ip][id], (*egress)[ip][id]
+ curIngress, curEgress := (*ingress)[addr][enode], (*egress)[addr][enode]
deltaIngress, deltaEgress := curIngress, curEgress
if deltaIngress >= peer.prevIngress {
deltaIngress -= peer.prevIngress
@@ -523,11 +486,22 @@ func (db *Dashboard) collectPeerData() {
}
// Creating the traffic sample events.
diff = append(diff, &peerEvent{
- IP: ip,
- ID: id,
+ Addr: addr,
+ Enode: enode,
Ingress: ChartEntries{i},
Egress: ChartEntries{e},
})
+ if peer.peer != nil {
+ info := peer.peer.Info()
+ if !reflect.DeepEqual(peer.Protocols, info.Protocols) {
+ peer.Protocols = info.Protocols
+ diff = append(diff, &peerEvent{
+ Addr: addr,
+ Enode: enode,
+ Protocols: peer.Protocols,
+ })
+ }
+ }
}
}
db.peerLock.Unlock()
@@ -541,7 +515,7 @@ func (db *Dashboard) collectPeerData() {
// prepare them for the next metering.
*ingress, *egress = make(trafficMap), make(trafficMap)
newPeerEvents = newPeerEvents[:0]
- case err := <-subPeer.Err():
+ case err := <-db.subPeer.Err():
log.Warn("Peer subscription error", "err", err)
return
case errc := <-db.quit:
diff --git a/p2p/dial.go b/p2p/dial.go
index 075a0f9368..7b44b6430b 100644
--- a/p2p/dial.go
+++ b/p2p/dial.go
@@ -350,7 +350,7 @@ func (t *dialTask) dial(srv *Server, dest *enode.Node) error {
if err != nil {
return &dialError{err}
}
- mfd := newMeteredConn(fd, false, dest.IP())
+ mfd := newMeteredConn(fd, false, &net.TCPAddr{IP: dest.IP(), Port: dest.TCP()})
return srv.SetupConn(mfd, t.flags, dest)
}
diff --git a/p2p/metrics.go b/p2p/metrics.go
index 2b1ad6df41..c2f8bc1499 100644
--- a/p2p/metrics.go
+++ b/p2p/metrics.go
@@ -19,14 +19,11 @@
package p2p
import (
- "fmt"
"net"
"sync"
"sync/atomic"
"time"
- "github.com/ethereum/go-ethereum/p2p/enode"
-
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
@@ -58,24 +55,25 @@ var (
type MeteredPeerEventType int
const (
- // PeerConnected is the type of event emitted when a peer successfully
- // made the handshake.
- PeerConnected MeteredPeerEventType = iota
+ // PeerHandshakeSucceeded is the type of event
+ // emitted when a peer successfully makes the handshake.
+ PeerHandshakeSucceeded MeteredPeerEventType = iota
+
+ // PeerHandshakeFailed is the type of event emitted when a peer fails to
+ // make the encryption handshake or disconnects before it.
+ PeerHandshakeFailed
// PeerDisconnected is the type of event emitted when a peer disconnects.
PeerDisconnected
-
- // PeerHandshakeFailed is the type of event emitted when a peer fails to
- // make the handshake or disconnects before the handshake.
- PeerHandshakeFailed
)
// MeteredPeerEvent is an event emitted when peers connect or disconnect.
type MeteredPeerEvent struct {
Type MeteredPeerEventType // Type of peer event
- IP net.IP // IP address of the peer
- ID enode.ID // NodeID of the peer
+ Addr string // TCP address of the peer
Elapsed time.Duration // Time elapsed between the connection and the handshake/disconnection
+ Enode string // Node URL
+ Peer *Peer // Connected remote node instance
Ingress uint64 // Ingress count at the moment of the event
Egress uint64 // Egress count at the moment of the event
}
@@ -91,9 +89,9 @@ func SubscribeMeteredPeerEvent(ch chan<- MeteredPeerEvent) event.Subscription {
type meteredConn struct {
net.Conn // Network connection to wrap with metering
- connected time.Time // Connection time of the peer
- ip net.IP // IP address of the peer
- id enode.ID // NodeID of the peer
+ connected time.Time // Connection time of the peer
+ addr *net.TCPAddr // TCP address of the peer
+ enode string // Node URL of the peer
// trafficMetered denotes if the peer is registered in the traffic registries.
// Its value is true if the metered peer count doesn't reach the limit in the
@@ -109,13 +107,13 @@ type meteredConn struct {
// connection meter and also increases the metered peer count. If the metrics
// system is disabled or the IP address is unspecified, this function returns
// the original object.
-func newMeteredConn(conn net.Conn, ingress bool, ip net.IP) net.Conn {
+func newMeteredConn(conn net.Conn, ingress bool, addr *net.TCPAddr) net.Conn {
// Short circuit if metrics are disabled
if !metrics.Enabled {
return conn
}
- if ip.IsUnspecified() {
- log.Warn("Peer IP is unspecified")
+ if addr == nil || addr.IP.IsUnspecified() {
+ log.Warn("Peer address is unspecified")
return conn
}
// Bump the connection counters and wrap the connection
@@ -126,7 +124,7 @@ func newMeteredConn(conn net.Conn, ingress bool, ip net.IP) net.Conn {
}
return &meteredConn{
Conn: conn,
- ip: ip,
+ addr: addr,
connected: time.Now(),
}
}
@@ -157,30 +155,28 @@ func (c *meteredConn) Write(b []byte) (n int, err error) {
return n, err
}
-// handshakeDone is called when a peer handshake is done. Registers the peer to
-// the ingress and the egress traffic registries using the peer's IP and node ID,
-// also emits connect event.
-func (c *meteredConn) handshakeDone(id enode.ID) {
- // TODO (kurkomisi): use the node URL instead of the pure node ID. (the String() method of *Node)
+// handshakeDone is called after the connection passes the handshake.
+func (c *meteredConn) handshakeDone(peer *Peer) {
+ enode := peer.Node().String()
if atomic.AddInt32(&meteredPeerCount, 1) >= MeteredPeerLimit {
// Don't register the peer in the traffic registries.
atomic.AddInt32(&meteredPeerCount, -1)
c.lock.Lock()
- c.id, c.trafficMetered = id, false
+ c.enode, c.trafficMetered = enode, false
c.lock.Unlock()
log.Warn("Metered peer count reached the limit")
} else {
- key := fmt.Sprintf("%s/%s", c.ip, id.String())
c.lock.Lock()
- c.id, c.trafficMetered = id, true
- c.ingressMeter = metrics.NewRegisteredMeter(key, PeerIngressRegistry)
- c.egressMeter = metrics.NewRegisteredMeter(key, PeerEgressRegistry)
+ c.enode, c.trafficMetered = enode, true
+ c.ingressMeter = metrics.NewRegisteredMeter(enode, PeerIngressRegistry)
+ c.egressMeter = metrics.NewRegisteredMeter(enode, PeerEgressRegistry)
c.lock.Unlock()
}
meteredPeerFeed.Send(MeteredPeerEvent{
- Type: PeerConnected,
- IP: c.ip,
- ID: id,
+ Type: PeerHandshakeSucceeded,
+ Addr: c.addr.String(),
+ Enode: enode,
+ Peer: peer,
Elapsed: time.Since(c.connected),
})
}
@@ -190,24 +186,24 @@ func (c *meteredConn) handshakeDone(id enode.ID) {
func (c *meteredConn) Close() error {
err := c.Conn.Close()
c.lock.RLock()
- if c.id == (enode.ID{}) {
- // If the peer disconnects before the handshake.
+ if c.enode == "" {
+ // If the peer disconnects before/during the handshake.
c.lock.RUnlock()
meteredPeerFeed.Send(MeteredPeerEvent{
Type: PeerHandshakeFailed,
- IP: c.ip,
+ Addr: c.addr.String(),
Elapsed: time.Since(c.connected),
})
return err
}
- id := c.id
+ enode := c.enode
if !c.trafficMetered {
// If the peer isn't registered in the traffic registries.
c.lock.RUnlock()
meteredPeerFeed.Send(MeteredPeerEvent{
- Type: PeerDisconnected,
- IP: c.ip,
- ID: id,
+ Type: PeerDisconnected,
+ Addr: c.addr.String(),
+ Enode: enode,
})
return err
}
@@ -218,14 +214,13 @@ func (c *meteredConn) Close() error {
atomic.AddInt32(&meteredPeerCount, -1)
// Unregister the peer from the traffic registries
- key := fmt.Sprintf("%s/%s", c.ip, id)
- PeerIngressRegistry.Unregister(key)
- PeerEgressRegistry.Unregister(key)
+ PeerIngressRegistry.Unregister(enode)
+ PeerEgressRegistry.Unregister(enode)
meteredPeerFeed.Send(MeteredPeerEvent{
Type: PeerDisconnected,
- IP: c.ip,
- ID: id,
+ Addr: c.addr.String(),
+ Enode: enode,
Ingress: ingress,
Egress: egress,
})
diff --git a/p2p/server.go b/p2p/server.go
index 566f01ffc5..ee3ce08c06 100644
--- a/p2p/server.go
+++ b/p2p/server.go
@@ -736,6 +736,9 @@ running:
if p.Inbound() {
inboundCount++
}
+ if conn, ok := c.fd.(*meteredConn); ok {
+ conn.handshakeDone(p)
+ }
}
// The dialer logic relies on the assumption that
// dial tasks complete after the peer has been added or
@@ -863,11 +866,11 @@ func (srv *Server) listenLoop() {
}
}
- var ip net.IP
+ var addr *net.TCPAddr
if tcp, ok := fd.RemoteAddr().(*net.TCPAddr); ok {
- ip = tcp.IP
+ addr = tcp
}
- fd = newMeteredConn(fd, true, ip)
+ fd = newMeteredConn(fd, true, addr)
srv.log.Trace("Accepted connection", "addr", fd.RemoteAddr())
go func() {
srv.SetupConn(fd, inboundConn, nil)
@@ -920,9 +923,6 @@ func (srv *Server) setupConn(c *conn, flags connFlag, dialDest *enode.Node) erro
} else {
c.node = nodeFromConn(remotePubkey, c.fd)
}
- if conn, ok := c.fd.(*meteredConn); ok {
- conn.handshakeDone(c.node.ID())
- }
clog := srv.log.New("id", c.node.ID(), "addr", c.fd.RemoteAddr(), "conn", c.flags)
err = srv.checkpoint(c, srv.posthandshake)
if err != nil {