Skip to content

Commit

Permalink
explorer: Add tabs for block program and account stats (#15702)
Browse files Browse the repository at this point in the history
  • Loading branch information
jstarry authored Mar 4, 2021
1 parent f30e358 commit e16596b
Show file tree
Hide file tree
Showing 9 changed files with 349 additions and 15 deletions.
6 changes: 4 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,10 @@ function App() {
/>
<Route
exact
path={"/block/:id"}
render={({ match }) => <BlockDetailsPage slot={match.params.id} />}
path={["/block/:id", "/block/:id/:tab"]}
render={({ match }) => (
<BlockDetailsPage slot={match.params.id} tab={match.params.tab} />
)}
/>
<Route
exact
Expand Down
10 changes: 10 additions & 0 deletions src/components/account/UpgradeableProgramSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
ProgramDataAccountInfo,
} from "validators/accounts/upgradeable-program";
import { Slot } from "components/common/Slot";
import { addressLabel } from "utils/tx";
import { useCluster } from "providers/cluster";

export function UpgradeableProgramSection({
account,
Expand All @@ -19,6 +21,8 @@ export function UpgradeableProgramSection({
programData: ProgramDataAccountInfo;
}) {
const refresh = useFetchAccountInfo();
const { cluster } = useCluster();
const label = addressLabel(account.pubkey.toBase58(), cluster);
return (
<div className="card">
<div className="card-header">
Expand All @@ -41,6 +45,12 @@ export function UpgradeableProgramSection({
<Address pubkey={account.pubkey} alignRight raw />
</td>
</tr>
{label && (
<tr>
<td>Address Label</td>
<td className="text-lg-right">{label}</td>
</tr>
)}
<tr>
<td>Balance (SOL)</td>
<td className="text-lg-right text-uppercase">
Expand Down
110 changes: 110 additions & 0 deletions src/components/block/BlockAccountsCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import React from "react";
import { ConfirmedBlock, PublicKey } from "@solana/web3.js";
import { Address } from "components/common/Address";

type AccountStats = {
reads: number;
writes: number;
};

const PAGE_SIZE = 25;

export function BlockAccountsCard({ block }: { block: ConfirmedBlock }) {
const [numDisplayed, setNumDisplayed] = React.useState(10);
const totalTransactions = block.transactions.length;

const accountStats = React.useMemo(() => {
const statsMap = new Map<string, AccountStats>();
block.transactions.forEach((tx) => {
const txSet = new Map<string, boolean>();
tx.transaction.instructions.forEach((ix) => {
ix.keys.forEach((key) => {
const address = key.pubkey.toBase58();
txSet.set(address, key.isWritable);
});
});

txSet.forEach((isWritable, address) => {
const stats = statsMap.get(address) || { reads: 0, writes: 0 };
if (isWritable) {
stats.writes++;
} else {
stats.reads++;
}
statsMap.set(address, stats);
});
});

const accountEntries = [];
for (let entry of statsMap) {
accountEntries.push(entry);
}

accountEntries.sort((a, b) => {
const aCount = a[1].reads + a[1].writes;
const bCount = b[1].reads + b[1].writes;
if (aCount < bCount) return 1;
if (aCount > bCount) return -1;
return 0;
});

return accountEntries;
}, [block]);

return (
<div className="card">
<div className="card-header align-items-center">
<h3 className="card-header-title">Block Account Usage</h3>
</div>

<div className="table-responsive mb-0">
<table className="table table-sm table-nowrap card-table">
<thead>
<tr>
<th className="text-muted">Account</th>
<th className="text-muted">Read-Write Count</th>
<th className="text-muted">Read-Only Count</th>
<th className="text-muted">Total Count</th>
<th className="text-muted">% of Transactions</th>
</tr>
</thead>
<tbody>
{accountStats
.slice(0, numDisplayed)
.map(([address, { writes, reads }]) => {
return (
<tr key={address}>
<td>
<Address pubkey={new PublicKey(address)} link />
</td>
<td>{writes}</td>
<td>{reads}</td>
<td>{writes + reads}</td>
<td>
{((100 * (writes + reads)) / totalTransactions).toFixed(
2
)}
%
</td>
</tr>
);
})}
</tbody>
</table>
</div>

{accountStats.length > numDisplayed && (
<div className="card-footer">
<button
className="btn btn-primary w-100"
onClick={() =>
setNumDisplayed((displayed) => displayed + PAGE_SIZE)
}
>
Load More
</button>
</div>
)}
</div>
);
}
19 changes: 18 additions & 1 deletion src/components/block/BlockHistoryCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import { ErrorCard } from "components/common/ErrorCard";
import { Signature } from "components/common/Signature";
import bs58 from "bs58";

const PAGE_SIZE = 25;

export function BlockHistoryCard({ block }: { block: ConfirmedBlock }) {
const [numDisplayed, setNumDisplayed] = React.useState(PAGE_SIZE);

if (block.transactions.length === 0) {
return <ErrorCard text="This block has no transactions" />;
}
Expand All @@ -24,7 +28,7 @@ export function BlockHistoryCard({ block }: { block: ConfirmedBlock }) {
</tr>
</thead>
<tbody className="list">
{block.transactions.map((tx, i) => {
{block.transactions.slice(0, numDisplayed).map((tx, i) => {
let statusText;
let statusClass;
let signature: React.ReactNode;
Expand Down Expand Up @@ -60,6 +64,19 @@ export function BlockHistoryCard({ block }: { block: ConfirmedBlock }) {
</tbody>
</table>
</div>

{block.transactions.length > numDisplayed && (
<div className="card-footer">
<button
className="btn btn-primary w-100"
onClick={() =>
setNumDisplayed((displayed) => displayed + PAGE_SIZE)
}
>
Load More
</button>
</div>
)}
</div>
);
}
99 changes: 92 additions & 7 deletions src/components/block/BlockOverviewCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,19 @@ import { Slot } from "components/common/Slot";
import { ClusterStatus, useCluster } from "providers/cluster";
import { BlockHistoryCard } from "./BlockHistoryCard";
import { BlockRewardsCard } from "./BlockRewardsCard";
import { ConfirmedBlock } from "@solana/web3.js";
import { NavLink } from "react-router-dom";
import { clusterPath } from "utils/url";
import { BlockProgramsCard } from "./BlockProgramsCard";
import { BlockAccountsCard } from "./BlockAccountsCard";

export function BlockOverviewCard({ slot }: { slot: number }) {
export function BlockOverviewCard({
slot,
tab,
}: {
slot: number;
tab?: string;
}) {
const confirmedBlock = useBlock(slot);
const fetchBlock = useFetchBlock();
const { status } = useCluster();
Expand Down Expand Up @@ -46,29 +57,103 @@ export function BlockOverviewCard({ slot }: { slot: number }) {
<Slot slot={slot} />
</td>
</tr>
<tr>
<td className="w-100">Blockhash</td>
<td className="text-lg-right text-monospace">
<span>{block.blockhash}</span>
</td>
</tr>
<tr>
<td className="w-100">Parent Slot</td>
<td className="text-lg-right text-monospace">
<Slot slot={block.parentSlot} link />
</td>
</tr>
<tr>
<td className="w-100">Blockhash</td>
<td className="w-100">Parent Blockhash</td>
<td className="text-lg-right text-monospace">
<span>{block.blockhash}</span>
<span>{block.previousBlockhash}</span>
</td>
</tr>
<tr>
<td className="w-100">Previous Blockhash</td>
<td className="w-100">Total Transactions</td>
<td className="text-lg-right text-monospace">
<span>{block.previousBlockhash}</span>
<span>{block.transactions.length}</span>
</td>
</tr>
</TableCardBody>
</div>

<BlockRewardsCard block={block} />
<BlockHistoryCard block={block} />
<MoreSection block={block} slot={slot} tab={tab} />
</>
);
}

const TABS: Tab[] = [
{
slug: "history",
title: "Transactions",
path: "",
},
{
slug: "rewards",
title: "Rewards",
path: "/rewards",
},
{
slug: "programs",
title: "Programs",
path: "/programs",
},
{
slug: "accounts",
title: "Accounts",
path: "/accounts",
},
];

type MoreTabs = "history" | "rewards" | "programs" | "accounts";

type Tab = {
slug: MoreTabs;
title: string;
path: string;
};

function MoreSection({
slot,
block,
tab,
}: {
slot: number;
block: ConfirmedBlock;
tab?: string;
}) {
return (
<>
<div className="container">
<div className="header">
<div className="header-body pt-0">
<ul className="nav nav-tabs nav-overflow header-tabs">
{TABS.map(({ title, slug, path }) => (
<li key={slug} className="nav-item">
<NavLink
className="nav-link"
to={clusterPath(`/block/${slot}${path}`)}
exact
>
{title}
</NavLink>
</li>
))}
</ul>
</div>
</div>
</div>
{tab === undefined && <BlockHistoryCard block={block} />}
{tab === "rewards" && <BlockRewardsCard block={block} />}
{tab === "accounts" && <BlockAccountsCard block={block} />}
{tab === "programs" && <BlockProgramsCard block={block} />}
</>
);
}
Loading

0 comments on commit e16596b

Please sign in to comment.