Skip to content
This repository has been archived by the owner on Feb 27, 2021. It is now read-only.

Commit

Permalink
Merge pull request #210 from etclabscore/feat/stats-miners
Browse files Browse the repository at this point in the history
feat: miner stats
  • Loading branch information
shanejonas authored Jan 3, 2020
2 parents 6fec58a + 256a6c1 commit be8d514
Show file tree
Hide file tree
Showing 13 changed files with 352 additions and 10 deletions.
8 changes: 7 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"history": "^4.9.0",
"i18next": "^17.3.1",
"i18next-browser-languagedetector": "^3.1.1",
"lodash": "^4.17.15",
"moment": "^2.24.0",
"qs": "^6.5.2",
"query-string": "^6.8.3",
Expand All @@ -64,6 +65,7 @@
"@semantic-release/exec": "^3.3.8",
"@types/bignumber.js": "^4.0.3",
"@types/jest": "^21.1.6",
"@types/lodash": "^4.14.149",
"@types/node": "^8.10.58",
"@types/react": "^16.9.11",
"@types/react-dom": "^16.9.3",
Expand Down
2 changes: 2 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { createPreserveQueryHistory } from "./helpers/createPreserveHistory";
import BlockRawContainer from "./containers/BlockRawContainer";
import TransactionRawContainer from "./containers/TransactionRawContainer";
import expeditionLogo from "./expedition.png";
import MinerStatsPage from "./containers/MinerStatsPage";

const history = createPreserveQueryHistory(createBrowserHistory, ["network", "rpcUrl"])();

Expand Down Expand Up @@ -254,6 +255,7 @@ function App(props: any) {
<CssBaseline />
<Switch>
<Route path={"/"} component={Dashboard} exact={true} />
<Route path={"/stats/miners"} component={MinerStatsPage} />
<Route path={"/block/:hash/raw"} component={BlockRawContainer} />
<Route path={"/block/:hash"} component={Block} />
<Route path={"/blocks/:number"} component={NodeView} />
Expand Down
16 changes: 16 additions & 0 deletions src/components/BlockView/BlockView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ function BlockView(props: any) {
const {
timestamp, hash, parentHash, miner, nonce, difficulty,
extraData, stateRoot, transactionsRoot, receiptsRoot, transactions,
gasUsed, gasLimit, size,
} = block;

return (
Expand Down Expand Up @@ -75,6 +76,21 @@ function BlockView(props: any) {
</TableCell>
</TableRow>

<TableRow>
<TableCell>{t("Gas Used")}</TableCell>
<TableCell>{hexToNumber(gasUsed)}</TableCell>
</TableRow>

<TableRow>
<TableCell>{t("Gas Limit")}</TableCell>
<TableCell>{hexToNumber(gasLimit)}</TableCell>
</TableRow>

<TableRow>
<TableCell>{t("Size")}</TableCell>
<TableCell>{hexToNumber(size)}</TableCell>
</TableRow>

<TableRow>
<TableCell>{t("Nonce")}</TableCell>
<TableCell>{hexToNumber(nonce)}</TableCell>
Expand Down
43 changes: 43 additions & 0 deletions src/components/CustomPieChartLabel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from "react";
import { VictoryTooltip } from "victory";

class CustomPieChartLabel extends React.Component {
public static defaultEvents = (VictoryTooltip as any).defaultEvents;
public render() {
return (
<>
{(this.props as any).defaultActive &&
<VictoryTooltip
{...(this.props as any)}
active={(this.props as any).defaultActive &&
(this.props as any).defaultActive.x === (this.props as any).datum.x}
text={`${(this.props as any).datum.x}\n${(this.props as any).datum.y}`}
cornerRadius={5}
height={40}
flyoutStyle={{
stroke: "none",
}}
/>
}
<VictoryTooltip
{...(this.props as any)}
// active={(this.props as any).defaultActive &&
// (this.props as any).defaultActive.x === (this.props as any).datum.x}
width={100}
text={`${(this.props as any).datum.x}\n${(this.props as any).datum.y}`}
// x={(this.props as any).width / 2}
// y={(this.props as any).y + 15}
// orientation="bottom"
// pointerLength={0}
cornerRadius={5}
height={40}
flyoutStyle={{
stroke: "none",
}}
/>
</>
);
}
}

export default CustomPieChartLabel;
97 changes: 97 additions & 0 deletions src/components/MinerStats.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import React, { useState } from "react";
import { Grid } from "@material-ui/core";
import ChartCard from "./ChartCard";
import { VictoryPie } from "victory";
import { hexToString } from "@etclabscore/eserialize";
import CustomPieChartLabel from "./CustomPieChartLabel";
import { useTranslation } from "react-i18next";
import _ from "lodash";

const blockTopMinerCount = (blocks: any[]) => {
const result = _(blocks).chain()
.countBy((b: any) => hexToString(b.extraData))
.map((key: string, val: number) => ({
x: val,
y: key,
label: val,
}))
.sortBy((item: any) => item.y)
.reverse()
.value();
return result;
};

const blockTopMinerCountByAddress = (blocks: any[]) => {
const result = _(blocks).chain()
.countBy((b: any) => b.miner)
.map((key: string, val: number) => ({
x: val,
y: key,
label: val,
}))
.sortBy((item: any) => item.y)
.reverse()
.value();
return result;
};

interface IProps {
blocks: any[];
config: any;
}

const config = {
blockTime: 15, // seconds
blockHistoryLength: 100,
chartHeight: 200,
chartWidth: 400,
};

const MinerStats: React.FC<IProps> = ({blocks}) => {
const [showDefaultPieHover, setShowDefaultPieHover] = useState(true);
const { t } = useTranslation();

return (
<Grid container justify="space-evenly">
<Grid key="uncles" item xs={12} md={4} lg={4}>
<ChartCard title={t("Top Miners last blocks by address", { count: config.blockHistoryLength })}>
<VictoryPie
cornerRadius={1}
// innerRadius={50}
colorScale="cool"
data={blockTopMinerCountByAddress(blocks)}
events={[{
target: "data",
eventHandlers: {
onMouseOver: () => {
return [{
target: "labels",
mutation: () => {
setShowDefaultPieHover(false);
return { active: true };
},
}];
},
},
}]}
labelComponent={<CustomPieChartLabel {...{
defaultActive: showDefaultPieHover ? blockTopMinerCountByAddress(blocks)[0] : undefined,
}} />}
>
</VictoryPie>
</ChartCard>
</Grid>
<Grid key="uncles" item xs={12} md={3} lg={3}>
<ChartCard title={t("Top Miners last blocks by extraData", { count: config.blockHistoryLength })}>
<VictoryPie
colorScale="cool"
labelComponent={<CustomPieChartLabel />}
data={blockTopMinerCount(blocks)}
/>
</ChartCard>
</Grid>
</Grid>
);
};

export default MinerStats;
97 changes: 97 additions & 0 deletions src/components/MinerStatsTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import React from "react";
import { Table, TableRow, TableCell, TableHead, TableBody, Typography, Button } from "@material-ui/core";
import { hexToString, hexToNumber } from "@etclabscore/eserialize";
import { useHistory } from "react-router-dom";
import _ from "lodash";
import greenColor from "@material-ui/core/colors/green";

const blockTopMiners = (blocks: any[]) => {
const result = _(blocks).chain()
.countBy((b: any) => b.miner)
.map((key: string, val: number) => ({
address: val,
blocksMined: key,
}))
.sortBy((item: any) => item.blocksMined)
.reverse()
.value();
return result;
};

const groupByMiner = (blocks: any[]) => {
const result = _.chain(blocks)
.groupBy((b: any) => b.miner)
.map((value, key) => {
return {
[key]: _.groupBy(value, (item) => {
return hexToString(item.extraData);
}),
};
})
.value();
return result;
};

interface IProps {
blocks: any[];
}

const MinerStatsTable: React.FC<IProps> = ({ blocks }) => {
const history = useHistory();
const topMiners = blockTopMiners(blocks);
const groupedMiners = Object.assign({}, ...groupByMiner(blocks));
return (
<Table aria-label="simple table">
<TableHead >
<TableRow>
<TableCell>Blocks Mined</TableCell>
<TableCell>Address</TableCell>
<TableCell>ExtraData</TableCell>
<TableCell>Blocks</TableCell>
</TableRow>
</TableHead>
<TableBody>
{topMiners.map((minerData: any) => (
<TableRow key={minerData.miner}>
<TableCell component="th" scope="row">
{minerData.blocksMined}
</TableCell>
<TableCell>{minerData.address}</TableCell>
<TableCell colSpan={2}>
<Table>
<TableBody>
{_.map(groupedMiners[minerData.address], (bs: any[], key: string) => (
<TableRow>
<TableCell>{key}</TableCell>
<TableCell colSpan={1}>
{bs.map((block) => {
const percentFull = (hexToNumber(block.gasUsed) / hexToNumber(block.gasLimit)) * 100;
return (
<Button
variant="outlined"
style={{
margin: "3px",
background: `linear-gradient(to right, ${greenColor[600]} 0% ${percentFull}%, transparent ${percentFull}% 100%)`,
}}
onClick={() => history.push(`/block/${block.hash}`)}
>
<Typography>
{hexToNumber(block.number)}
</Typography>
</Button>
);
})}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableCell>
</TableRow>
))}
</TableBody>
</Table >
);
};

export default MinerStatsTable;
Loading

0 comments on commit be8d514

Please sign in to comment.