explorer: Display correct list of block programs (#17489)
This commit is contained in:
parent
9a5330b7eb
commit
179856c13a
|
@ -1,5 +1,5 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { ConfirmedBlock, PublicKey } from "@solana/web3.js";
|
import { BlockResponse, PublicKey } from "@solana/web3.js";
|
||||||
import { Address } from "components/common/Address";
|
import { Address } from "components/common/Address";
|
||||||
|
|
||||||
type AccountStats = {
|
type AccountStats = {
|
||||||
|
@ -9,18 +9,19 @@ type AccountStats = {
|
||||||
|
|
||||||
const PAGE_SIZE = 25;
|
const PAGE_SIZE = 25;
|
||||||
|
|
||||||
export function BlockAccountsCard({ block }: { block: ConfirmedBlock }) {
|
export function BlockAccountsCard({ block }: { block: BlockResponse }) {
|
||||||
const [numDisplayed, setNumDisplayed] = React.useState(10);
|
const [numDisplayed, setNumDisplayed] = React.useState(10);
|
||||||
const totalTransactions = block.transactions.length;
|
const totalTransactions = block.transactions.length;
|
||||||
|
|
||||||
const accountStats = React.useMemo(() => {
|
const accountStats = React.useMemo(() => {
|
||||||
const statsMap = new Map<string, AccountStats>();
|
const statsMap = new Map<string, AccountStats>();
|
||||||
block.transactions.forEach((tx) => {
|
block.transactions.forEach((tx) => {
|
||||||
|
const message = tx.transaction.message;
|
||||||
const txSet = new Map<string, boolean>();
|
const txSet = new Map<string, boolean>();
|
||||||
tx.transaction.instructions.forEach((ix) => {
|
message.instructions.forEach((ix) => {
|
||||||
ix.keys.forEach((key) => {
|
ix.accounts.forEach((index) => {
|
||||||
const address = key.pubkey.toBase58();
|
const address = message.accountKeys[index].toBase58();
|
||||||
txSet.set(address, key.isWritable);
|
txSet.set(address, message.isAccountWritable(index));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { ConfirmedBlock } from "@solana/web3.js";
|
import { BlockResponse } from "@solana/web3.js";
|
||||||
import { ErrorCard } from "components/common/ErrorCard";
|
import { ErrorCard } from "components/common/ErrorCard";
|
||||||
import { Signature } from "components/common/Signature";
|
import { Signature } from "components/common/Signature";
|
||||||
import bs58 from "bs58";
|
|
||||||
|
|
||||||
const PAGE_SIZE = 25;
|
const PAGE_SIZE = 25;
|
||||||
|
|
||||||
export function BlockHistoryCard({ block }: { block: ConfirmedBlock }) {
|
export function BlockHistoryCard({ block }: { block: BlockResponse }) {
|
||||||
const [numDisplayed, setNumDisplayed] = React.useState(PAGE_SIZE);
|
const [numDisplayed, setNumDisplayed] = React.useState(PAGE_SIZE);
|
||||||
|
|
||||||
if (block.transactions.length === 0) {
|
if (block.transactions.length === 0) {
|
||||||
|
@ -32,7 +31,7 @@ export function BlockHistoryCard({ block }: { block: ConfirmedBlock }) {
|
||||||
let statusText;
|
let statusText;
|
||||||
let statusClass;
|
let statusClass;
|
||||||
let signature: React.ReactNode;
|
let signature: React.ReactNode;
|
||||||
if (tx.meta?.err || !tx.transaction.signature) {
|
if (tx.meta?.err || tx.transaction.signatures.length === 0) {
|
||||||
statusClass = "warning";
|
statusClass = "warning";
|
||||||
statusText = "Failed";
|
statusText = "Failed";
|
||||||
} else {
|
} else {
|
||||||
|
@ -40,12 +39,9 @@ export function BlockHistoryCard({ block }: { block: ConfirmedBlock }) {
|
||||||
statusText = "Success";
|
statusText = "Success";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tx.transaction.signature) {
|
if (tx.transaction.signatures.length > 0) {
|
||||||
signature = (
|
signature = (
|
||||||
<Signature
|
<Signature signature={tx.transaction.signatures[0]} link />
|
||||||
signature={bs58.encode(tx.transaction.signature)}
|
|
||||||
link
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { Slot } from "components/common/Slot";
|
||||||
import { ClusterStatus, useCluster } from "providers/cluster";
|
import { ClusterStatus, useCluster } from "providers/cluster";
|
||||||
import { BlockHistoryCard } from "./BlockHistoryCard";
|
import { BlockHistoryCard } from "./BlockHistoryCard";
|
||||||
import { BlockRewardsCard } from "./BlockRewardsCard";
|
import { BlockRewardsCard } from "./BlockRewardsCard";
|
||||||
import { ConfirmedBlock } from "@solana/web3.js";
|
import { BlockResponse } from "@solana/web3.js";
|
||||||
import { NavLink } from "react-router-dom";
|
import { NavLink } from "react-router-dom";
|
||||||
import { clusterPath } from "utils/url";
|
import { clusterPath } from "utils/url";
|
||||||
import { BlockProgramsCard } from "./BlockProgramsCard";
|
import { BlockProgramsCard } from "./BlockProgramsCard";
|
||||||
|
@ -134,7 +134,7 @@ function MoreSection({
|
||||||
tab,
|
tab,
|
||||||
}: {
|
}: {
|
||||||
slot: number;
|
slot: number;
|
||||||
block: ConfirmedBlock;
|
block: BlockResponse;
|
||||||
tab?: string;
|
tab?: string;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { ConfirmedBlock, PublicKey } from "@solana/web3.js";
|
import { BlockResponse, PublicKey } from "@solana/web3.js";
|
||||||
import { Address } from "components/common/Address";
|
import { Address } from "components/common/Address";
|
||||||
import { TableCardBody } from "components/common/TableCardBody";
|
import { TableCardBody } from "components/common/TableCardBody";
|
||||||
|
|
||||||
export function BlockProgramsCard({ block }: { block: ConfirmedBlock }) {
|
export function BlockProgramsCard({ block }: { block: BlockResponse }) {
|
||||||
const totalTransactions = block.transactions.length;
|
const totalTransactions = block.transactions.length;
|
||||||
const txSuccesses = new Map<string, number>();
|
const txSuccesses = new Map<string, number>();
|
||||||
const txFrequency = new Map<string, number>();
|
const txFrequency = new Map<string, number>();
|
||||||
|
@ -11,25 +11,24 @@ export function BlockProgramsCard({ block }: { block: ConfirmedBlock }) {
|
||||||
|
|
||||||
let totalInstructions = 0;
|
let totalInstructions = 0;
|
||||||
block.transactions.forEach((tx) => {
|
block.transactions.forEach((tx) => {
|
||||||
totalInstructions += tx.transaction.instructions.length;
|
const message = tx.transaction.message;
|
||||||
|
totalInstructions += message.instructions.length;
|
||||||
const programUsed = new Set<string>();
|
const programUsed = new Set<string>();
|
||||||
const trackProgramId = (programId: PublicKey) => {
|
const trackProgram = (index: number) => {
|
||||||
|
if (index >= message.accountKeys.length) return;
|
||||||
|
const programId = message.accountKeys[index];
|
||||||
const programAddress = programId.toBase58();
|
const programAddress = programId.toBase58();
|
||||||
programUsed.add(programAddress);
|
programUsed.add(programAddress);
|
||||||
const frequency = ixFrequency.get(programAddress);
|
const frequency = ixFrequency.get(programAddress);
|
||||||
ixFrequency.set(programAddress, frequency ? frequency + 1 : 1);
|
ixFrequency.set(programAddress, frequency ? frequency + 1 : 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
tx.transaction.instructions.forEach((ix, index) => {
|
message.instructions.forEach((ix) => trackProgram(ix.programIdIndex));
|
||||||
trackProgramId(ix.programId);
|
tx.meta?.innerInstructions?.forEach((inner) => {
|
||||||
tx.meta?.innerInstructions?.forEach((inner) => {
|
totalInstructions += inner.instructions.length;
|
||||||
if (inner.index !== index) return;
|
inner.instructions.forEach((innerIx) =>
|
||||||
totalInstructions += inner.instructions.length;
|
trackProgram(innerIx.programIdIndex)
|
||||||
inner.instructions.forEach((innerIx) => {
|
);
|
||||||
if (innerIx.programIdIndex >= ix.keys.length) return;
|
|
||||||
trackProgramId(ix.keys[innerIx.programIdIndex].pubkey);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const successful = tx.meta?.err === null;
|
const successful = tx.meta?.err === null;
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { lamportsToSolString } from "utils";
|
import { lamportsToSolString } from "utils";
|
||||||
import { ConfirmedBlock, PublicKey } from "@solana/web3.js";
|
import { BlockResponse, PublicKey } from "@solana/web3.js";
|
||||||
import { Address } from "components/common/Address";
|
import { Address } from "components/common/Address";
|
||||||
|
|
||||||
const PAGE_SIZE = 10;
|
const PAGE_SIZE = 10;
|
||||||
|
|
||||||
export function BlockRewardsCard({ block }: { block: ConfirmedBlock }) {
|
export function BlockRewardsCard({ block }: { block: BlockResponse }) {
|
||||||
const [rewardsDisplayed, setRewardsDisplayed] = React.useState(PAGE_SIZE);
|
const [rewardsDisplayed, setRewardsDisplayed] = React.useState(PAGE_SIZE);
|
||||||
|
|
||||||
if (!block.rewards || block.rewards.length < 1) {
|
if (!block.rewards || block.rewards.length < 1) {
|
||||||
|
|
|
@ -38,10 +38,12 @@ export function InstructionCard({
|
||||||
const [showRaw, setShowRaw] = React.useState(defaultRaw || false);
|
const [showRaw, setShowRaw] = React.useState(defaultRaw || false);
|
||||||
const signature = useContext(SignatureContext);
|
const signature = useContext(SignatureContext);
|
||||||
const details = useTransactionDetails(signature);
|
const details = useTransactionDetails(signature);
|
||||||
|
|
||||||
let raw: TransactionInstruction | undefined = undefined;
|
let raw: TransactionInstruction | undefined = undefined;
|
||||||
if (details && childIndex === undefined) {
|
if (details && childIndex === undefined) {
|
||||||
raw = details?.data?.raw?.transaction.instructions[index];
|
raw = details?.data?.raw?.instructions[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchRaw = useFetchRawTransaction();
|
const fetchRaw = useFetchRawTransaction();
|
||||||
const fetchRawTrigger = () => fetchRaw(signature);
|
const fetchRawTrigger = () => fetchRaw(signature);
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ import {
|
||||||
PartiallyDecodedInstruction,
|
PartiallyDecodedInstruction,
|
||||||
PublicKey,
|
PublicKey,
|
||||||
SignatureResult,
|
SignatureResult,
|
||||||
Transaction,
|
|
||||||
TransactionSignature,
|
TransactionSignature,
|
||||||
} from "@solana/web3.js";
|
} from "@solana/web3.js";
|
||||||
import { BpfLoaderDetailsCard } from "components/instruction/bpf-loader/BpfLoaderDetailsCard";
|
import { BpfLoaderDetailsCard } from "components/instruction/bpf-loader/BpfLoaderDetailsCard";
|
||||||
|
@ -59,8 +58,6 @@ export function InstructionsSection({ signature }: SignatureProps) {
|
||||||
|
|
||||||
if (!status?.data?.info || !details?.data?.transaction) return null;
|
if (!status?.data?.info || !details?.data?.transaction) return null;
|
||||||
|
|
||||||
const raw = details.data.raw?.transaction;
|
|
||||||
|
|
||||||
const { transaction } = details.data.transaction;
|
const { transaction } = details.data.transaction;
|
||||||
const { meta } = details.data.transaction;
|
const { meta } = details.data.transaction;
|
||||||
|
|
||||||
|
@ -106,7 +103,6 @@ export function InstructionsSection({ signature }: SignatureProps) {
|
||||||
signature,
|
signature,
|
||||||
tx: transaction,
|
tx: transaction,
|
||||||
childIndex,
|
childIndex,
|
||||||
raw,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
innerCards.push(res);
|
innerCards.push(res);
|
||||||
|
@ -120,7 +116,6 @@ export function InstructionsSection({ signature }: SignatureProps) {
|
||||||
signature,
|
signature,
|
||||||
tx: transaction,
|
tx: transaction,
|
||||||
innerCards,
|
innerCards,
|
||||||
raw,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -147,7 +142,6 @@ function renderInstructionCard({
|
||||||
signature,
|
signature,
|
||||||
innerCards,
|
innerCards,
|
||||||
childIndex,
|
childIndex,
|
||||||
raw,
|
|
||||||
}: {
|
}: {
|
||||||
ix: ParsedInstruction | PartiallyDecodedInstruction;
|
ix: ParsedInstruction | PartiallyDecodedInstruction;
|
||||||
tx: ParsedTransaction;
|
tx: ParsedTransaction;
|
||||||
|
@ -156,7 +150,6 @@ function renderInstructionCard({
|
||||||
signature: TransactionSignature;
|
signature: TransactionSignature;
|
||||||
innerCards?: JSX.Element[];
|
innerCards?: JSX.Element[];
|
||||||
childIndex?: number;
|
childIndex?: number;
|
||||||
raw?: Transaction;
|
|
||||||
}) {
|
}) {
|
||||||
const key = `${index}-${childIndex}`;
|
const key = `${index}-${childIndex}`;
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import * as Sentry from "@sentry/react";
|
import * as Sentry from "@sentry/react";
|
||||||
import * as Cache from "providers/cache";
|
import * as Cache from "providers/cache";
|
||||||
import { Connection, ConfirmedBlock } from "@solana/web3.js";
|
import { Connection, BlockResponse } from "@solana/web3.js";
|
||||||
import { useCluster, Cluster } from "./cluster";
|
import { useCluster, Cluster } from "./cluster";
|
||||||
|
|
||||||
export enum FetchStatus {
|
export enum FetchStatus {
|
||||||
|
@ -16,7 +16,7 @@ export enum ActionType {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Block = {
|
type Block = {
|
||||||
block?: ConfirmedBlock;
|
block?: BlockResponse;
|
||||||
};
|
};
|
||||||
|
|
||||||
type State = Cache.State<Block>;
|
type State = Cache.State<Block>;
|
||||||
|
@ -72,18 +72,18 @@ export async function fetchBlock(
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const connection = new Connection(url, "finalized");
|
const connection = new Connection(url, "finalized");
|
||||||
data = { block: await connection.getConfirmedBlock(Number(key)) };
|
const block = await connection.getBlock(Number(key));
|
||||||
status = FetchStatus.Fetched;
|
if (block === null) {
|
||||||
} catch (err) {
|
data = {};
|
||||||
const error = err as Error;
|
|
||||||
if (error.message.includes("not found")) {
|
|
||||||
data = {} as Block;
|
|
||||||
status = FetchStatus.Fetched;
|
status = FetchStatus.Fetched;
|
||||||
} else {
|
} else {
|
||||||
status = FetchStatus.FetchFailed;
|
data = { block };
|
||||||
if (cluster !== Cluster.Custom) {
|
status = FetchStatus.Fetched;
|
||||||
Sentry.captureException(error, { tags: { url } });
|
}
|
||||||
}
|
} catch (err) {
|
||||||
|
status = FetchStatus.FetchFailed;
|
||||||
|
if (cluster !== Cluster.Custom) {
|
||||||
|
Sentry.captureException(err, { tags: { url } });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import {
|
||||||
Connection,
|
Connection,
|
||||||
TransactionSignature,
|
TransactionSignature,
|
||||||
ParsedConfirmedTransaction,
|
ParsedConfirmedTransaction,
|
||||||
ConfirmedTransaction,
|
Transaction,
|
||||||
} from "@solana/web3.js";
|
} from "@solana/web3.js";
|
||||||
import { useCluster, Cluster } from "../cluster";
|
import { useCluster, Cluster } from "../cluster";
|
||||||
import * as Cache from "providers/cache";
|
import * as Cache from "providers/cache";
|
||||||
|
@ -12,7 +12,7 @@ import { reportError } from "utils/sentry";
|
||||||
|
|
||||||
export interface Details {
|
export interface Details {
|
||||||
transaction?: ParsedConfirmedTransaction | null;
|
transaction?: ParsedConfirmedTransaction | null;
|
||||||
raw?: ConfirmedTransaction | null;
|
raw?: Transaction | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
type State = Cache.State<Details>;
|
type State = Cache.State<Details>;
|
||||||
|
@ -128,17 +128,23 @@ async function fetchRawTransaction(
|
||||||
url: string
|
url: string
|
||||||
) {
|
) {
|
||||||
let fetchStatus;
|
let fetchStatus;
|
||||||
let transaction;
|
|
||||||
try {
|
try {
|
||||||
transaction = await new Connection(url).getConfirmedTransaction(signature);
|
const response = await new Connection(url).getTransaction(signature);
|
||||||
fetchStatus = FetchStatus.Fetched;
|
fetchStatus = FetchStatus.Fetched;
|
||||||
|
|
||||||
|
let data: Details = { raw: null };
|
||||||
|
if (response !== null) {
|
||||||
|
const { message, signatures } = response.transaction;
|
||||||
|
data = {
|
||||||
|
raw: Transaction.populate(message, signatures),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ActionType.Update,
|
type: ActionType.Update,
|
||||||
status: fetchStatus,
|
status: fetchStatus,
|
||||||
key: signature,
|
key: signature,
|
||||||
data: {
|
data,
|
||||||
raw: transaction,
|
|
||||||
},
|
|
||||||
url,
|
url,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
Loading…
Reference in New Issue