Merge branch 'feature/london' into develop

This commit is contained in:
Willian Mitsuda 2021-07-28 23:41:12 -03:00
commit 65dd8b5e4b
11 changed files with 536 additions and 232 deletions

View File

@ -1,92 +1,35 @@
import React, { useEffect, useState, useMemo, useContext } from "react"; import React, { useEffect, useMemo, useContext } from "react";
import { useParams, NavLink } from "react-router-dom"; import { useParams, NavLink } from "react-router-dom";
import { ethers, BigNumber } from "ethers"; import { BigNumber, ethers } from "ethers";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faBurn } from "@fortawesome/free-solid-svg-icons";
import StandardFrame from "./StandardFrame"; import StandardFrame from "./StandardFrame";
import StandardSubtitle from "./StandardSubtitle"; import StandardSubtitle from "./StandardSubtitle";
import NavBlock from "./block/NavBlock"; import NavBlock from "./block/NavBlock";
import ContentFrame from "./ContentFrame"; import ContentFrame from "./ContentFrame";
import InfoRow from "./components/InfoRow";
import Timestamp from "./components/Timestamp"; import Timestamp from "./components/Timestamp";
import GasValue from "./components/GasValue"; import GasValue from "./components/GasValue";
import PercentageBar from "./components/PercentageBar";
import BlockLink from "./components/BlockLink"; import BlockLink from "./components/BlockLink";
import DecoratedAddressLink from "./components/DecoratedAddressLink"; import DecoratedAddressLink from "./components/DecoratedAddressLink";
import TransactionValue from "./components/TransactionValue"; import TransactionValue from "./components/TransactionValue";
import FormattedBalance from "./components/FormattedBalance";
import HexValue from "./components/HexValue"; import HexValue from "./components/HexValue";
import { RuntimeContext } from "./useRuntime"; import { RuntimeContext } from "./useRuntime";
import { useLatestBlockNumber } from "./useLatestBlock"; import { useLatestBlockNumber } from "./useLatestBlock";
import { blockTxsURL } from "./url"; import { blockTxsURL } from "./url";
import { useBlockData } from "./useErigonHooks";
type BlockParams = { type BlockParams = {
blockNumberOrHash: string; blockNumberOrHash: string;
}; };
interface ExtendedBlock extends ethers.providers.Block {
blockReward: BigNumber;
unclesReward: BigNumber;
feeReward: BigNumber;
size: number;
sha3Uncles: string;
stateRoot: string;
totalDifficulty: BigNumber;
}
const Block: React.FC = () => { const Block: React.FC = () => {
const { provider } = useContext(RuntimeContext); const { provider } = useContext(RuntimeContext);
const params = useParams<BlockParams>(); const params = useParams<BlockParams>();
const [block, setBlock] = useState<ExtendedBlock>(); const block = useBlockData(provider, params.blockNumberOrHash);
useEffect(() => {
if (!provider) {
return;
}
const readBlock = async () => {
let blockPromise: Promise<any>;
if (ethers.utils.isHexString(params.blockNumberOrHash, 32)) {
blockPromise = provider.send("eth_getBlockByHash", [
params.blockNumberOrHash,
false,
]);
} else {
blockPromise = provider.send("eth_getBlockByNumber", [
params.blockNumberOrHash,
false,
]);
}
const [_rawBlock, _rawIssuance, _rawReceipts] = await Promise.all([
blockPromise,
provider.send("erigon_issuance", [params.blockNumberOrHash]),
provider.send("eth_getBlockReceipts", [params.blockNumberOrHash]),
]);
const receipts = (_rawReceipts as any[]).map((r) =>
provider.formatter.receipt(r)
);
const fees = receipts.reduce(
(acc, r) => acc.add(r.effectiveGasPrice.mul(r.gasUsed)),
BigNumber.from(0)
);
const _block = provider.formatter.block(_rawBlock);
const extBlock: ExtendedBlock = {
blockReward: provider.formatter.bigNumber(
_rawIssuance.blockReward ?? 0
),
unclesReward: provider.formatter.bigNumber(
_rawIssuance.uncleReward ?? 0
),
feeReward: fees,
size: provider.formatter.number(_rawBlock.size),
sha3Uncles: _rawBlock.sha3Uncles,
stateRoot: _rawBlock.stateRoot,
totalDifficulty: provider.formatter.bigNumber(
_rawBlock.totalDifficulty
),
..._block,
};
setBlock(extBlock);
};
readBlock();
}, [provider, params.blockNumberOrHash]);
useEffect(() => { useEffect(() => {
if (block) { if (block) {
document.title = `Block #${block.number} | Otterscan`; document.title = `Block #${block.number} | Otterscan`;
@ -101,6 +44,11 @@ const Block: React.FC = () => {
console.error(err); console.error(err);
} }
}, [block]); }, [block]);
const burntFees =
block?.baseFeePerGas && block.baseFeePerGas.mul(block.gasUsed);
const netFeeReward = block && block.feeReward.sub(burntFees ?? 0);
const gasUsedPerc =
block && block.gasUsed.mul(10000).div(block.gasLimit).toNumber() / 100;
const latestBlockNumber = useLatestBlockNumber(provider); const latestBlockNumber = useLatestBlockNumber(provider);
@ -143,38 +91,77 @@ const Block: React.FC = () => {
<DecoratedAddressLink address={block.miner} miner /> <DecoratedAddressLink address={block.miner} miner />
</InfoRow> </InfoRow>
<InfoRow title="Block Reward"> <InfoRow title="Block Reward">
<TransactionValue value={block.blockReward.add(block.feeReward)} /> <TransactionValue
value={block.blockReward.add(netFeeReward ?? 0)}
/>
{!block.feeReward.isZero() && ( {!block.feeReward.isZero() && (
<> <>
{" "} {" "}
(<TransactionValue value={block.blockReward} hideUnit /> +{" "} (<TransactionValue value={block.blockReward} hideUnit /> +{" "}
<TransactionValue value={block.feeReward} hideUnit />) <TransactionValue
value={netFeeReward ?? BigNumber.from(0)}
hideUnit
/>
)
</> </>
)} )}
</InfoRow> </InfoRow>
<InfoRow title="Uncles Reward"> <InfoRow title="Uncles Reward">
<TransactionValue value={block.unclesReward} /> <TransactionValue value={block.unclesReward} />
</InfoRow> </InfoRow>
<InfoRow title="Difficult">
{ethers.utils.commify(block.difficulty)}
</InfoRow>
<InfoRow title="Total Difficult">
{ethers.utils.commify(block.totalDifficulty.toString())}
</InfoRow>
<InfoRow title="Size"> <InfoRow title="Size">
{ethers.utils.commify(block.size)} bytes {ethers.utils.commify(block.size)} bytes
</InfoRow> </InfoRow>
<InfoRow title="Gas Used"> {block.baseFeePerGas && (
<GasValue value={block.gasUsed} /> <InfoRow title="Base Fee">
<span>
<FormattedBalance value={block.baseFeePerGas} decimals={9} />{" "}
Gwei (
<FormattedBalance
value={block.baseFeePerGas}
decimals={0}
/>{" "}
wei)
</span>
</InfoRow> </InfoRow>
<InfoRow title="Gas Limit"> )}
{burntFees && (
<InfoRow title="Burnt Fees">
<div className="flex items-baseline space-x-1">
<span className="flex space-x-1 text-orange-500">
<span title="Burnt fees">
<FontAwesomeIcon icon={faBurn} size="1x" />
</span>
<span>
<span className="line-through">
<FormattedBalance value={burntFees} />
</span>{" "}
Ether
</span>
</span>
</div>
</InfoRow>
)}
<InfoRow title="Gas Used/Limit">
<div className="flex space-x-3 items-baseline">
<div>
<GasValue value={block.gasUsed} /> /{" "}
<GasValue value={block.gasLimit} /> <GasValue value={block.gasLimit} />
</div>
<PercentageBar perc={gasUsedPerc!} />
</div>
</InfoRow> </InfoRow>
<InfoRow title="Extra Data"> <InfoRow title="Extra Data">
{extraStr} (Hex:{" "} {extraStr} (Hex:{" "}
<span className="font-data">{block.extraData}</span>) <span className="font-data">{block.extraData}</span>)
</InfoRow> </InfoRow>
<InfoRow title="Ether Price">N/A</InfoRow> <InfoRow title="Ether Price">N/A</InfoRow>
<InfoRow title="Difficult">
{ethers.utils.commify(block.difficulty)}
</InfoRow>
<InfoRow title="Total Difficult">
{ethers.utils.commify(block.totalDifficulty.toString())}
</InfoRow>
<InfoRow title="Hash"> <InfoRow title="Hash">
<HexValue value={block.hash} /> <HexValue value={block.hash} />
</InfoRow> </InfoRow>
@ -196,15 +183,4 @@ const Block: React.FC = () => {
); );
}; };
type InfoRowProps = {
title: string;
};
const InfoRow: React.FC<InfoRowProps> = ({ title, children }) => (
<div className="grid grid-cols-4 py-4 text-sm">
<div>{title}:</div>
<div className="col-span-3">{children}</div>
</div>
);
export default React.memo(Block); export default React.memo(Block);

View File

@ -92,12 +92,14 @@ const Transaction: React.FC = () => {
value: _response.value, value: _response.value,
tokenTransfers, tokenTransfers,
tokenMetas, tokenMetas,
type: _response.type ?? 0,
fee: _response.gasPrice!.mul(_receipt.gasUsed), fee: _response.gasPrice!.mul(_receipt.gasUsed),
blockBaseFeePerGas: _block.baseFeePerGas,
maxFeePerGas: _response.maxFeePerGas,
maxPriorityFeePerGas: _response.maxPriorityFeePerGas,
gasPrice: _response.gasPrice!, gasPrice: _response.gasPrice!,
gasLimit: _response.gasLimit,
gasUsed: _receipt.gasUsed, gasUsed: _receipt.gasUsed,
gasUsedPerc: gasLimit: _response.gasLimit,
_receipt.gasUsed.toNumber() / _response.gasLimit.toNumber(),
nonce: _response.nonce, nonce: _response.nonce,
data: _response.data, data: _response.data,
logs: _receipt.logs, logs: _receipt.logs,

View File

@ -0,0 +1,23 @@
import React from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faExternalLinkAlt } from "@fortawesome/free-solid-svg-icons";
type ExternalLinkProps = {
href: string;
};
const ExternalLink: React.FC<ExternalLinkProps> = ({ href, children }) => (
<a
className="text-link-blue hover:text-link-blue-hover"
href={href}
target="_blank"
rel="noopener noreferrer"
>
<span className="inline-flex items-baseline space-x-1">
<span>{children}</span>
<FontAwesomeIcon icon={faExternalLinkAlt} size="sm" />
</span>
</a>
);
export default ExternalLink;

View File

@ -1,7 +1,7 @@
import React from "react"; import React from "react";
type InfoRowProps = React.PropsWithChildren<{ type InfoRowProps = React.PropsWithChildren<{
title: string; title: React.ReactNode;
}>; }>;
const InfoRow: React.FC<InfoRowProps> = ({ title, children }) => ( const InfoRow: React.FC<InfoRowProps> = ({ title, children }) => (

View File

@ -0,0 +1,21 @@
import React from "react";
type PercentageBarProps = {
perc: number;
};
const PercentageBar: React.FC<PercentageBarProps> = ({ perc }) => (
<div className="self-center w-40 border rounded border-gray-200">
<div className="w-full h-5 rounded bg-gradient-to-r from-red-400 via-yellow-300 to-green-400 relative">
<div
className="absolute top-0 right-0 bg-white h-full rounded-r"
style={{ width: `${100 - perc}%` }}
></div>
<div className="w-full h-full absolute flex mix-blend-multiply text-sans text-gray-600">
<span className="m-auto">{perc}%</span>
</div>
</div>
</div>
);
export default React.memo(PercentageBar);

View File

@ -0,0 +1,34 @@
import React from "react";
type PercentageGaugeProps = {
perc: number;
bgFull: string;
bgPerc: string;
textPerc: string;
};
const PercentageGauge: React.FC<PercentageGaugeProps> = ({
perc,
bgFull,
bgPerc,
textPerc,
}) => (
<div className="w-60 h-6 border-l-2 border-gray-400 relative">
<div className="flex absolute w-full h-full">
<div className={`my-auto h-5 rounded-r-lg w-full ${bgFull}`}></div>
</div>
<div className="flex absolute w-full h-full">
<div
className={`my-auto h-5 rounded-r-lg ${bgPerc}`}
style={{ width: `${perc}%` }}
></div>
</div>
<div
className={`flex absolute w-full h-full mix-blend-multiply text-sans ${textPerc}`}
>
<span className="m-auto">{perc}%</span>
</div>
</div>
);
export default React.memo(PercentageGauge);

View File

@ -0,0 +1,39 @@
import React from "react";
import ExternalLink from "./ExternalLink";
type TransactionTypeProps = {
type: number;
};
const TransactionType: React.FC<TransactionTypeProps> = ({ type }) => {
let description: React.ReactNode;
switch (type) {
case 0:
description = "legacy";
break;
case 1:
description = (
<ExternalLink href="https://eips.ethereum.org/EIPS/eip-2930">
EIP-2930
</ExternalLink>
);
break;
case 2:
description = (
<ExternalLink href="https://eips.ethereum.org/EIPS/eip-1559">
EIP-1559
</ExternalLink>
);
break;
default:
description = "unknown";
}
return (
<span>
{type} <span className="font-bold">({description})</span>
</span>
);
};
export default React.memo(TransactionType);

View File

@ -15,10 +15,14 @@ import Copy from "../components/Copy";
import Timestamp from "../components/Timestamp"; import Timestamp from "../components/Timestamp";
import InternalTransactionOperation from "../components/InternalTransactionOperation"; import InternalTransactionOperation from "../components/InternalTransactionOperation";
import MethodName from "../components/MethodName"; import MethodName from "../components/MethodName";
import TransactionType from "../components/TransactionType";
import RewardSplit from "./RewardSplit";
import GasValue from "../components/GasValue"; import GasValue from "../components/GasValue";
import FormattedBalance from "../components/FormattedBalance"; import FormattedBalance from "../components/FormattedBalance";
import TokenTransferItem from "../TokenTransferItem"; import TokenTransferItem from "../TokenTransferItem";
import { TransactionData, InternalOperation } from "../types"; import { TransactionData, InternalOperation } from "../types";
import PercentageBar from "../components/PercentageBar";
import ExternalLink from "../components/ExternalLink";
type DetailsProps = { type DetailsProps = {
txData: TransactionData; txData: TransactionData;
@ -30,7 +34,12 @@ const Details: React.FC<DetailsProps> = ({
txData, txData,
internalOps, internalOps,
sendsEthToMiner, sendsEthToMiner,
}) => ( }) => {
const hasEIP1559 =
txData.blockBaseFeePerGas !== undefined &&
txData.blockBaseFeePerGas !== null;
return (
<ContentFrame tabs> <ContentFrame tabs>
<InfoRow title="Transaction Hash"> <InfoRow title="Transaction Hash">
<div className="flex items-baseline space-x-2"> <div className="flex items-baseline space-x-2">
@ -130,9 +139,43 @@ const Details: React.FC<DetailsProps> = ({
{ethers.utils.formatEther(txData.value)} Ether {ethers.utils.formatEther(txData.value)} Ether
</span> </span>
</InfoRow> </InfoRow>
<InfoRow title="Transaction Fee"> <InfoRow
<FormattedBalance value={txData.fee} /> Ether title={
<>
Type (
<ExternalLink href="https://eips.ethereum.org/EIPS/eip-2718">
EIP-2718
</ExternalLink>
)
</>
}
>
<TransactionType type={txData.type} />
</InfoRow> </InfoRow>
{txData.type === 2 && (
<>
<InfoRow title="Max Priority Fee Per Gas">
<span>
<FormattedBalance value={txData.maxPriorityFeePerGas!} /> Ether (
<FormattedBalance
value={txData.maxPriorityFeePerGas!}
decimals={9}
/>{" "}
Gwei)
</span>
</InfoRow>
<InfoRow title="Max Fee Per Gas">
<span>
<FormattedBalance value={txData.maxFeePerGas!} /> Ether (
<FormattedBalance
value={txData.maxFeePerGas!}
decimals={9}
/>{" "}
Gwei)
</span>
</InfoRow>
</>
)}
<InfoRow title="Gas Price"> <InfoRow title="Gas Price">
<div className="flex items-baseline space-x-1"> <div className="flex items-baseline space-x-1">
<span> <span>
@ -146,14 +189,43 @@ const Details: React.FC<DetailsProps> = ({
)} )}
</div> </div>
</InfoRow> </InfoRow>
<InfoRow title="Ether Price">N/A</InfoRow> <InfoRow title="Gas Used/Limit">
<InfoRow title="Gas Limit"> <div className="flex space-x-3 items-baseline">
<div>
<GasValue value={txData.gasUsed} /> /{" "}
<GasValue value={txData.gasLimit} /> <GasValue value={txData.gasLimit} />
</div>
<PercentageBar
perc={
Math.round(
(txData.gasUsed.toNumber() / txData.gasLimit.toNumber()) * 10000
) / 100
}
/>
</div>
</InfoRow> </InfoRow>
<InfoRow title="Gas Used by Transaction"> {hasEIP1559 && (
<GasValue value={txData.gasUsed} /> ( <InfoRow title="Block Base Fee">
{(txData.gasUsedPerc * 100).toFixed(2)}%) <span>
<FormattedBalance value={txData.blockBaseFeePerGas!} decimals={9} />{" "}
Gwei (
<FormattedBalance
value={txData.blockBaseFeePerGas!}
decimals={0}
/>{" "}
wei)
</span>
</InfoRow> </InfoRow>
)}
<InfoRow title="Transaction Fee">
<div className="space-y-3">
<div>
<FormattedBalance value={txData.fee} /> Ether
</div>
{hasEIP1559 && <RewardSplit txData={txData} />}
</div>
</InfoRow>
<InfoRow title="Ether Price">N/A</InfoRow>
<InfoRow title="Nonce">{txData.nonce}</InfoRow> <InfoRow title="Nonce">{txData.nonce}</InfoRow>
<InfoRow title="Position in Block"> <InfoRow title="Position in Block">
<span className="rounded px-2 py-1 bg-gray-100 text-gray-500 text-xs"> <span className="rounded px-2 py-1 bg-gray-100 text-gray-500 text-xs">
@ -169,5 +241,6 @@ const Details: React.FC<DetailsProps> = ({
</InfoRow> </InfoRow>
</ContentFrame> </ContentFrame>
); );
};
export default React.memo(Details); export default React.memo(Details);

View File

@ -0,0 +1,62 @@
import React from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faBurn, faCoins } from "@fortawesome/free-solid-svg-icons";
import FormattedBalance from "../components/FormattedBalance";
import { TransactionData } from "../types";
import PercentageGauge from "../components/PercentageGauge";
type RewardSplitProps = {
txData: TransactionData;
};
const RewardSplit: React.FC<RewardSplitProps> = ({ txData }) => {
const burntFees = txData.blockBaseFeePerGas!.mul(txData.gasUsed);
const minerReward = txData.gasPrice.mul(txData.gasUsed).sub(burntFees);
const burntPerc =
burntFees.mul(10000).div(txData.gasPrice.mul(txData.gasUsed)).toNumber() /
100;
return (
<div className="inline-block">
<div className="grid grid-cols-2 gap-x-2 gap-y-1 items-center text-sm">
<PercentageGauge
perc={burntPerc}
bgFull="bg-orange-100"
bgPerc="bg-orange-500"
textPerc="text-orange-800"
/>
<div className="flex items-baseline space-x-1">
<span className="flex space-x-1 text-orange-500">
<span title="Burnt fees">
<FontAwesomeIcon icon={faBurn} size="1x" />
</span>
<span>
<span className="line-through">
<FormattedBalance value={burntFees} />
</span>{" "}
Ether
</span>
</span>
</div>
<PercentageGauge
perc={100 - burntPerc}
bgFull="bg-yellow-100"
bgPerc="bg-yellow-300"
textPerc="text-yellow-700"
/>
<div className="flex items-baseline space-x-1">
<span className="flex space-x-1">
<span className="text-yellow-300" title="Miner fees">
<FontAwesomeIcon icon={faCoins} size="1x" />
</span>
<span>
<FormattedBalance value={minerReward} /> Ether
</span>
</span>
</div>
</div>
</div>
);
};
export default React.memo(RewardSplit);

View File

@ -49,11 +49,14 @@ export type TransactionData = {
value: BigNumber; value: BigNumber;
tokenTransfers: TokenTransfer[]; tokenTransfers: TokenTransfer[];
tokenMetas: TokenMetas; tokenMetas: TokenMetas;
type: number;
maxFeePerGas?: BigNumber | undefined;
maxPriorityFeePerGas?: BigNumber | undefined;
fee: BigNumber; fee: BigNumber;
blockBaseFeePerGas?: BigNumber | undefined | null;
gasPrice: BigNumber; gasPrice: BigNumber;
gasLimit: BigNumber;
gasUsed: BigNumber; gasUsed: BigNumber;
gasUsedPerc: number; gasLimit: BigNumber;
nonce: number; nonce: number;
data: string; data: string;
logs: ethers.providers.Log[]; logs: ethers.providers.Log[];

View File

@ -1,8 +1,79 @@
import { ethers } from "ethers"; import { ethers, BigNumber } from "ethers";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { getInternalOperations } from "./nodeFunctions"; import { getInternalOperations } from "./nodeFunctions";
import { TransactionData, InternalOperation } from "./types"; import { TransactionData, InternalOperation } from "./types";
export interface ExtendedBlock extends ethers.providers.Block {
blockReward: BigNumber;
unclesReward: BigNumber;
feeReward: BigNumber;
size: number;
sha3Uncles: string;
stateRoot: string;
totalDifficulty: BigNumber;
}
export const useBlockData = (
provider: ethers.providers.JsonRpcProvider | undefined,
blockNumberOrHash: string
) => {
const [block, setBlock] = useState<ExtendedBlock>();
useEffect(() => {
if (!provider) {
return;
}
const readBlock = async () => {
let blockPromise: Promise<any>;
if (ethers.utils.isHexString(blockNumberOrHash, 32)) {
blockPromise = provider.send("eth_getBlockByHash", [
blockNumberOrHash,
false,
]);
} else {
blockPromise = provider.send("eth_getBlockByNumber", [
blockNumberOrHash,
false,
]);
}
const [_rawBlock, _rawIssuance, _rawReceipts] = await Promise.all([
blockPromise,
provider.send("erigon_issuance", [blockNumberOrHash]),
provider.send("eth_getBlockReceipts", [blockNumberOrHash]),
]);
const receipts = (_rawReceipts as any[]).map((r) =>
provider.formatter.receipt(r)
);
const fees = receipts.reduce(
(acc, r) => acc.add(r.effectiveGasPrice.mul(r.gasUsed)),
BigNumber.from(0)
);
const _block = provider.formatter.block(_rawBlock);
const extBlock: ExtendedBlock = {
blockReward: provider.formatter.bigNumber(
_rawIssuance.blockReward ?? 0
),
unclesReward: provider.formatter.bigNumber(
_rawIssuance.uncleReward ?? 0
),
feeReward: fees,
size: provider.formatter.number(_rawBlock.size),
sha3Uncles: _rawBlock.sha3Uncles,
stateRoot: _rawBlock.stateRoot,
totalDifficulty: provider.formatter.bigNumber(
_rawBlock.totalDifficulty
),
..._block,
};
setBlock(extBlock);
};
readBlock();
}, [provider, blockNumberOrHash]);
return block;
};
export const useInternalOperations = ( export const useInternalOperations = (
provider: ethers.providers.JsonRpcProvider | undefined, provider: ethers.providers.JsonRpcProvider | undefined,
txData: TransactionData | undefined txData: TransactionData | undefined