explorer: Support filtering block transactions by account (#24787)
This commit is contained in:
parent
8f2680687d
commit
898b3529ff
|
@ -1,6 +1,8 @@
|
|||
import React from "react";
|
||||
import { BlockResponse, PublicKey } from "@solana/web3.js";
|
||||
import { Address } from "components/common/Address";
|
||||
import { Link } from "react-router-dom";
|
||||
import { clusterPath } from "utils/url";
|
||||
|
||||
type AccountStats = {
|
||||
reads: number;
|
||||
|
@ -9,7 +11,13 @@ type AccountStats = {
|
|||
|
||||
const PAGE_SIZE = 25;
|
||||
|
||||
export function BlockAccountsCard({ block }: { block: BlockResponse }) {
|
||||
export function BlockAccountsCard({
|
||||
block,
|
||||
blockSlot,
|
||||
}: {
|
||||
block: BlockResponse;
|
||||
blockSlot: number;
|
||||
}) {
|
||||
const [numDisplayed, setNumDisplayed] = React.useState(10);
|
||||
const totalTransactions = block.transactions.length;
|
||||
|
||||
|
@ -76,7 +84,16 @@ export function BlockAccountsCard({ block }: { block: BlockResponse }) {
|
|||
return (
|
||||
<tr key={address}>
|
||||
<td>
|
||||
<Address pubkey={new PublicKey(address)} link />
|
||||
<Link
|
||||
to={clusterPath(
|
||||
`/block/${blockSlot}`,
|
||||
new URLSearchParams(
|
||||
`accountFilter=${address}&filter=all`
|
||||
)
|
||||
)}
|
||||
>
|
||||
<Address pubkey={new PublicKey(address)} />
|
||||
</Link>
|
||||
</td>
|
||||
<td>{writes}</td>
|
||||
<td>{reads}</td>
|
||||
|
|
|
@ -18,11 +18,21 @@ import { parseProgramLogs } from "utils/program-logs";
|
|||
|
||||
const PAGE_SIZE = 25;
|
||||
|
||||
const useQueryFilter = (query: URLSearchParams): string => {
|
||||
const useQueryProgramFilter = (query: URLSearchParams): string => {
|
||||
const filter = query.get("filter");
|
||||
return filter || "";
|
||||
};
|
||||
|
||||
const useQueryAccountFilter = (query: URLSearchParams): PublicKey | null => {
|
||||
const filter = query.get("accountFilter");
|
||||
if (filter !== null) {
|
||||
try {
|
||||
return new PublicKey(filter);
|
||||
} catch {}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
type SortMode = "index" | "compute";
|
||||
const useQuerySort = (query: URLSearchParams): SortMode => {
|
||||
const sort = query.get("sort");
|
||||
|
@ -43,7 +53,8 @@ export function BlockHistoryCard({ block }: { block: BlockResponse }) {
|
|||
const [numDisplayed, setNumDisplayed] = React.useState(PAGE_SIZE);
|
||||
const [showDropdown, setDropdown] = React.useState(false);
|
||||
const query = useQuery();
|
||||
const filter = useQueryFilter(query);
|
||||
const programFilter = useQueryProgramFilter(query);
|
||||
const accountFilter = useQueryAccountFilter(query);
|
||||
const sortMode = useQuerySort(query);
|
||||
const { cluster } = useCluster();
|
||||
const location = useLocation();
|
||||
|
@ -107,26 +118,40 @@ export function BlockHistoryCard({ block }: { block: BlockResponse }) {
|
|||
|
||||
const filteredTransactions = React.useMemo(() => {
|
||||
const voteFilter = VOTE_PROGRAM_ID.toBase58();
|
||||
const filteredTxs = transactions.filter(({ invocations }) => {
|
||||
if (filter === ALL_TRANSACTIONS) {
|
||||
return true;
|
||||
} else if (filter === HIDE_VOTES) {
|
||||
// hide vote txs that don't invoke any other programs
|
||||
return !(invocations.size === 1 || invocations.has(voteFilter));
|
||||
}
|
||||
return invocations.has(filter);
|
||||
});
|
||||
const filteredTxs = transactions
|
||||
.filter(({ invocations }) => {
|
||||
if (programFilter === ALL_TRANSACTIONS) {
|
||||
return true;
|
||||
} else if (programFilter === HIDE_VOTES) {
|
||||
// hide vote txs that don't invoke any other programs
|
||||
return !(invocations.size === 1 || invocations.has(voteFilter));
|
||||
}
|
||||
return invocations.has(programFilter);
|
||||
})
|
||||
.filter(({ index }) => {
|
||||
if (accountFilter === null) {
|
||||
return true;
|
||||
}
|
||||
const tx = block.transactions[index].transaction;
|
||||
return tx.message.accountKeys.find((key) => key.equals(accountFilter));
|
||||
});
|
||||
|
||||
if (sortMode === "compute") {
|
||||
filteredTxs.sort((a, b) => b.computeUnits - a.computeUnits);
|
||||
}
|
||||
|
||||
return filteredTxs;
|
||||
}, [transactions, filter, sortMode]);
|
||||
}, [
|
||||
block.transactions,
|
||||
transactions,
|
||||
programFilter,
|
||||
accountFilter,
|
||||
sortMode,
|
||||
]);
|
||||
|
||||
if (filteredTransactions.length === 0) {
|
||||
const errorMessage =
|
||||
filter === ALL_TRANSACTIONS
|
||||
programFilter === ALL_TRANSACTIONS
|
||||
? "This block has no transactions"
|
||||
: "No transactions found with this filter";
|
||||
return <ErrorCard text={errorMessage} />;
|
||||
|
@ -144,7 +169,7 @@ export function BlockHistoryCard({ block }: { block: BlockResponse }) {
|
|||
<div className="card-header align-items-center">
|
||||
<h3 className="card-header-title">{title}</h3>
|
||||
<FilterDropdown
|
||||
filter={filter}
|
||||
filter={programFilter}
|
||||
toggle={() => setDropdown((show) => !show)}
|
||||
show={showDropdown}
|
||||
invokedPrograms={invokedPrograms}
|
||||
|
@ -152,6 +177,15 @@ export function BlockHistoryCard({ block }: { block: BlockResponse }) {
|
|||
></FilterDropdown>
|
||||
</div>
|
||||
|
||||
{accountFilter !== null && (
|
||||
<div className="card-body">
|
||||
Showing transactions which load account:
|
||||
<div className="d-inline-block ms-2">
|
||||
<Address pubkey={accountFilter} link />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="table-responsive mb-0">
|
||||
<table className="table table-sm table-nowrap card-table">
|
||||
<thead>
|
||||
|
@ -234,7 +268,7 @@ export function BlockHistoryCard({ block }: { block: BlockResponse }) {
|
|||
</table>
|
||||
</div>
|
||||
|
||||
{block.transactions.length > numDisplayed && (
|
||||
{filteredTransactions.length > numDisplayed && (
|
||||
<div className="card-footer">
|
||||
<button
|
||||
className="btn btn-primary w-100"
|
||||
|
|
|
@ -230,7 +230,9 @@ function MoreSection({
|
|||
</div>
|
||||
{tab === undefined && <BlockHistoryCard block={block} />}
|
||||
{tab === "rewards" && <BlockRewardsCard block={block} />}
|
||||
{tab === "accounts" && <BlockAccountsCard block={block} />}
|
||||
{tab === "accounts" && (
|
||||
<BlockAccountsCard block={block} blockSlot={slot} />
|
||||
)}
|
||||
{tab === "programs" && <BlockProgramsCard block={block} />}
|
||||
</>
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue