get more network info from RPC

This commit is contained in:
Matthias Kretschmann 2019-12-12 20:48:00 +01:00
parent 9c129af275
commit 2bb562dd7a
Signed by: m
GPG Key ID: 606EEEF3C479A91F
16 changed files with 801 additions and 357 deletions

783
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,7 @@
"build": "next build",
"serve": "next start",
"analyze": "ANALYZE=true next build",
"test": "npm run lint && NODE_ENV=test jest",
"test": "npm run lint && NODE_ENV=test jest --silent",
"test:watch": "npm run lint && NODE_ENV=test jest --watch",
"lint": "eslint --ignore-path .gitignore --ext .js .",
"format": "prettier --ignore-path .gitignore **/**/*.{css,yml,js,jsx,ts,tsx,json} --write"
@ -19,22 +19,24 @@
"@oceanprotocol/typographies": "^0.1.0",
"@zeit/next-css": "^1.0.1",
"axios": "^0.19.0",
"next": "9.1.4",
"ethereumjs-units": "^0.2.0",
"next": "^9.1.5",
"next-seo": "^3.1.0",
"next-svgr": "^0.0.2",
"react": "^16.12.0",
"react-dom": "^16.12.0"
},
"devDependencies": {
"@next/bundle-analyzer": "^9.1.4",
"@next/bundle-analyzer": "^9.1.5",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/react": "^9.3.3",
"@types/bn.js": "^4.11.5",
"@types/jest": "^24.0.23",
"@types/next-seo": "^1.10.0",
"@types/node": "^12.12.14",
"@types/react": "^16.9.15",
"@typescript-eslint/eslint-plugin": "^2.10.0",
"@typescript-eslint/parser": "^2.10.0",
"@types/node": "^12.12.17",
"@types/react": "^16.9.16",
"@typescript-eslint/eslint-plugin": "^2.11.0",
"@typescript-eslint/parser": "^2.11.0",
"cssnano": "^4.1.10",
"eslint": "^6.7.2",
"eslint-config-oceanprotocol": "^1.5.0",

1
src/@types/node_modules.d.ts vendored Normal file
View File

@ -0,0 +1 @@
declare module 'ethereumjs-units'

View File

@ -1,6 +1,6 @@
import React from 'react'
import { render } from '@testing-library/react'
import Layout from '../Layout'
import Layout from './Layout'
describe('Layout', () => {
it('renders without crashing', () => {

View File

@ -1,68 +0,0 @@
import React from 'react'
import { render, wait, waitForElement } from '@testing-library/react'
import axios from 'axios'
import Network from '../components/Network'
import { mocked } from 'ts-jest/dist/util/testing'
jest.mock('axios')
const axiosMock: any = mocked(axios)
const mockResponse = {
status: 200,
duration: 1000,
data: { result: '0x345' }
}
const network = {
name: 'Pacific',
project: 'Ocean Protocol',
type: 'mainnet',
networkId: '0xCEA11',
rpcUrl: 'https://pacific.oceanprotocol.com',
explorerUrl: 'https://submarine.oceanprotocol.com'
}
const networkNoRpc = {
name: 'Pacific',
project: 'Ocean Protocol',
type: 'mainnet',
networkId: '0xCEA11',
explorerUrl: 'https://submarine.oceanprotocol.com'
}
afterEach(() => {
jest.clearAllTimers()
})
describe('Network', () => {
it('renders without crashing', async () => {
axiosMock.post.mockResolvedValue(mockResponse)
const { container, rerender, getByText } = render(
<Network network={network} />
)
expect(container.firstChild).toBeInTheDocument()
await waitForElement(() => getByText('Online'))
expect(axiosMock.post).toHaveBeenCalledTimes(2)
rerender(<Network network={networkNoRpc} />)
await waitForElement(() => getByText('Online'))
expect(axiosMock.post).toHaveBeenCalledTimes(2)
})
it('renders without response', async () => {
axiosMock.post.mockResolvedValue(undefined)
const { container } = render(<Network network={network} />)
await wait()
expect(container.firstChild).toBeInTheDocument()
})
it('re-fetches after 5 sec.', async () => {
jest.useFakeTimers()
axiosMock.post.mockResolvedValue(mockResponse)
const { getByText } = render(<Network network={network} />)
jest.advanceTimersByTime(6000)
await waitForElement(() => getByText('Online'))
// expect(setInterval).toHaveBeenCalledTimes(1)
})
})

View File

@ -0,0 +1,18 @@
.moreInfo {
font-size: var(--font-size-mini);
}
.moreInfo p {
margin-bottom: 0;
display: flex;
justify-content: space-between;
}
.moreInfo span {
color: var(--brand-grey-lighter);
}
.clientVersion {
margin-top: var(--spacer);
font-size: var(--font-size-mini);
}

View File

@ -0,0 +1,35 @@
import React from 'react'
import styles from './Info.module.css'
export default function Info({
gasPrice,
gasLimit,
clientVersion,
peers
}: {
gasPrice: string
gasLimit: string
clientVersion: string
peers: number
}) {
return (
<div className={styles.moreInfo}>
{peers && (
<p>
Connected Peers <span>{peers}</span>
</p>
)}
{gasLimit && (
<p>
Gas Limit <span>{gasLimit} gas</span>
</p>
)}
{gasPrice && (
<p>
Gas Prize <span>{gasPrice} Gwei</span>
</p>
)}
{clientVersion && <p className={styles.clientVersion}>{clientVersion}</p>}
</div>
)
}

View File

@ -1,14 +1,22 @@
.networkData {
min-height: 66px;
min-height: 105px;
}
.status {
font-size: var(--font-size-large);
margin-bottom: calc(var(--spacer) / 8);
}
.block {
margin-bottom: var(--spacer);
}
.block a {
color: var(--brand-grey-lighter);
}
.status {
font-size: var(--font-size-large);
margin-bottom: 0;
.block a:hover {
color: var(--brand-pink);
}
.success {
@ -24,7 +32,3 @@
margin-left: calc(var(--spacer) / 4);
font-size: var(--font-size-mini);
}
.clientVersion {
font-size: var(--font-size-mini);
}

View File

@ -1,25 +1,37 @@
import React, { useState, useEffect } from 'react'
import { fetchRpc, AxiosResponseCustom } from '../../rpc'
import { fetchRpc } from '../../rpc'
import Spinner from '../Spinner'
import { NetworkProps } from '.'
import styles from './Data.module.css'
import styles from './Status.module.css'
import Info from './Info'
import { getClientVersion, getGasPrize, getPeers } from './utils'
export default function Data({ network }: { network: NetworkProps }) {
export default function Status({ network }: { network: NetworkProps }) {
const { rpcUrl, explorerUrl } = network
const [status, setStatus] = useState('')
const [block, setBlock] = useState(0)
const [latency, setLatency] = useState(0)
const [gasLimit, setGasLimit] = useState()
const [clientVersion, setClientVersion] = useState('')
const [gasPrice, setGasPrice] = useState()
const [peers, setPeers] = useState()
async function getStatusAndBlock() {
if (!rpcUrl) return
const response: AxiosResponseCustom = await fetchRpc(
rpcUrl,
'eth_blockNumber'
)
const response = await fetchRpc(rpcUrl, 'eth_getBlockByNumber', [
'latest',
true
])
if (!response || response.status !== 200) {
const responseParity = await fetchRpc(rpcUrl, 'parity_mode')
if (
!response ||
!response.data ||
response.status !== 200 ||
responseParity.data.result !== 'active'
) {
setStatus('Offline')
return
}
@ -27,29 +39,26 @@ export default function Data({ network }: { network: NetworkProps }) {
setStatus('Online')
response.duration && setLatency(response.duration)
const blockNumber =
response && response.data && parseInt(response.data.result, 16)
setBlock(blockNumber)
const { number, gasLimit } = response.data.result
setBlock(parseInt(number, 16))
setGasLimit(parseInt(gasLimit, 16))
}
async function getClientVersion() {
async function getData() {
getStatusAndBlock()
if (!rpcUrl) return
const response: AxiosResponseCustom = await fetchRpc(
rpcUrl,
'web3_clientVersion'
)
response && response.data && setClientVersion(response.data.result)
setClientVersion(await getClientVersion(rpcUrl))
setGasPrice(await getGasPrize(rpcUrl))
setPeers(await getPeers(rpcUrl))
}
useEffect(() => {
getStatusAndBlock()
getClientVersion()
getData()
const timer = setInterval(() => {
getStatusAndBlock()
getClientVersion()
getData()
}, 5000) // run every 5 sec.
return () => {
clearInterval(timer)
@ -77,9 +86,13 @@ export default function Data({ network }: { network: NetworkProps }) {
At block #<a href={`${explorerUrl}/blocks/${block}`}>{block}</a>
</p>
)}
{clientVersion && (
<p className={styles.clientVersion}>{clientVersion}</p>
)}
<Info
gasPrice={gasPrice}
gasLimit={gasLimit}
clientVersion={clientVersion}
peers={peers}
/>
</>
) : (
<Spinner />

View File

@ -0,0 +1,33 @@
import React from 'react'
import { render, waitForElement } from '@testing-library/react'
import Network from '.'
const network = {
name: 'Pacific',
project: 'Ocean Protocol',
type: 'mainnet',
networkId: '0xCEA11',
rpcUrl: 'https://pacific.oceanprotocol.com',
explorerUrl: 'https://submarine.oceanprotocol.com'
}
const networkNoRpc = {
name: 'Pacific',
project: 'Ocean Protocol',
type: 'mainnet',
networkId: '0xCEA11',
explorerUrl: 'https://submarine.oceanprotocol.com'
}
describe('Network', () => {
it('renders without crashing', async () => {
const { container, rerender, getByText } = render(
<Network network={network} />
)
expect(container.firstChild).toBeInTheDocument()
await waitForElement(() => getByText('Online' || 'Offline'))
rerender(<Network network={networkNoRpc} />)
await waitForElement(() => getByText('Online' || 'Offline'))
})
})

View File

@ -1,6 +1,6 @@
import React from 'react'
import styles from './index.module.css'
import Data from './Data'
import Status from './Status'
export interface NetworkProps {
name: string
@ -26,7 +26,7 @@ export default function Network({ network }: { network: NetworkProps }) {
</p>
</header>
<Data network={network} />
<Status network={network} />
</div>
)
}

View File

@ -0,0 +1,63 @@
import axios from 'axios'
import { convert } from 'ethereumjs-units'
import { mocked } from 'ts-jest/dist/util/testing'
import { getGasPrize, getClientVersion, getPeers } from './utils'
jest.mock('axios')
const axiosMock: any = mocked(axios)
describe('Network Utils', () => {
it('getGasPrize', async () => {
const response = {
status: 200,
duration: 1000,
data: { result: '0x345' }
}
axiosMock.post.mockImplementationOnce(() => Promise.resolve(response))
await expect(getGasPrize('http://rpc.com')).resolves.toEqual(
convert(parseInt(response.data.result, 16), 'wei', 'gwei')
)
})
it('getGasPrize: Error', async () => {
axiosMock.post.mockImplementationOnce(() => Promise.resolve(null))
await expect(getGasPrize('http://rpc.com')).resolves.toEqual(null)
})
it('getClientVersion', async () => {
const response = {
status: 200,
duration: 1000,
data: { result: '0x345' }
}
axiosMock.post.mockImplementationOnce(() => Promise.resolve(response))
await expect(getClientVersion('http://rpc.com')).resolves.toEqual(
response.data.result
)
})
it('getClientVersion: Error', async () => {
axiosMock.post.mockImplementationOnce(() => Promise.resolve(null))
await expect(getClientVersion('http://rpc.com')).resolves.toEqual(null)
})
it('getPeers', async () => {
const response = {
status: 200,
duration: 1000,
data: { result: '0x24' }
}
axiosMock.post.mockImplementationOnce(() => Promise.resolve(response))
await expect(getPeers('http://rpc.com')).resolves.toEqual(
parseInt(response.data.result, 16)
)
})
it('getPeers: Error', async () => {
axiosMock.post.mockImplementationOnce(() => Promise.resolve(null))
await expect(getPeers('http://rpc.com')).resolves.toEqual(null)
})
})

View File

@ -0,0 +1,40 @@
import { convert } from 'ethereumjs-units'
import { fetchRpc, AxiosResponseCustom } from '../../rpc'
export async function getGasPrize(rpcUrl: string) {
try {
const response: AxiosResponseCustom = await fetchRpc(rpcUrl, 'eth_gasPrice')
return convert(parseInt(response.data.result, 16), 'wei', 'gwei')
} catch (error) {
console.log(error.message)
return null
}
}
export async function getClientVersion(rpcUrl: string) {
try {
const response: AxiosResponseCustom = await fetchRpc(
rpcUrl,
'web3_clientVersion'
)
return response.data.result
} catch (error) {
console.log(error.message)
return null
}
}
export async function getPeers(rpcUrl: string) {
try {
const response: AxiosResponseCustom = await fetchRpc(
rpcUrl,
'net_peerCount'
)
return parseInt(response.data.result, 16)
} catch (error) {
console.log(error.message)
return null
}
}

View File

@ -1,7 +1,7 @@
.spinner {
position: relative;
text-align: center;
margin-top: calc(var(--spacer) * 2);
margin-top: calc(var(--spacer) * 3);
line-height: 1.3;
}

View File

@ -1,6 +1,6 @@
import React from 'react'
import { render } from '@testing-library/react'
import Spinner from '../components/Spinner'
import Spinner from './Spinner'
describe('Spinner', () => {
it('renders without crashing', () => {

View File

@ -12,11 +12,11 @@ export interface AxiosResponseCustom extends AxiosResponse {
config: AxiosRequestConfigCustom
}
async function fetchRpc(url: string, method: string) {
async function fetchRpc(url: string, method: string, params?: any[]) {
try {
const response = await axios.post(url, {
const response: AxiosResponseCustom = await axios.post(url, {
method,
params: [],
params,
id: 1,
jsonrpc: '2.0'
})