From d1b76b4fe758bf74401d594658e85930878a0c02 Mon Sep 17 00:00:00 2001 From: shanejonas Date: Thu, 2 Jan 2020 15:44:12 -0800 Subject: [PATCH 1/3] feat: miner stats --- package-lock.json | 8 +- package.json | 2 + src/App.tsx | 2 + src/components/BlockView/BlockView.tsx | 16 ++++ src/components/CustomPieChartLabel.tsx | 43 ++++++++++ src/components/MinerStats.tsx | 97 +++++++++++++++++++++ src/components/MinerStatsTable.tsx | 112 +++++++++++++++++++++++++ src/containers/Dashboard.tsx | 35 +++++--- src/containers/MinerStatsPage.tsx | 49 +++++++++++ src/themes/jadeTheme.ts | 10 +++ src/translations/cn.ts | 4 + src/translations/en.ts | 4 + src/translations/kr.ts | 4 + 13 files changed, 374 insertions(+), 12 deletions(-) create mode 100644 src/components/CustomPieChartLabel.tsx create mode 100644 src/components/MinerStats.tsx create mode 100644 src/components/MinerStatsTable.tsx create mode 100644 src/containers/MinerStatsPage.tsx diff --git a/package-lock.json b/package-lock.json index 67d9c116..fb482397 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "@etclabscore/jade-explorer", + "name": "@etclabscore/expedition", "version": "1.0.2", "lockfileVersion": 1, "requires": true, @@ -3109,6 +3109,12 @@ "integrity": "sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A==", "dev": true }, + "@types/lodash": { + "version": "4.14.149", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.149.tgz", + "integrity": "sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ==", + "dev": true + }, "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", diff --git a/package.json b/package.json index 87b7200f..24bb71ec 100644 --- a/package.json +++ b/package.json @@ -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", @@ -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", diff --git a/src/App.tsx b/src/App.tsx index 9c8074b7..b9e1388c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -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"])(); @@ -254,6 +255,7 @@ function App(props: any) { + diff --git a/src/components/BlockView/BlockView.tsx b/src/components/BlockView/BlockView.tsx index 9724c1c3..8cc5da6c 100644 --- a/src/components/BlockView/BlockView.tsx +++ b/src/components/BlockView/BlockView.tsx @@ -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 ( @@ -75,6 +76,21 @@ function BlockView(props: any) { + + {t("Gas Used")} + {hexToNumber(gasUsed)} + + + + {t("Gas Limit")} + {hexToNumber(gasLimit)} + + + + {t("Size")} + {hexToNumber(size)} + + {t("Nonce")} {hexToNumber(nonce)} diff --git a/src/components/CustomPieChartLabel.tsx b/src/components/CustomPieChartLabel.tsx new file mode 100644 index 00000000..c4a8444c --- /dev/null +++ b/src/components/CustomPieChartLabel.tsx @@ -0,0 +1,43 @@ +import React from "react"; +import { VictoryLabel, VictoryTooltip } from "victory"; + +class CustomPieChartLabel extends React.Component { + public static defaultEvents = (VictoryTooltip as any).defaultEvents; + public render() { + return ( + <> + {(this.props as any).defaultActive && + + } + + + ); + } +} + +export default CustomPieChartLabel; diff --git a/src/components/MinerStats.tsx b/src/components/MinerStats.tsx new file mode 100644 index 00000000..5b56a897 --- /dev/null +++ b/src/components/MinerStats.tsx @@ -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 = ({blocks}) => { + const [showDefaultPieHover, setShowDefaultPieHover] = useState(true); + const { t } = useTranslation(); + + return ( + + + + { + return [{ + target: "labels", + mutation: () => { + setShowDefaultPieHover(false); + return { active: true }; + }, + }]; + }, + }, + }]} + labelComponent={} + > + + + + + + } + data={blockTopMinerCount(blocks)} + /> + + + + ); +}; + +export default MinerStats; diff --git a/src/components/MinerStatsTable.tsx b/src/components/MinerStatsTable.tsx new file mode 100644 index 00000000..2c99a3c7 --- /dev/null +++ b/src/components/MinerStatsTable.tsx @@ -0,0 +1,112 @@ +import React, { useState } from "react"; +import { Grid, Table, TableRow, TableCell, TableHead, TableBody, Typography, Button, LinearProgress } from "@material-ui/core"; +import { Link as RouterLink } from "react-router-dom"; +import { hexToString, hexToNumber } from "@etclabscore/eserialize"; +import { useHistory } from "react-router-dom"; +import { useTranslation } from "react-i18next"; +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[]; + config: any; +} + +const config = { + blockTime: 15, // seconds + blockHistoryLength: 100, + chartHeight: 200, + chartWidth: 400, +}; + +const MinerStatsTable: React.FC = ({ blocks }) => { + const [showHover, setHover] = useState(true); + const history = useHistory(); + const { t } = useTranslation(); + const topMiners = blockTopMiners(blocks); + const groupedMiners = Object.assign({}, ...groupByMiner(blocks)); + console.log("topMiners", topMiners); + console.log("groupedMiners", groupedMiners); + return ( + + + + Blocks Mined + Address + ExtraData + Blocks + + + + {topMiners.map((minerData: any) => ( + + + {minerData.blocksMined} + + {minerData.address} + +
+ + {_.map(groupedMiners[minerData.address], (bs: any[], key: string) => ( + + {key} + + {bs.map((block) => { + const percentFull = (hexToNumber(block.gasUsed) / hexToNumber(block.gasLimit)) * 100; + return ( + + ) + })} + + + ))} + +
+ + + +
+ ))} + + + ); +}; + +export default MinerStatsTable; diff --git a/src/containers/Dashboard.tsx b/src/containers/Dashboard.tsx index ed2a956f..07fe53f2 100644 --- a/src/containers/Dashboard.tsx +++ b/src/containers/Dashboard.tsx @@ -1,8 +1,9 @@ -import { Grid, Typography, CircularProgress, Theme } from "@material-ui/core"; +import { Grid, Typography, CircularProgress, Theme, Button } from "@material-ui/core"; import useMultiGethStore from "../stores/useMultiGethStore"; +import _ from "lodash"; import BigNumber from "bignumber.js"; import * as React from "react"; -import { VictoryBar, VictoryChart, VictoryLine } from "victory"; +import { VictoryBar, VictoryChart, VictoryLine, VictoryPie } from "victory"; import { hashesToGH, weiToGwei } from "../components/formatters"; import HashRate from "../components/HashRate"; import getBlocks, { useBlockNumber } from "../helpers"; @@ -12,9 +13,11 @@ import getTheme from "../themes/victoryTheme"; import ChartCard from "../components/ChartCard"; import BlockCardListContainer from "./BlockCardList"; import BlockListContainer from "./BlockList"; -import { hexToNumber } from "@etclabscore/eserialize"; +import { hexToNumber, hexToString } from "@etclabscore/eserialize"; import EthereumJSONRPC from "@etclabscore/ethereum-json-rpc"; import { useTranslation } from "react-i18next"; +import { ArrowForwardIos } from "@material-ui/icons"; +import { useHistory } from "react-router-dom"; const useState = React.useState; @@ -56,6 +59,7 @@ const blockMapTransactionCount = (block: any) => { export default (props: any) => { const [erpc]: [EthereumJSONRPC] = useMultiGethStore(); const theme = useTheme(); + const history = useHistory(); const victoryTheme = getTheme(theme); const [blockNumber] = useBlockNumber(erpc); const [chainId, setChainId] = useState(); @@ -64,6 +68,7 @@ export default (props: any) => { const [gasPrice, setGasPrice] = useState(); const [syncing, setSyncing] = useState(); const [peerCount, setPeerCount] = useState(); + const [showDefaultPieHover, setShowDefaultPieHover] = useState(true); const [pendingTransctionsLength, setPendingTransactionsLength] = useState(0); const { t } = useTranslation(); @@ -81,7 +86,7 @@ export default (props: any) => { React.useEffect(() => { if (!erpc || blockNumber === undefined) { return; } erpc.eth_getBlockByNumber(`0x${blockNumber.toString(16)}`, true).then(setBlock); - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-hooks/exhaustive-deps }, [blockNumber]); React.useEffect(() => { @@ -93,7 +98,7 @@ export default (props: any) => { ).then((bl) => { setBlocks(bl); }); - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-hooks/exhaustive-deps }, [blockNumber]); useInterval(() => { @@ -168,36 +173,44 @@ export default (props: any) => { - + - + - + - + - + + + +
{ props.history.push(`/blocks/${blockNumber - 15}`); }} style={{ marginTop: "30px" }} /> - + ); }; diff --git a/src/containers/MinerStatsPage.tsx b/src/containers/MinerStatsPage.tsx new file mode 100644 index 00000000..f8c667d2 --- /dev/null +++ b/src/containers/MinerStatsPage.tsx @@ -0,0 +1,49 @@ + +import { CircularProgress } from "@material-ui/core"; +import useMultiGethStore from "../stores/useMultiGethStore"; +import _ from "lodash"; +import * as React from "react"; +import getBlocks, { useBlockNumber } from "../helpers"; +import EthereumJSONRPC from "@etclabscore/ethereum-json-rpc"; +import { useTranslation } from "react-i18next"; +import MinerStats from "../components/MinerStats"; +import MinerStatsTable from "../components/MinerStatsTable"; + +const useState = React.useState; + +const config = { + blockTime: 15, // seconds + blockHistoryLength: 100, + chartHeight: 200, + chartWidth: 400, +}; + +export default (props: any) => { + const [erpc]: [EthereumJSONRPC] = useMultiGethStore(); + const [blockNumber] = useBlockNumber(erpc); + const [blocks, setBlocks] = useState(); + const { t } = useTranslation(); + + React.useEffect(() => { + if (!erpc || blockNumber === null) { return; } + getBlocks( + Math.max(blockNumber - config.blockHistoryLength + 1, 0), + blockNumber, + erpc, + ).then((bl) => { + setBlocks(bl); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [blockNumber]); + + if (!blocks) { + return ; + } + + return ( + <> + + + + ); +}; diff --git a/src/themes/jadeTheme.ts b/src/themes/jadeTheme.ts index 15d85ea7..6a1e61b0 100644 --- a/src/themes/jadeTheme.ts +++ b/src/themes/jadeTheme.ts @@ -16,6 +16,11 @@ export const lightTheme = responsiveFontSizes(createMuiTheme({ background: "#fff !important", }, }, + MuiPaper: { + root: { + overflow: "visible !important", + }, + }, }, palette: { background: { @@ -41,6 +46,11 @@ export const darkTheme = responsiveFontSizes(createMuiTheme({ }, }, overrides: { + MuiPaper: { + root: { + overflow: "visible !important", + }, + }, MuiTable: { root: { background: "transparent !important", diff --git a/src/translations/cn.ts b/src/translations/cn.ts index e3a5b710..1e18b340 100644 --- a/src/translations/cn.ts +++ b/src/translations/cn.ts @@ -23,6 +23,10 @@ export default { "Gas Used last blocks_plural": "前{{count}}个区块Gas消耗", "Uncles last blocks": "上个区块叔块", "Uncles last blocks_plural": "前{{count}}个区块叔块", + "Top Miners last blocks by extraData": "Top Miners last block", + "Top Miners last blocks by extraData_plural": "Top Miners last {{count}} blocks", + "Top Miners last blocks by address": "Top Miners last block by address", + "Top Miners last blocks by address_plural": "Top Miners last {{count}} blocks by address", // block list "Block Number": "区块编号", "Hash": "哈希", diff --git a/src/translations/en.ts b/src/translations/en.ts index 53f9a636..c2621ffc 100644 --- a/src/translations/en.ts +++ b/src/translations/en.ts @@ -21,6 +21,10 @@ export default { "Gas Used last blocks_plural": "Gas Used last {{count}} blocks", "Uncles last blocks": "Uncles last block", "Uncles last blocks_plural": "Uncles last {{count}} blocks", + "Top Miners last blocks by extraData": "Top Miners last block by extraData", + "Top Miners last blocks by extraData_plural": "Top Miners last {{count}} blocks by extraData", + "Top Miners last blocks by address": "Top Miners last block by address", + "Top Miners last blocks by address_plural": "Top Miners last {{count}} blocks by address", "Block Number": "Block Number", "Hash": "Hash", "Timestamp": "Timestamp", diff --git a/src/translations/kr.ts b/src/translations/kr.ts index 40a279d1..235cb8d9 100644 --- a/src/translations/kr.ts +++ b/src/translations/kr.ts @@ -23,6 +23,10 @@ export default { "Gas Used last blocks_plural": "{{count}}개 이전 블록 Gas소모", "Uncles last blocks": "이번 블록의 엉클블록", "Uncles last blocks_plural": "{{count}}개 이전 블록의 엉클블록", + "Top Miners last blocks": "Top Miners last block", + "Top Miners last blocks_plural": "Top Miners last {{count}} blocks", + "Top Miners last blocks by address": "Top Miners last block by address", + "Top Miners last blocks by address_plural": "Top Miners last {{count}} blocks by address", // block list "Block Number": "블록번호", "Hash": "해시", From b23be409bf8d2305e5ab0deb24bdacb974bbd829 Mon Sep 17 00:00:00 2001 From: shanejonas Date: Thu, 2 Jan 2020 16:04:18 -0800 Subject: [PATCH 2/3] fix: linting --- src/components/BlockView/BlockView.tsx | 2 +- src/components/CustomPieChartLabel.tsx | 2 +- src/components/MinerStatsTable.tsx | 21 +++------------------ src/containers/Dashboard.tsx | 6 ++---- src/containers/MinerStatsPage.tsx | 5 +---- 5 files changed, 8 insertions(+), 28 deletions(-) diff --git a/src/components/BlockView/BlockView.tsx b/src/components/BlockView/BlockView.tsx index 8cc5da6c..08868c1c 100644 --- a/src/components/BlockView/BlockView.tsx +++ b/src/components/BlockView/BlockView.tsx @@ -20,7 +20,7 @@ function BlockView(props: any) { const { timestamp, hash, parentHash, miner, nonce, difficulty, extraData, stateRoot, transactionsRoot, receiptsRoot, transactions, - gasUsed, gasLimit, size + gasUsed, gasLimit, size, } = block; return ( diff --git a/src/components/CustomPieChartLabel.tsx b/src/components/CustomPieChartLabel.tsx index c4a8444c..8743aed3 100644 --- a/src/components/CustomPieChartLabel.tsx +++ b/src/components/CustomPieChartLabel.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { VictoryLabel, VictoryTooltip } from "victory"; +import { VictoryTooltip } from "victory"; class CustomPieChartLabel extends React.Component { public static defaultEvents = (VictoryTooltip as any).defaultEvents; diff --git a/src/components/MinerStatsTable.tsx b/src/components/MinerStatsTable.tsx index 2c99a3c7..904a81bf 100644 --- a/src/components/MinerStatsTable.tsx +++ b/src/components/MinerStatsTable.tsx @@ -1,6 +1,5 @@ -import React, { useState } from "react"; -import { Grid, Table, TableRow, TableCell, TableHead, TableBody, Typography, Button, LinearProgress } from "@material-ui/core"; -import { Link as RouterLink } from "react-router-dom"; +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 { useTranslation } from "react-i18next"; @@ -36,24 +35,12 @@ const groupByMiner = (blocks: any[]) => { interface IProps { blocks: any[]; - config: any; } -const config = { - blockTime: 15, // seconds - blockHistoryLength: 100, - chartHeight: 200, - chartWidth: 400, -}; - const MinerStatsTable: React.FC = ({ blocks }) => { - const [showHover, setHover] = useState(true); const history = useHistory(); - const { t } = useTranslation(); const topMiners = blockTopMiners(blocks); const groupedMiners = Object.assign({}, ...groupByMiner(blocks)); - console.log("topMiners", topMiners); - console.log("groupedMiners", groupedMiners); return ( @@ -92,7 +79,7 @@ const MinerStatsTable: React.FC = ({ blocks }) => { {hexToNumber(block.number)} - ) + ); })} @@ -100,8 +87,6 @@ const MinerStatsTable: React.FC = ({ blocks }) => {
- - ))} diff --git a/src/containers/Dashboard.tsx b/src/containers/Dashboard.tsx index 07fe53f2..a0057c0c 100644 --- a/src/containers/Dashboard.tsx +++ b/src/containers/Dashboard.tsx @@ -1,9 +1,8 @@ import { Grid, Typography, CircularProgress, Theme, Button } from "@material-ui/core"; import useMultiGethStore from "../stores/useMultiGethStore"; -import _ from "lodash"; import BigNumber from "bignumber.js"; import * as React from "react"; -import { VictoryBar, VictoryChart, VictoryLine, VictoryPie } from "victory"; +import { VictoryBar, VictoryChart, VictoryLine } from "victory"; import { hashesToGH, weiToGwei } from "../components/formatters"; import HashRate from "../components/HashRate"; import getBlocks, { useBlockNumber } from "../helpers"; @@ -13,7 +12,7 @@ import getTheme from "../themes/victoryTheme"; import ChartCard from "../components/ChartCard"; import BlockCardListContainer from "./BlockCardList"; import BlockListContainer from "./BlockList"; -import { hexToNumber, hexToString } from "@etclabscore/eserialize"; +import { hexToNumber } from "@etclabscore/eserialize"; import EthereumJSONRPC from "@etclabscore/ethereum-json-rpc"; import { useTranslation } from "react-i18next"; import { ArrowForwardIos } from "@material-ui/icons"; @@ -68,7 +67,6 @@ export default (props: any) => { const [gasPrice, setGasPrice] = useState(); const [syncing, setSyncing] = useState(); const [peerCount, setPeerCount] = useState(); - const [showDefaultPieHover, setShowDefaultPieHover] = useState(true); const [pendingTransctionsLength, setPendingTransactionsLength] = useState(0); const { t } = useTranslation(); diff --git a/src/containers/MinerStatsPage.tsx b/src/containers/MinerStatsPage.tsx index f8c667d2..43f3ba45 100644 --- a/src/containers/MinerStatsPage.tsx +++ b/src/containers/MinerStatsPage.tsx @@ -1,11 +1,9 @@ import { CircularProgress } from "@material-ui/core"; import useMultiGethStore from "../stores/useMultiGethStore"; -import _ from "lodash"; import * as React from "react"; import getBlocks, { useBlockNumber } from "../helpers"; import EthereumJSONRPC from "@etclabscore/ethereum-json-rpc"; -import { useTranslation } from "react-i18next"; import MinerStats from "../components/MinerStats"; import MinerStatsTable from "../components/MinerStatsTable"; @@ -22,7 +20,6 @@ export default (props: any) => { const [erpc]: [EthereumJSONRPC] = useMultiGethStore(); const [blockNumber] = useBlockNumber(erpc); const [blocks, setBlocks] = useState(); - const { t } = useTranslation(); React.useEffect(() => { if (!erpc || blockNumber === null) { return; } @@ -43,7 +40,7 @@ export default (props: any) => { return ( <> - + ); }; From 256a6c1a206d714730d9e7a5ef27d3a45d7d3ed4 Mon Sep 17 00:00:00 2001 From: shanejonas Date: Thu, 2 Jan 2020 16:58:10 -0800 Subject: [PATCH 3/3] fix: green percent color and margin for block fill percentage --- src/components/MinerStatsTable.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/MinerStatsTable.tsx b/src/components/MinerStatsTable.tsx index 904a81bf..d3f268a1 100644 --- a/src/components/MinerStatsTable.tsx +++ b/src/components/MinerStatsTable.tsx @@ -2,7 +2,6 @@ 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 { useTranslation } from "react-i18next"; import _ from "lodash"; import greenColor from "@material-ui/core/colors/green"; @@ -71,7 +70,8 @@ const MinerStatsTable: React.FC = ({ blocks }) => {