dashboard: send current block to the dashboard client (#19762)
This adds all dashboard changes from the last couple months. We're about to remove the dashboard, but decided that we should get all the recent work in first in case anyone wants to pick up this project later on. * cmd, dashboard, eth, p2p: send peer info to the dashboard * dashboard: update npm packages, improve UI, rebase * dashboard, p2p: remove println, change doc * cmd, dashboard, eth, p2p: cleanup after review * dashboard: send current block to the dashboard client
This commit is contained in:
parent
6f1a600f6c
commit
4ea9b62b5c
@ -156,9 +156,6 @@ func makeFullNode(ctx *cli.Context) *node.Node {
|
||||
}
|
||||
utils.RegisterEthService(stack, &cfg.Eth)
|
||||
|
||||
if ctx.GlobalBool(utils.DashboardEnabledFlag.Name) {
|
||||
utils.RegisterDashboardService(stack, &cfg.Dashboard, gitCommit)
|
||||
}
|
||||
// Whisper must be explicitly enabled by specifying at least 1 whisper flag or in dev mode
|
||||
shhEnabled := enableWhisper(ctx)
|
||||
shhAutoEnabled := !ctx.GlobalIsSet(utils.WhisperEnabledFlag.Name) && ctx.GlobalIsSet(utils.DeveloperFlag.Name)
|
||||
@ -182,6 +179,12 @@ func makeFullNode(ctx *cli.Context) *node.Node {
|
||||
if cfg.Ethstats.URL != "" {
|
||||
utils.RegisterEthStatsService(stack, cfg.Ethstats.URL)
|
||||
}
|
||||
|
||||
// Add dashboard daemon if requested. This should be the last registered service
|
||||
// in order to be able to collect information about the other services.
|
||||
if ctx.GlobalBool(utils.DashboardEnabledFlag.Name) {
|
||||
utils.RegisterDashboardService(stack, &cfg.Dashboard, gitCommit)
|
||||
}
|
||||
return stack
|
||||
}
|
||||
|
||||
|
@ -1561,9 +1561,18 @@ func RegisterEthService(stack *node.Node, cfg *eth.Config) {
|
||||
|
||||
// RegisterDashboardService adds a dashboard to the stack.
|
||||
func RegisterDashboardService(stack *node.Node, cfg *dashboard.Config, commit string) {
|
||||
stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
|
||||
return dashboard.New(cfg, commit, ctx.ResolvePath("logs")), nil
|
||||
err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
|
||||
var (
|
||||
ethServ *eth.Ethereum
|
||||
lesServ *les.LightEthereum
|
||||
)
|
||||
_ = ctx.Service(ðServ)
|
||||
_ = ctx.Service(&lesServ)
|
||||
return dashboard.New(cfg, ethServ, lesServ, commit, ctx.ResolvePath("logs")), nil
|
||||
})
|
||||
if err != nil {
|
||||
Fatalf("Failed to register the dashboard service: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterShhService configures Whisper and adds it to the given node.
|
||||
|
19816
dashboard/assets.go
19816
dashboard/assets.go
File diff suppressed because one or more lines are too long
53
dashboard/assets/components/Chain.jsx
Normal file
53
dashboard/assets/components/Chain.jsx
Normal file
@ -0,0 +1,53 @@
|
||||
// @flow
|
||||
|
||||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, {Component} from 'react';
|
||||
import type {Chain as ChainType} from '../types/content';
|
||||
|
||||
export const inserter = () => (update: ChainType, prev: ChainType) => {
|
||||
if (!update.currentBlock) {
|
||||
return;
|
||||
}
|
||||
if (!prev.currentBlock) {
|
||||
prev.currentBlock = {};
|
||||
}
|
||||
prev.currentBlock.number = update.currentBlock.number;
|
||||
prev.currentBlock.timestamp = update.currentBlock.timestamp;
|
||||
return prev;
|
||||
};
|
||||
|
||||
// styles contains the constant styles of the component.
|
||||
const styles = {};
|
||||
|
||||
// themeStyles returns the styles generated from the theme for the component.
|
||||
const themeStyles = theme => ({});
|
||||
|
||||
export type Props = {
|
||||
content: Content,
|
||||
};
|
||||
|
||||
type State = {};
|
||||
|
||||
// Logs renders the log page.
|
||||
class Chain extends Component<Props, State> {
|
||||
render() {
|
||||
return <></>;
|
||||
}
|
||||
}
|
||||
|
||||
export default Chain;
|
@ -25,6 +25,7 @@ import Header from 'Header';
|
||||
import Body from 'Body';
|
||||
import {inserter as logInserter, SAME} from 'Logs';
|
||||
import {inserter as peerInserter} from 'Network';
|
||||
import {inserter as chainInserter} from 'Chain';
|
||||
import {MENU} from '../common';
|
||||
import type {Content} from '../types/content';
|
||||
|
||||
@ -83,17 +84,24 @@ const appender = <T>(limit: number, mapper = replacer) => (update: Array<T>, pre
|
||||
// the execution of unnecessary operations (e.g. copy of the log array).
|
||||
const defaultContent: () => Content = () => ({
|
||||
general: {
|
||||
version: null,
|
||||
commit: null,
|
||||
version: null,
|
||||
genesis: '',
|
||||
},
|
||||
home: {},
|
||||
chain: {},
|
||||
chain: {
|
||||
currentBlock: {
|
||||
number: 0,
|
||||
timestamp: 0,
|
||||
},
|
||||
},
|
||||
txpool: {},
|
||||
network: {
|
||||
peers: {
|
||||
bundles: {},
|
||||
},
|
||||
diff: [],
|
||||
activePeerCount: 0,
|
||||
},
|
||||
system: {
|
||||
activeMemory: [],
|
||||
@ -121,9 +129,10 @@ const updaters = {
|
||||
general: {
|
||||
version: replacer,
|
||||
commit: replacer,
|
||||
genesis: replacer,
|
||||
},
|
||||
home: null,
|
||||
chain: null,
|
||||
chain: chainInserter(),
|
||||
txpool: null,
|
||||
network: peerInserter(200),
|
||||
system: {
|
||||
@ -241,6 +250,7 @@ class Dashboard extends Component<Props, State> {
|
||||
<div className={this.props.classes.dashboard} style={styles.dashboard}>
|
||||
<Header
|
||||
switchSideBar={this.switchSideBar}
|
||||
content={this.state.content}
|
||||
/>
|
||||
<Body
|
||||
opened={this.state.sideBar}
|
||||
|
@ -32,6 +32,9 @@ import ChartRow from 'ChartRow';
|
||||
import CustomTooltip, {bytePlotter, bytePerSecPlotter, percentPlotter, multiplier} from 'CustomTooltip';
|
||||
import {chartStrokeWidth, styles as commonStyles} from '../common';
|
||||
import type {General, System} from '../types/content';
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {faNetworkWired} from "@fortawesome/free-solid-svg-icons";
|
||||
import Toolbar from "@material-ui/core/Toolbar";
|
||||
|
||||
const FOOTER_SYNC_ID = 'footerSyncId';
|
||||
|
||||
@ -154,6 +157,23 @@ class Footer extends Component<Props, State> {
|
||||
|
||||
render() {
|
||||
const {general, system} = this.props;
|
||||
let network = '';
|
||||
switch (general.genesis) {
|
||||
case '0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3':
|
||||
network = 'main';
|
||||
break;
|
||||
case '0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d':
|
||||
network = 'ropsten';
|
||||
break;
|
||||
case '0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177':
|
||||
network = 'rinkeby';
|
||||
break;
|
||||
case '0xbf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a':
|
||||
network = 'görli';
|
||||
break;
|
||||
default:
|
||||
network = `unknown (${general.genesis.substring(0, 8)})`;
|
||||
}
|
||||
|
||||
return (
|
||||
<Grid container className={this.props.classes.footer} direction='row' alignItems='center' style={styles.footer}>
|
||||
@ -202,6 +222,9 @@ class Footer extends Component<Props, State> {
|
||||
</a>
|
||||
</Typography>
|
||||
)}
|
||||
<Typography style={styles.headerText}>
|
||||
<span style={commonStyles.light}>Network</span> {network}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
|
@ -23,16 +23,25 @@ import AppBar from '@material-ui/core/AppBar';
|
||||
import Toolbar from '@material-ui/core/Toolbar';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||
import {faBars} from '@fortawesome/free-solid-svg-icons';
|
||||
import {faBars, faSortAmountUp, faClock, faUsers, faSync} from '@fortawesome/free-solid-svg-icons';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import type {Content} from '../types/content';
|
||||
|
||||
|
||||
const magnitude = [31536000, 604800, 86400, 3600, 60, 1];
|
||||
const label = ['y', 'w', 'd', 'h', 'm', 's'];
|
||||
|
||||
// styles contains the constant styles of the component.
|
||||
const styles = {
|
||||
header: {
|
||||
height: '8%',
|
||||
},
|
||||
headerText: {
|
||||
marginRight: 15,
|
||||
},
|
||||
toolbar: {
|
||||
height: '100%',
|
||||
minHeight: 'unset',
|
||||
},
|
||||
};
|
||||
|
||||
@ -50,16 +59,52 @@ const themeStyles = (theme: Object) => ({
|
||||
title: {
|
||||
paddingLeft: theme.spacing.unit,
|
||||
fontSize: 3 * theme.spacing.unit,
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
|
||||
export type Props = {
|
||||
classes: Object, // injected by withStyles()
|
||||
switchSideBar: () => void,
|
||||
content: Content,
|
||||
networkID: number,
|
||||
};
|
||||
|
||||
type State = {
|
||||
since: string,
|
||||
}
|
||||
// Header renders the header of the dashboard.
|
||||
class Header extends Component<Props> {
|
||||
class Header extends Component<Props, State> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {since: ''};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.interval = setInterval(() => this.setState(() => {
|
||||
// time (seconds) since last block.
|
||||
let timeDiff = Math.floor((Date.now() - this.props.content.chain.currentBlock.timestamp * 1000) / 1000);
|
||||
let since = '';
|
||||
let i = 0;
|
||||
for (; i < magnitude.length && timeDiff < magnitude[i]; i++);
|
||||
for (let j = 2; i < magnitude.length && j > 0; j--, i++) {
|
||||
const t = Math.floor(timeDiff / magnitude[i]);
|
||||
if (t > 0) {
|
||||
since += `${t}${label[i]} `;
|
||||
timeDiff %= magnitude[i];
|
||||
}
|
||||
}
|
||||
if (since === '') {
|
||||
since = 'now';
|
||||
}
|
||||
this.setState({since: since});
|
||||
}), 1000);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearInterval(this.interval);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {classes} = this.props;
|
||||
|
||||
@ -72,6 +117,15 @@ class Header extends Component<Props> {
|
||||
<Typography type='title' color='inherit' noWrap className={classes.title}>
|
||||
Go Ethereum Dashboard
|
||||
</Typography>
|
||||
<Typography style={styles.headerText}>
|
||||
<FontAwesomeIcon icon={faSortAmountUp} /> {this.props.content.chain.currentBlock.number}
|
||||
</Typography>
|
||||
<Typography style={styles.headerText}>
|
||||
<FontAwesomeIcon icon={faClock} /> {this.state.since}
|
||||
</Typography>
|
||||
<Typography style={styles.headerText}>
|
||||
<FontAwesomeIcon icon={faUsers} /> {this.props.content.network.activePeerCount}
|
||||
</Typography>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
);
|
||||
|
@ -20,6 +20,7 @@ import React, {Component} from 'react';
|
||||
|
||||
import withStyles from '@material-ui/core/styles/withStyles';
|
||||
|
||||
import Chain from 'Chain';
|
||||
import Network from 'Network';
|
||||
import Logs from 'Logs';
|
||||
import Footer from 'Footer';
|
||||
@ -95,7 +96,9 @@ class Main extends Component<Props, State> {
|
||||
children = <div>Work in progress.</div>;
|
||||
break;
|
||||
case MENU.get('chain').id:
|
||||
children = <div>Work in progress.</div>;
|
||||
children = <Chain
|
||||
content={this.props.content.chain}
|
||||
/>;
|
||||
break;
|
||||
case MENU.get('txpool').id:
|
||||
children = <div>Work in progress.</div>;
|
||||
|
@ -18,6 +18,7 @@
|
||||
|
||||
import React, {Component} from 'react';
|
||||
|
||||
import withStyles from '@material-ui/core/styles/withStyles';
|
||||
import Table from '@material-ui/core/Table';
|
||||
import TableHead from '@material-ui/core/TableHead';
|
||||
import TableBody from '@material-ui/core/TableBody';
|
||||
@ -27,17 +28,23 @@ import Grid from '@material-ui/core/Grid/Grid';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import {AreaChart, Area, Tooltip, YAxis} from 'recharts';
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
|
||||
import {faCircle as fasCircle} from '@fortawesome/free-solid-svg-icons';
|
||||
import {faCircle as farCircle} from '@fortawesome/free-regular-svg-icons';
|
||||
import {faCircle as fasCircle} from '@fortawesome/free-solid-svg-icons'; // More icons at fontawesome.com/icons
|
||||
import {faCircle as farCircle, faClipboard as farClipboard} from '@fortawesome/free-regular-svg-icons';
|
||||
import convert from 'color-convert';
|
||||
import {Scrollbars} from 'react-custom-scrollbars';
|
||||
|
||||
import CustomTooltip, {bytePlotter, multiplier} from 'CustomTooltip';
|
||||
import type {Network as NetworkType, PeerEvent} from '../types/content';
|
||||
import {styles as commonStyles, chartStrokeWidth, hues, hueScale} from '../common';
|
||||
import {chartStrokeWidth, hues, hueScale} from '../common';
|
||||
|
||||
// Peer chart dimensions.
|
||||
const trafficChartHeight = 18;
|
||||
const trafficChartWidth = 400;
|
||||
const trafficChartHeight = 15;
|
||||
const trafficChartWidth = 200;
|
||||
|
||||
// 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 +127,58 @@ 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';
|
||||
}
|
||||
if (parts.length < 2) {
|
||||
console.error('Incorrect node name', name);
|
||||
return parts[0];
|
||||
}
|
||||
const versionRE = RegExp(/^v?\d+\.\d+\.\d+.*/);
|
||||
// Drop optional custom identifier.
|
||||
if (!versionRE.test(parts[1])) {
|
||||
if (parts.length < 3 || !versionRE.test(parts[2])) {
|
||||
console.error('Incorrect node name', name);
|
||||
return parts[0];
|
||||
}
|
||||
parts[1] = parts[2];
|
||||
}
|
||||
// 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 : ''}`;
|
||||
};
|
||||
|
||||
// protocol returns a shortened version of the eth protocol values.
|
||||
const protocol = (p: Object) => {
|
||||
if (!p) {
|
||||
return '';
|
||||
}
|
||||
if (typeof p === 'string') {
|
||||
return p;
|
||||
}
|
||||
if (!(p instanceof Object)) {
|
||||
console.error('Wrong protocol type', p, typeof p);
|
||||
return '';
|
||||
}
|
||||
if (!p.hasOwnProperty('version') || !p.hasOwnProperty('difficulty') || !p.hasOwnProperty('head')) {
|
||||
console.error('Missing protocol attributes', p);
|
||||
return '';
|
||||
}
|
||||
return `h=${p.head.substring(0, 10)} v=${p.version} td=${p.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 +193,104 @@ 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 = protocol(peer.protocols.eth);
|
||||
peer.les = protocol(peer.protocols.les);
|
||||
});
|
||||
}
|
||||
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: '',
|
||||
les: '',
|
||||
};
|
||||
}
|
||||
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 = protocol(peer.protocols.eth);
|
||||
peer.les = protocol(peer.protocols.les);
|
||||
}
|
||||
if (!peer.maxIngress) {
|
||||
setIngressChartAttributes(peer);
|
||||
}
|
||||
@ -300,11 +379,29 @@ export const inserter = (sampleLimit: number) => (update: NetworkType, prev: Net
|
||||
}
|
||||
});
|
||||
}
|
||||
prev.activePeerCount = 0;
|
||||
Object.entries(prev.peers.bundles).forEach(([addr, bundle]) => {
|
||||
if (!bundle.knownPeers || Object.keys(bundle.knownPeers).length < 1) {
|
||||
return;
|
||||
}
|
||||
Object.entries(bundle.knownPeers).forEach(([enode, peer]) => {
|
||||
if (peer.active === true) {
|
||||
prev.activePeerCount++;
|
||||
}
|
||||
});
|
||||
});
|
||||
return prev;
|
||||
};
|
||||
|
||||
// styles contains the constant styles of the component.
|
||||
const styles = {
|
||||
title: {
|
||||
marginLeft: 5,
|
||||
},
|
||||
table: {
|
||||
borderCollapse: 'unset',
|
||||
padding: 5,
|
||||
},
|
||||
tableHead: {
|
||||
height: 'auto',
|
||||
},
|
||||
@ -317,10 +414,36 @@ const styles = {
|
||||
paddingBottom: 0,
|
||||
paddingLeft: 5,
|
||||
border: 'none',
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 10,
|
||||
},
|
||||
content: {
|
||||
height: '800px',
|
||||
},
|
||||
};
|
||||
|
||||
// themeStyles returns the styles generated from the theme for the component.
|
||||
const themeStyles = theme => ({
|
||||
title: {
|
||||
color: theme.palette.common.white,
|
||||
},
|
||||
table: {
|
||||
background: theme.palette.grey[900],
|
||||
},
|
||||
});
|
||||
|
||||
// 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 = {
|
||||
classes: Object, // injected by withStyles()
|
||||
container: Object,
|
||||
content: NetworkType,
|
||||
shouldUpdate: Object,
|
||||
@ -351,48 +474,113 @@ class Network extends Component<Props, State> {
|
||||
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) => {
|
||||
lesList = () => {
|
||||
const list = [];
|
||||
Object.values(this.props.content.peers.bundles).forEach((bundle) => {
|
||||
if (!bundle.knownPeers || Object.keys(bundle.knownPeers).length < 1) {
|
||||
return;
|
||||
}
|
||||
Object.entries(bundle.knownPeers).forEach(([enode, peer]) => {
|
||||
if (peer.les === '' || peer.eth !== '') {
|
||||
return;
|
||||
}
|
||||
list.push({enode, name: peer.name, location: bundle.location, protocols: peer.protocols});
|
||||
});
|
||||
});
|
||||
return list;
|
||||
};
|
||||
|
||||
ethList = () => {
|
||||
const list = [];
|
||||
Object.values(this.props.content.peers.bundles).forEach((bundle) => {
|
||||
if (!bundle.knownPeers || Object.keys(bundle.knownPeers).length < 1) {
|
||||
return;
|
||||
}
|
||||
Object.entries(bundle.knownPeers).forEach(([enode, peer]) => {
|
||||
if (peer.eth === '' && peer.les !== '') {
|
||||
return;
|
||||
}
|
||||
list.push({enode, name: peer.name, location: bundle.location, protocols: peer.protocols});
|
||||
});
|
||||
});
|
||||
return list;
|
||||
};
|
||||
|
||||
attemptList = () => {
|
||||
const list = [];
|
||||
Object.entries(this.props.content.peers.bundles).forEach(([addr, bundle]) => {
|
||||
if (!bundle.attempts) {
|
||||
return;
|
||||
}
|
||||
list.push({addr, location: bundle.location, attempts: bundle.attempts});
|
||||
});
|
||||
return list;
|
||||
};
|
||||
|
||||
knownPeerTableRow = (addr, enode, bundle, peer, showTraffic, proto) => {
|
||||
const ingressValues = peer.ingress.map(({value}) => ({ingress: value || 0.001}));
|
||||
const egressValues = peer.egress.map(({value}) => ({egress: -value || -0.001}));
|
||||
|
||||
return (
|
||||
<TableRow key={`known_${ip}_${id}`} style={styles.tableRow}>
|
||||
<TableRow key={`known_${addr}_${enode}`} style={styles.tableRow}>
|
||||
<TableCell style={styles.tableCell}>
|
||||
{peer.active
|
||||
? <FontAwesomeIcon icon={fasCircle} color='green' />
|
||||
: <FontAwesomeIcon icon={farCircle} style={commonStyles.light} />
|
||||
: <FontAwesomeIcon icon={farCircle} />
|
||||
}
|
||||
</TableCell>
|
||||
<TableCell style={{fontFamily: 'monospace', cursor: 'copy', ...styles.tableCell, ...commonStyles.light}} onClick={this.copyToClipboard(id)}>
|
||||
{id.substring(0, 10)}
|
||||
</TableCell>
|
||||
<TableCell style={styles.tableCell}>
|
||||
{bundle.location ? (() => {
|
||||
const l = bundle.location;
|
||||
return `${l.country ? l.country : ''}${l.city ? `/${l.city}` : ''}`;
|
||||
})() : ''}
|
||||
<TableCell
|
||||
style={{
|
||||
cursor: 'copy',
|
||||
...styles.tableCell,
|
||||
...limitedWidthStyle(80),
|
||||
}}
|
||||
onClick={this.copyToClipboard(enode)}
|
||||
>
|
||||
{enode.substring(8)}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
style={{
|
||||
cursor: 'copy',
|
||||
...styles.tableCell,
|
||||
...limitedWidthStyle(80),
|
||||
}}
|
||||
onClick={this.copyToClipboard(peer.name)}
|
||||
>
|
||||
{peer.shortName}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
style={{
|
||||
cursor: 'copy',
|
||||
...styles.tableCell,
|
||||
...limitedWidthStyle(100),
|
||||
}}
|
||||
onClick={this.copyToClipboard(JSON.stringify(bundle.location))}
|
||||
>
|
||||
{bundle.shortLocation}
|
||||
</TableCell>
|
||||
<TableCell style={styles.tableCell}>
|
||||
{showTraffic ? (
|
||||
<>
|
||||
<AreaChart
|
||||
width={trafficChartWidth}
|
||||
height={trafficChartHeight}
|
||||
data={ingressValues}
|
||||
margin={{top: 5, right: 5, bottom: 0, left: 5}}
|
||||
syncId={`peerIngress_${ip}_${id}`}
|
||||
syncId={`peerIngress_${addr}_${enode}`}
|
||||
>
|
||||
<defs>
|
||||
<linearGradient id={`ingressGradient_${ip}_${id}`} x1='0' y1='1' x2='0' y2='0'>
|
||||
<linearGradient id={`ingressGradient_${addr}_${enode}`} x1='0' y1='1' x2='0' y2='0'>
|
||||
{peer.ingressGradient
|
||||
&& peer.ingressGradient.map(({offset, color}, i) => (
|
||||
<stop
|
||||
key={`ingressStop_${ip}_${id}_${i}`}
|
||||
key={`ingressStop_${addr}_${enode}_${i}`}
|
||||
offset={`${offset}%`}
|
||||
stopColor={color}
|
||||
/>
|
||||
@ -405,7 +593,7 @@ class Network extends Component<Props, State> {
|
||||
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 +603,14 @@ class Network extends Component<Props, State> {
|
||||
height={trafficChartHeight}
|
||||
data={egressValues}
|
||||
margin={{top: 0, right: 5, bottom: 5, left: 5}}
|
||||
syncId={`peerIngress_${ip}_${id}`}
|
||||
syncId={`peerIngress_${addr}_${enode}`}
|
||||
>
|
||||
<defs>
|
||||
<linearGradient id={`egressGradient_${ip}_${id}`} x1='0' y1='1' x2='0' y2='0'>
|
||||
<linearGradient id={`egressGradient_${addr}_${enode}`} x1='0' y1='1' x2='0' y2='0'>
|
||||
{peer.egressGradient
|
||||
&& peer.egressGradient.map(({offset, color}, i) => (
|
||||
<stop
|
||||
key={`egressStop_${ip}_${id}_${i}`}
|
||||
key={`egressStop_${addr}_${enode}_${i}`}
|
||||
offset={`${offset}%`}
|
||||
stopColor={color}
|
||||
/>
|
||||
@ -435,95 +623,236 @@ class Network extends Component<Props, State> {
|
||||
dataKey='egress'
|
||||
isAnimationActive={false}
|
||||
type='monotone'
|
||||
fill={`url(#egressGradient_${ip}_${id})`}
|
||||
fill={`url(#egressGradient_${addr}_${enode})`}
|
||||
stroke={peer.egressGradient[0].color}
|
||||
strokeWidth={chartStrokeWidth}
|
||||
/>
|
||||
</AreaChart>
|
||||
</>
|
||||
) : null}
|
||||
</TableCell>
|
||||
{typeof proto === 'object' ? (
|
||||
<>
|
||||
<TableCell
|
||||
style={{
|
||||
cursor: 'copy',
|
||||
...styles.tableCell,
|
||||
...limitedWidthStyle(80),
|
||||
}}
|
||||
onClick={this.copyToClipboard(JSON.stringify(proto.head))}
|
||||
>
|
||||
{proto.head}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
style={{
|
||||
cursor: 'copy',
|
||||
...styles.tableCell,
|
||||
}}
|
||||
onClick={this.copyToClipboard(JSON.stringify(proto.difficulty))}
|
||||
>
|
||||
{proto.difficulty}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
style={{
|
||||
cursor: 'copy',
|
||||
...styles.tableCell,
|
||||
}}
|
||||
onClick={this.copyToClipboard(JSON.stringify(proto.version))}
|
||||
>
|
||||
{proto.version}
|
||||
</TableCell>
|
||||
</>
|
||||
) : null }
|
||||
</TableRow>
|
||||
);
|
||||
};
|
||||
|
||||
connectionAttemptTableRow = (addr, bundle) => (
|
||||
<TableRow key={`attempt_${addr}`} style={styles.tableRow}>
|
||||
<TableCell
|
||||
style={{cursor: 'copy', ...styles.tableCell}}
|
||||
onClick={this.copyToClipboard(addr)}
|
||||
>
|
||||
{addr}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
style={{cursor: 'copy', ...limitedWidthStyle(100), ...styles.tableCell}}
|
||||
onClick={this.copyToClipboard(JSON.stringify(bundle.location))}
|
||||
>
|
||||
{bundle.shortLocation}
|
||||
</TableCell>
|
||||
<TableCell style={styles.tableCell}>
|
||||
{bundle.attempts}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
|
||||
render() {
|
||||
const {classes} = this.props;
|
||||
return (
|
||||
<Grid container direction='row' justify='space-between'>
|
||||
<Grid item>
|
||||
<Grid container direction='row' spacing={3}>
|
||||
<Grid item style={{width: '40%'}}>
|
||||
<div className={classes.table} style={styles.table}>
|
||||
<Typography variant='subtitle1' gutterBottom className={classes.title} style={styles.title}>
|
||||
Full peers
|
||||
<FontAwesomeIcon
|
||||
icon={farClipboard}
|
||||
onClick={this.copyToClipboard(JSON.stringify(this.ethList()))}
|
||||
style={{float: 'right'}}
|
||||
/>
|
||||
</Typography>
|
||||
<Scrollbars style={styles.content}>
|
||||
<Table>
|
||||
<TableHead style={styles.tableHead}>
|
||||
<TableRow style={styles.tableRow}>
|
||||
<TableCell style={styles.tableCell} />
|
||||
<TableCell style={styles.tableCell}>Node ID</TableCell>
|
||||
<TableCell style={styles.tableCell}>Node URL</TableCell>
|
||||
<TableCell style={styles.tableCell}>Name</TableCell>
|
||||
<TableCell style={styles.tableCell}>Location</TableCell>
|
||||
<TableCell style={styles.tableCell}>Traffic</TableCell>
|
||||
<TableCell style={styles.tableCell}>Head</TableCell>
|
||||
<TableCell style={styles.tableCell}>TD</TableCell>
|
||||
<TableCell style={styles.tableCell}>V</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{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);
|
||||
if (peer.eth === '' && peer.les !== '') {
|
||||
return null;
|
||||
}
|
||||
return this.knownPeerTableRow(addr, enode, bundle, peer, true, peer.protocols.eth);
|
||||
});
|
||||
})}
|
||||
</TableBody>
|
||||
<TableBody>
|
||||
{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);
|
||||
if (peer.eth === '' && peer.les !== '') {
|
||||
return null;
|
||||
}
|
||||
return this.knownPeerTableRow(addr, enode, bundle, peer, false, peer.protocols.eth);
|
||||
});
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Scrollbars>
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Typography variant='subtitle1' gutterBottom>
|
||||
Connection attempts
|
||||
<Grid item style={{width: '40%'}}>
|
||||
<div className={classes.table} style={styles.table}>
|
||||
<Typography variant='subtitle1' gutterBottom className={classes.title} style={styles.title}>
|
||||
Light peers
|
||||
<FontAwesomeIcon
|
||||
icon={farClipboard}
|
||||
onClick={this.copyToClipboard(JSON.stringify(this.lesList()))}
|
||||
style={{float: 'right'}}
|
||||
/>
|
||||
</Typography>
|
||||
<Scrollbars style={styles.content}>
|
||||
<Table>
|
||||
<TableHead style={styles.tableHead}>
|
||||
<TableRow style={styles.tableRow}>
|
||||
<TableCell style={styles.tableCell}>IP</TableCell>
|
||||
<TableCell style={styles.tableCell} />
|
||||
<TableCell style={styles.tableCell}>Node URL</TableCell>
|
||||
<TableCell style={styles.tableCell}>Name</TableCell>
|
||||
<TableCell style={styles.tableCell}>Location</TableCell>
|
||||
<TableCell style={styles.tableCell}>Traffic</TableCell>
|
||||
<TableCell style={styles.tableCell}>Head</TableCell>
|
||||
<TableCell style={styles.tableCell}>TD</TableCell>
|
||||
<TableCell style={styles.tableCell}>V</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{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(([enode, peer]) => {
|
||||
if (peer.active === false) {
|
||||
return null;
|
||||
}
|
||||
if (peer.les === '' || peer.eth !== '') {
|
||||
return null;
|
||||
}
|
||||
return this.knownPeerTableRow(addr, enode, bundle, peer, true, peer.protocols.les);
|
||||
});
|
||||
})}
|
||||
</TableBody>
|
||||
<TableBody>
|
||||
{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(([enode, peer]) => {
|
||||
if (peer.active === true) {
|
||||
return null;
|
||||
}
|
||||
if (peer.les === '' || peer.eth !== '') {
|
||||
return null;
|
||||
}
|
||||
return this.knownPeerTableRow(addr, enode, bundle, peer, false, peer.protocols.les);
|
||||
});
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Scrollbars>
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid item xs>
|
||||
<div className={classes.table} style={styles.table}>
|
||||
<Typography variant='subtitle1' gutterBottom className={classes.title} style={styles.title}>
|
||||
Connection attempts
|
||||
<FontAwesomeIcon
|
||||
icon={farClipboard}
|
||||
onClick={this.copyToClipboard(JSON.stringify(this.attemptList()))}
|
||||
style={{float: 'right'}}
|
||||
/>
|
||||
</Typography>
|
||||
<Scrollbars style={styles.content}>
|
||||
<Table>
|
||||
<TableHead style={styles.tableHead}>
|
||||
<TableRow style={styles.tableRow}>
|
||||
<TableCell style={styles.tableCell}>TCP address</TableCell>
|
||||
<TableCell style={styles.tableCell}>Location</TableCell>
|
||||
<TableCell style={styles.tableCell}>Nr</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{Object.entries(this.props.content.peers.bundles).map(([ip, bundle]) => {
|
||||
if (!bundle.attempts || bundle.attempts.length < 1) {
|
||||
{Object.entries(this.props.content.peers.bundles).map(([addr, bundle]) => {
|
||||
if (!bundle.attempts || bundle.attempts <= attemptSeparator) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<TableRow key={`attempt_${ip}`} style={styles.tableRow}>
|
||||
<TableCell style={styles.tableCell}>{ip}</TableCell>
|
||||
<TableCell style={styles.tableCell}>
|
||||
{bundle.location ? (() => {
|
||||
const l = bundle.location;
|
||||
return `${l.country ? l.country : ''}${l.city ? `/${l.city}` : ''}`;
|
||||
})() : ''}
|
||||
</TableCell>
|
||||
<TableCell style={styles.tableCell}>
|
||||
{Object.values(bundle.attempts).length}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
return this.connectionAttemptTableRow(addr, bundle);
|
||||
})}
|
||||
</TableBody>
|
||||
<TableBody>
|
||||
{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);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Scrollbars>
|
||||
</div>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Network;
|
||||
export default withStyles(themeStyles)(Network);
|
||||
|
@ -47,10 +47,11 @@ const themeStyles = theme => ({
|
||||
background: theme.palette.grey[900],
|
||||
},
|
||||
listItem: {
|
||||
minWidth: theme.spacing.unit * 7,
|
||||
minWidth: theme.spacing(7),
|
||||
color: theme.palette.common.white,
|
||||
},
|
||||
icon: {
|
||||
fontSize: theme.spacing.unit * 3,
|
||||
fontSize: theme.spacing(3),
|
||||
overflow: 'unset',
|
||||
},
|
||||
});
|
||||
|
@ -1,54 +1,56 @@
|
||||
{
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@babel/core": "7.3.4",
|
||||
"@babel/plugin-proposal-class-properties": "7.3.4",
|
||||
"@babel/core": "7.4.5",
|
||||
"@babel/plugin-proposal-class-properties": "7.4.4",
|
||||
"@babel/plugin-proposal-function-bind": "7.2.0",
|
||||
"@babel/plugin-transform-flow-strip-types": "7.3.4",
|
||||
"@babel/preset-env": "7.3.4",
|
||||
"@babel/plugin-transform-flow-strip-types": "7.4.4",
|
||||
"@babel/preset-env": "7.4.5",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"@babel/preset-stage-0": "^7.0.0",
|
||||
"@fortawesome/fontawesome-free-regular": "^5.0.13",
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.15",
|
||||
"@fortawesome/free-regular-svg-icons": "^5.7.2",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.7.2",
|
||||
"@fortawesome/fontawesome-svg-core": "1.2.18",
|
||||
"@fortawesome/free-regular-svg-icons": "5.8.2",
|
||||
"@fortawesome/free-solid-svg-icons": "5.8.2",
|
||||
"@fortawesome/react-fontawesome": "^0.1.4",
|
||||
"@material-ui/core": "3.9.2",
|
||||
"@material-ui/icons": "3.0.2",
|
||||
"@material-ui/core": "4.0.1",
|
||||
"@material-ui/icons": "4.0.1",
|
||||
"babel-eslint": "10.0.1",
|
||||
"babel-loader": "8.0.5",
|
||||
"babel-loader": "8.0.6",
|
||||
"classnames": "^2.2.6",
|
||||
"color-convert": "^2.0.0",
|
||||
"css-loader": "2.1.1",
|
||||
"escape-html": "^1.0.3",
|
||||
"eslint": "5.15.1",
|
||||
"eslint": "5.16.0",
|
||||
"eslint-config-airbnb": "^17.0.0",
|
||||
"eslint-loader": "2.1.2",
|
||||
"eslint-plugin-flowtype": "3.4.2",
|
||||
"eslint-plugin-import": "2.16.0",
|
||||
"eslint-plugin-flowtype": "3.9.1",
|
||||
"eslint-plugin-import": "2.17.3",
|
||||
"eslint-plugin-jsx-a11y": "6.2.1",
|
||||
"eslint-plugin-node": "8.0.1",
|
||||
"eslint-plugin-promise": "4.0.1",
|
||||
"eslint-plugin-react": "7.12.4",
|
||||
"eslint-plugin-node": "9.1.0",
|
||||
"eslint-plugin-promise": "4.1.1",
|
||||
"eslint-plugin-react": "7.13.0",
|
||||
"file-loader": "3.0.1",
|
||||
"flow-bin": "0.94.0",
|
||||
"flow-bin": "0.98.1",
|
||||
"flow-bin-loader": "^1.0.3",
|
||||
"flow-typed": "^2.5.1",
|
||||
"js-beautify": "1.9.0",
|
||||
"flow-typed": "2.5.2",
|
||||
"js-beautify": "1.10.0",
|
||||
"path": "^0.12.7",
|
||||
"react": "16.8.4",
|
||||
"react-dom": "16.8.4",
|
||||
"react-hot-loader": "4.8.0",
|
||||
"react-transition-group": "2.6.1",
|
||||
"recharts": "1.5.0",
|
||||
"react": "16.8.6",
|
||||
"react-custom-scrollbars": "^4.2.1",
|
||||
"react-dom": "16.8.6",
|
||||
"react-hot-loader": "4.8.8",
|
||||
"react-scrollbar": "0.5.6",
|
||||
"react-transition-group": "4.0.1",
|
||||
"recharts": "1.6.2",
|
||||
"style-loader": "0.23.1",
|
||||
"terser-webpack-plugin": "^1.2.3",
|
||||
"terser-webpack-plugin": "1.3.0",
|
||||
"url": "^0.11.0",
|
||||
"url-loader": "1.1.2",
|
||||
"webpack": "4.29.6",
|
||||
"webpack-cli": "3.2.3",
|
||||
"webpack-dashboard": "3.0.0",
|
||||
"webpack-dev-server": "3.2.1",
|
||||
"webpack": "4.32.2",
|
||||
"webpack-cli": "3.3.2",
|
||||
"webpack-dashboard": "3.0.7",
|
||||
"webpack-dev-server": "3.4.1",
|
||||
"webpack-merge": "4.2.1"
|
||||
},
|
||||
"scripts": {
|
||||
|
@ -35,6 +35,7 @@ export type ChartEntry = {
|
||||
export type General = {
|
||||
version: ?string,
|
||||
commit: ?string,
|
||||
genesis: ?string,
|
||||
};
|
||||
|
||||
export type Home = {
|
||||
@ -42,21 +43,29 @@ export type Home = {
|
||||
};
|
||||
|
||||
export type Chain = {
|
||||
/* TODO (kurkomisi) */
|
||||
currentBlock: Block,
|
||||
};
|
||||
|
||||
export type Block = {
|
||||
number: number,
|
||||
timestamp: number,
|
||||
}
|
||||
|
||||
export type TxPool = {
|
||||
/* TODO (kurkomisi) */
|
||||
};
|
||||
|
||||
export type Network = {
|
||||
peers: Peers,
|
||||
diff: Array<PeerEvent>
|
||||
diff: Array<PeerEvent>,
|
||||
activePeerCount: number,
|
||||
};
|
||||
|
||||
export type PeerEvent = {
|
||||
ip: string,
|
||||
id: string,
|
||||
name: string,
|
||||
addr: string,
|
||||
enode: string,
|
||||
protocols: {[string]: Object},
|
||||
remove: string,
|
||||
location: GeoLocation,
|
||||
connected: Date,
|
||||
@ -73,7 +82,7 @@ export type Peers = {
|
||||
export type PeerBundle = {
|
||||
location: GeoLocation,
|
||||
knownPeers: {[string]: KnownPeer},
|
||||
attempts: Array<UnknownPeer>,
|
||||
attempts: number,
|
||||
};
|
||||
|
||||
export type KnownPeer = {
|
||||
@ -81,14 +90,12 @@ export type KnownPeer = {
|
||||
disconnected: Array<Date>,
|
||||
ingress: Array<ChartEntries>,
|
||||
egress: Array<ChartEntries>,
|
||||
name: string,
|
||||
enode: string,
|
||||
protocols: {[string]: Object},
|
||||
active: boolean,
|
||||
};
|
||||
|
||||
export type UnknownPeer = {
|
||||
connected: Date,
|
||||
disconnected: Date,
|
||||
};
|
||||
|
||||
export type GeoLocation = {
|
||||
country: string,
|
||||
city: string,
|
||||
|
File diff suppressed because it is too large
Load Diff
77
dashboard/chain.go
Normal file
77
dashboard/chain.go
Normal file
@ -0,0 +1,77 @@
|
||||
package dashboard
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
type block struct {
|
||||
Number int64 `json:"number,omitempty"`
|
||||
Time uint64 `json:"timestamp,omitempty"`
|
||||
}
|
||||
|
||||
func (db *Dashboard) collectChainData() {
|
||||
defer db.wg.Done()
|
||||
|
||||
var (
|
||||
currentBlock *block
|
||||
chainCh chan core.ChainHeadEvent
|
||||
chainSub event.Subscription
|
||||
)
|
||||
switch {
|
||||
case db.ethServ != nil:
|
||||
chain := db.ethServ.BlockChain()
|
||||
currentBlock = &block{
|
||||
Number: chain.CurrentHeader().Number.Int64(),
|
||||
Time: chain.CurrentHeader().Time,
|
||||
}
|
||||
chainCh = make(chan core.ChainHeadEvent)
|
||||
chainSub = chain.SubscribeChainHeadEvent(chainCh)
|
||||
case db.lesServ != nil:
|
||||
chain := db.lesServ.BlockChain()
|
||||
currentBlock = &block{
|
||||
Number: chain.CurrentHeader().Number.Int64(),
|
||||
Time: chain.CurrentHeader().Time,
|
||||
}
|
||||
chainCh = make(chan core.ChainHeadEvent)
|
||||
chainSub = chain.SubscribeChainHeadEvent(chainCh)
|
||||
default:
|
||||
errc := <-db.quit
|
||||
errc <- nil
|
||||
return
|
||||
}
|
||||
defer chainSub.Unsubscribe()
|
||||
|
||||
db.chainLock.Lock()
|
||||
db.history.Chain = &ChainMessage{
|
||||
CurrentBlock: currentBlock,
|
||||
}
|
||||
db.chainLock.Unlock()
|
||||
db.sendToAll(&Message{Chain: &ChainMessage{CurrentBlock: currentBlock}})
|
||||
|
||||
for {
|
||||
select {
|
||||
case e := <-chainCh:
|
||||
currentBlock := &block{
|
||||
Number: e.Block.Number().Int64(),
|
||||
Time: e.Block.Time(),
|
||||
}
|
||||
db.chainLock.Lock()
|
||||
db.history.Chain = &ChainMessage{
|
||||
CurrentBlock: currentBlock,
|
||||
}
|
||||
db.chainLock.Unlock()
|
||||
|
||||
db.sendToAll(&Message{Chain: &ChainMessage{CurrentBlock: currentBlock}})
|
||||
case err := <-chainSub.Err():
|
||||
log.Warn("Chain subscription error", "err", err)
|
||||
errc := <-db.quit
|
||||
errc <- nil
|
||||
return
|
||||
case errc := <-db.quit:
|
||||
errc <- nil
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
@ -27,14 +27,16 @@ package dashboard
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"io"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/eth"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/les"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
@ -45,6 +47,7 @@ import (
|
||||
|
||||
const (
|
||||
sampleLimit = 200 // Maximum number of data samples
|
||||
dataCollectorCount = 4
|
||||
)
|
||||
|
||||
// Dashboard contains the dashboard internals.
|
||||
@ -58,6 +61,7 @@ type Dashboard struct {
|
||||
history *Message // Stored historical data
|
||||
|
||||
lock sync.Mutex // Lock protecting the dashboard's internals
|
||||
chainLock sync.RWMutex // Lock protecting the stored blockchain data
|
||||
sysLock sync.RWMutex // Lock protecting the stored system data
|
||||
peerLock sync.RWMutex // Lock protecting the stored peer data
|
||||
logLock sync.RWMutex // Lock protecting the stored log data
|
||||
@ -67,6 +71,12 @@ 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.
|
||||
|
||||
ethServ *eth.Ethereum // Ethereum object serving internals.
|
||||
lesServ *les.LightEthereum // LightEthereum object serving internals.
|
||||
}
|
||||
|
||||
// client represents active websocket connection with a remote browser.
|
||||
@ -77,12 +87,23 @@ 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, ethServ *eth.Ethereum, lesServ *les.LightEthereum, 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)
|
||||
}
|
||||
var genesis common.Hash
|
||||
if ethServ != nil {
|
||||
genesis = ethServ.BlockChain().Genesis().Hash()
|
||||
} else if lesServ != nil {
|
||||
genesis = lesServ.BlockChain().Genesis().Hash()
|
||||
}
|
||||
return &Dashboard{
|
||||
conns: make(map[uint32]*client),
|
||||
config: config,
|
||||
@ -91,24 +112,29 @@ func New(config *Config, commit string, logdir string) *Dashboard {
|
||||
General: &GeneralMessage{
|
||||
Commit: commit,
|
||||
Version: fmt.Sprintf("v%d.%d.%d%s", params.VersionMajor, params.VersionMinor, params.VersionPatch, versionMeta),
|
||||
Genesis: genesis,
|
||||
},
|
||||
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,
|
||||
peerCh: peerCh,
|
||||
subPeer: p2p.SubscribeMeteredPeerEvent(peerCh),
|
||||
ethServ: ethServ,
|
||||
lesServ: lesServ,
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
@ -127,7 +153,8 @@ func (db *Dashboard) APIs() []rpc.API { return nil }
|
||||
func (db *Dashboard) Start(server *p2p.Server) error {
|
||||
log.Info("Starting dashboard", "url", fmt.Sprintf("http://%s:%d", db.config.Host, db.config.Port))
|
||||
|
||||
db.wg.Add(3)
|
||||
db.wg.Add(dataCollectorCount)
|
||||
go db.collectChainData()
|
||||
go db.collectSystemData()
|
||||
go db.streamLogs()
|
||||
go db.collectPeerData()
|
||||
@ -141,7 +168,11 @@ func (db *Dashboard) Start(server *p2p.Server) error {
|
||||
}
|
||||
db.listener = listener
|
||||
|
||||
go http.Serve(listener, nil)
|
||||
go func() {
|
||||
if err := http.Serve(listener, nil); err != http.ErrServerClosed {
|
||||
log.Warn("Could not accept incoming HTTP connections", "err", err)
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -155,8 +186,8 @@ func (db *Dashboard) Stop() error {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
// Close the collectors.
|
||||
errc := make(chan error, 1)
|
||||
for i := 0; i < 3; i++ {
|
||||
errc := make(chan error, dataCollectorCount)
|
||||
for i := 0; i < dataCollectorCount; i++ {
|
||||
db.quit <- errc
|
||||
if err := <-errc; err != nil {
|
||||
errs = append(errs, err)
|
||||
@ -230,20 +261,21 @@ func (db *Dashboard) apiHandler(conn *websocket.Conn) {
|
||||
}()
|
||||
|
||||
// Send the past data.
|
||||
db.chainLock.RLock()
|
||||
db.sysLock.RLock()
|
||||
db.peerLock.RLock()
|
||||
db.logLock.RLock()
|
||||
|
||||
h := deepcopy.Copy(db.history).(*Message)
|
||||
|
||||
db.chainLock.RUnlock()
|
||||
db.sysLock.RUnlock()
|
||||
db.peerLock.RUnlock()
|
||||
db.logLock.RUnlock()
|
||||
|
||||
client.msg <- h
|
||||
|
||||
// Start tracking the connection and drop at connection loss.
|
||||
db.lock.Lock()
|
||||
client.msg <- h
|
||||
db.conns[id] = client
|
||||
db.lock.Unlock()
|
||||
defer func() {
|
||||
|
@ -18,6 +18,8 @@ package dashboard
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
@ -39,6 +41,7 @@ type ChartEntry struct {
|
||||
type GeneralMessage struct {
|
||||
Version string `json:"version,omitempty"`
|
||||
Commit string `json:"commit,omitempty"`
|
||||
Genesis common.Hash `json:"genesis,omitempty"`
|
||||
}
|
||||
|
||||
type HomeMessage struct {
|
||||
@ -46,7 +49,7 @@ type HomeMessage struct {
|
||||
}
|
||||
|
||||
type ChainMessage struct {
|
||||
/* TODO (kurkomisi) */
|
||||
CurrentBlock *block `json:"currentBlock,omitempty"`
|
||||
}
|
||||
|
||||
type TxPoolMessage struct {
|
||||
|
@ -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.
|
||||
|
||||
// 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{
|
||||
e := &peerEvent{
|
||||
Activity: Active,
|
||||
IP: peer.ip,
|
||||
ID: peer.id,
|
||||
})
|
||||
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,22 +276,18 @@ 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
|
||||
@ -335,7 +295,6 @@ type ActivityType string
|
||||
|
||||
const (
|
||||
RemoveKnown RemovedPeerType = "known"
|
||||
RemoveAttempt RemovedPeerType = "attempt"
|
||||
RemoveBundle RemovedPeerType = "bundle"
|
||||
|
||||
Active ActivityType = "active"
|
||||
@ -344,8 +303,10 @@ 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.
|
||||
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.
|
||||
@ -353,6 +314,8 @@ type peerEvent struct {
|
||||
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.
|
||||
@ -376,14 +339,12 @@ func (db *Dashboard) collectPeerData() {
|
||||
db.geodb, err = openGeoDB()
|
||||
if err != nil {
|
||||
log.Warn("Failed to open geodb", "err", err)
|
||||
errc := <-db.quit
|
||||
errc <- nil
|
||||
return
|
||||
}
|
||||
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 +361,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: <common traffic prefix><IP>/<ID>
|
||||
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 +389,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.Peer.Node().String(),
|
||||
peer: event.Peer,
|
||||
Connected: &connected,
|
||||
})
|
||||
case p2p.PeerDisconnected:
|
||||
ip, id := event.IP.String(), event.ID.String()
|
||||
addr, enode := event.Addr, event.Peer.Node().String()
|
||||
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 +423,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 +438,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 +450,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 +488,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,8 +517,10 @@ 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)
|
||||
errc := <-db.quit
|
||||
errc <- nil
|
||||
return
|
||||
case errc := <-db.quit:
|
||||
errc <- nil
|
||||
|
@ -304,7 +304,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)
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,6 @@
|
||||
package p2p
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@ -28,7 +27,6 @@ import (
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -58,24 +56,24 @@ 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 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
|
||||
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
|
||||
}
|
||||
@ -92,8 +90,8 @@ 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
|
||||
addr *net.TCPAddr // TCP address of the peer
|
||||
peer *Peer // Peer instance
|
||||
|
||||
// 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
|
||||
@ -128,7 +126,7 @@ func newMeteredConn(conn net.Conn, ingress bool, ip net.IP) net.Conn {
|
||||
|
||||
return &meteredConn{
|
||||
Conn: conn,
|
||||
ip: ip,
|
||||
addr: addr,
|
||||
connected: time.Now(),
|
||||
}
|
||||
}
|
||||
@ -159,30 +157,27 @@ 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) {
|
||||
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.peer, c.trafficMetered = peer, false
|
||||
c.lock.Unlock()
|
||||
log.Warn("Metered peer count reached the limit")
|
||||
} else {
|
||||
key := fmt.Sprintf("%s/%s", c.ip, id.String())
|
||||
enode := peer.Node().String()
|
||||
c.lock.Lock()
|
||||
c.id, c.trafficMetered = id, true
|
||||
c.ingressMeter = metrics.NewRegisteredMeter(key, PeerIngressRegistry)
|
||||
c.egressMeter = metrics.NewRegisteredMeter(key, PeerEgressRegistry)
|
||||
c.peer, c.trafficMetered = peer, 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(),
|
||||
Peer: peer,
|
||||
Elapsed: time.Since(c.connected),
|
||||
})
|
||||
}
|
||||
@ -192,44 +187,43 @@ 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.peer == nil {
|
||||
// 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),
|
||||
})
|
||||
activePeerGauge.Dec(1)
|
||||
return err
|
||||
}
|
||||
id := c.id
|
||||
peer := c.peer
|
||||
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,
|
||||
Addr: c.addr.String(),
|
||||
Peer: peer,
|
||||
})
|
||||
activePeerGauge.Dec(1)
|
||||
return err
|
||||
}
|
||||
ingress, egress := uint64(c.ingressMeter.Count()), uint64(c.egressMeter.Count())
|
||||
ingress, egress, enode := uint64(c.ingressMeter.Count()), uint64(c.egressMeter.Count()), c.peer.Node().String()
|
||||
c.lock.RUnlock()
|
||||
|
||||
// Decrement the metered peer count
|
||||
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(),
|
||||
Peer: peer,
|
||||
Ingress: ingress,
|
||||
Egress: egress,
|
||||
})
|
||||
|
@ -779,6 +779,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
|
||||
@ -902,9 +905,13 @@ func (srv *Server) listenLoop() {
|
||||
continue
|
||||
}
|
||||
if remoteIP != nil {
|
||||
fd = newMeteredConn(fd, true, remoteIP)
|
||||
var addr *net.TCPAddr
|
||||
if tcp, ok := fd.RemoteAddr().(*net.TCPAddr); ok {
|
||||
addr = tcp
|
||||
}
|
||||
fd = newMeteredConn(fd, true, addr)
|
||||
srv.log.Trace("Accepted connection", "addr", fd.RemoteAddr())
|
||||
}
|
||||
go func() {
|
||||
srv.SetupConn(fd, inboundConn, nil)
|
||||
slots <- struct{}{}
|
||||
@ -974,9 +981,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.checkpointPostHandshake)
|
||||
if err != nil {
|
||||
|
Loading…
Reference in New Issue
Block a user