Skip to content

Commit

Permalink
Explorer: Support displaying and inspecting versioned transactions
Browse files Browse the repository at this point in the history
  • Loading branch information
jstarry committed Sep 15, 2022
1 parent 1a4b8b5 commit 8ee23fe
Show file tree
Hide file tree
Showing 16 changed files with 137 additions and 89 deletions.
27 changes: 12 additions & 15 deletions explorer/src/components/ProgramLogsCardBody.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Message, ParsedMessage } from "@solana/web3.js";
import { ParsedMessage, PublicKey, VersionedMessage } from "@solana/web3.js";
import { Cluster } from "providers/cluster";
import { TableCardBody } from "components/common/TableCardBody";
import { InstructionLogs } from "utils/program-logs";
Expand All @@ -21,27 +21,24 @@ export function ProgramLogsCardBody({
cluster,
url,
}: {
message: Message | ParsedMessage;
message: VersionedMessage | ParsedMessage;
logs: InstructionLogs[];
cluster: Cluster;
url: string;
}) {
let logIndex = 0;
let instructionProgramIds: PublicKey[];
if ("compiledInstructions" in message) {
instructionProgramIds = message.compiledInstructions.map((ix) => {
return message.staticAccountKeys[ix.programIdIndex];
});
} else {
instructionProgramIds = message.instructions.map((ix) => ix.programId);
}

return (
<TableCardBody>
{message.instructions.map((ix, index) => {
let programId;
if ("programIdIndex" in ix) {
const programAccount = message.accountKeys[ix.programIdIndex];
if ("pubkey" in programAccount) {
programId = programAccount.pubkey;
} else {
programId = programAccount;
}
} else {
programId = ix.programId;
}

{instructionProgramIds.map((programId, index) => {
const programAddress = programId.toBase58();
let programLogs: InstructionLogs | undefined = logs[logIndex];
if (programLogs?.invokedProgram === programAddress) {
Expand Down
10 changes: 5 additions & 5 deletions explorer/src/components/block/BlockAccountsCard.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from "react";
import { BlockResponse, PublicKey } from "@solana/web3.js";
import { PublicKey, VersionedBlockResponse } from "@solana/web3.js";
import { Address } from "components/common/Address";
import { Link } from "react-router-dom";
import { clusterPath } from "utils/url";
Expand All @@ -15,7 +15,7 @@ export function BlockAccountsCard({
block,
blockSlot,
}: {
block: BlockResponse;
block: VersionedBlockResponse;
blockSlot: number;
}) {
const [numDisplayed, setNumDisplayed] = React.useState(10);
Expand All @@ -26,9 +26,9 @@ export function BlockAccountsCard({
block.transactions.forEach((tx) => {
const message = tx.transaction.message;
const txSet = new Map<string, boolean>();
message.instructions.forEach((ix) => {
ix.accounts.forEach((index) => {
const address = message.accountKeys[index].toBase58();
message.compiledInstructions.forEach((ix) => {
ix.accountKeyIndexes.forEach((index) => {
const address = message.getAccountKeys().get(index)!.toBase58();
txSet.set(address, message.isAccountWritable(index));
});
});
Expand Down
28 changes: 22 additions & 6 deletions explorer/src/components/block/BlockHistoryCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import React from "react";
import { Link, useHistory, useLocation } from "react-router-dom";
import { Location } from "history";
import {
BlockResponse,
ConfirmedTransactionMeta,
TransactionSignature,
PublicKey,
VOTE_PROGRAM_ID,
VersionedBlockResponse,
} from "@solana/web3.js";
import { ErrorCard } from "components/common/ErrorCard";
import { Signature } from "components/common/Signature";
Expand Down Expand Up @@ -51,7 +51,7 @@ type TransactionWithInvocations = {
logTruncated: boolean;
};

export function BlockHistoryCard({ block }: { block: BlockResponse }) {
export function BlockHistoryCard({ block }: { block: VersionedBlockResponse }) {
const [numDisplayed, setNumDisplayed] = React.useState(PAGE_SIZE);
const [showDropdown, setDropdown] = React.useState(false);
const query = useQuery();
Expand All @@ -72,7 +72,7 @@ export function BlockHistoryCard({ block }: { block: BlockResponse }) {
signature = tx.transaction.signatures[0];
}

let programIndexes = tx.transaction.message.instructions
let programIndexes = tx.transaction.message.compiledInstructions
.map((ix) => ix.programIdIndex)
.concat(
tx.meta?.innerInstructions?.flatMap((ix) => {
Expand All @@ -87,8 +87,14 @@ export function BlockHistoryCard({ block }: { block: BlockResponse }) {
});

const invocations = new Map<string, number>();
const accountKeysFromLookups = tx.meta?.loadedAddresses;
const accountKeys = tx.transaction.message.getAccountKeys(
accountKeysFromLookups && {
accountKeysFromLookups,
}
);
for (const [i, count] of indexMap.entries()) {
const programId = tx.transaction.message.accountKeys[i].toBase58();
const programId = accountKeys.get(i)!.toBase58();
invocations.set(programId, count);
const programTransactionCount = invokedPrograms.get(programId) || 0;
invokedPrograms.set(programId, programTransactionCount + 1);
Expand Down Expand Up @@ -143,8 +149,18 @@ export function BlockHistoryCard({ block }: { block: BlockResponse }) {
if (accountFilter === null) {
return true;
}
const tx = block.transactions[index].transaction;
return tx.message.accountKeys.find((key) => key.equals(accountFilter));

const tx = block.transactions[index];
const accountKeysFromLookups = tx.meta?.loadedAddresses;
const accountKeys = tx.transaction.message.getAccountKeys(
accountKeysFromLookups && {
accountKeysFromLookups,
}
);
return accountKeys
.keySegments()
.flat()
.find((key) => key.equals(accountFilter));
});

const showComputeUnits = filteredTxs.every(
Expand Down
4 changes: 2 additions & 2 deletions explorer/src/components/block/BlockOverviewCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Slot } from "components/common/Slot";
import { ClusterStatus, useCluster } from "providers/cluster";
import { BlockHistoryCard } from "./BlockHistoryCard";
import { BlockRewardsCard } from "./BlockRewardsCard";
import { BlockResponse } from "@solana/web3.js";
import { VersionedBlockResponse } from "@solana/web3.js";
import { NavLink } from "react-router-dom";
import { clusterPath } from "utils/url";
import { BlockProgramsCard } from "./BlockProgramsCard";
Expand Down Expand Up @@ -211,7 +211,7 @@ function MoreSection({
tab,
}: {
slot: number;
block: BlockResponse;
block: VersionedBlockResponse;
tab?: string;
}) {
return (
Expand Down
24 changes: 18 additions & 6 deletions explorer/src/components/block/BlockProgramsCard.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import React from "react";
import { BlockResponse, PublicKey } from "@solana/web3.js";
import { PublicKey, VersionedBlockResponse } from "@solana/web3.js";
import { Address } from "components/common/Address";
import { TableCardBody } from "components/common/TableCardBody";

export function BlockProgramsCard({ block }: { block: BlockResponse }) {
export function BlockProgramsCard({
block,
}: {
block: VersionedBlockResponse;
}) {
const totalTransactions = block.transactions.length;
const txSuccesses = new Map<string, number>();
const txFrequency = new Map<string, number>();
Expand All @@ -12,18 +16,26 @@ export function BlockProgramsCard({ block }: { block: BlockResponse }) {
let totalInstructions = 0;
block.transactions.forEach((tx) => {
const message = tx.transaction.message;
totalInstructions += message.instructions.length;
totalInstructions += message.compiledInstructions.length;
const programUsed = new Set<string>();
const accountKeysFromLookups = tx.meta?.loadedAddresses;
const accountKeys = tx.transaction.message.getAccountKeys(
accountKeysFromLookups && {
accountKeysFromLookups,
}
);
const trackProgram = (index: number) => {
if (index >= message.accountKeys.length) return;
const programId = message.accountKeys[index];
if (index >= accountKeys.length) return;
const programId = accountKeys.get(index)!;
const programAddress = programId.toBase58();
programUsed.add(programAddress);
const frequency = ixFrequency.get(programAddress);
ixFrequency.set(programAddress, frequency ? frequency + 1 : 1);
};

message.instructions.forEach((ix) => trackProgram(ix.programIdIndex));
message.compiledInstructions.forEach((ix) =>
trackProgram(ix.programIdIndex)
);
tx.meta?.innerInstructions?.forEach((inner) => {
totalInstructions += inner.instructions.length;
inner.instructions.forEach((innerIx) =>
Expand Down
4 changes: 2 additions & 2 deletions explorer/src/components/block/BlockRewardsCard.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React from "react";
import { SolBalance } from "utils";
import { BlockResponse, PublicKey } from "@solana/web3.js";
import { PublicKey, VersionedBlockResponse } from "@solana/web3.js";
import { Address } from "components/common/Address";

const PAGE_SIZE = 10;

export function BlockRewardsCard({ block }: { block: BlockResponse }) {
export function BlockRewardsCard({ block }: { block: VersionedBlockResponse }) {
const [rewardsDisplayed, setRewardsDisplayed] = React.useState(PAGE_SIZE);

if (!block.rewards || block.rewards.length < 1) {
Expand Down
8 changes: 8 additions & 0 deletions explorer/src/pages/TransactionDetailsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ function StatusCard({
const fee = transactionWithMeta?.meta?.fee;
const transaction = transactionWithMeta?.transaction;
const blockhash = transaction?.message.recentBlockhash;
const version = transactionWithMeta?.version;
const isNonce = (() => {
if (!transaction || transaction.message.instructions.length < 1) {
return false;
Expand Down Expand Up @@ -330,6 +331,13 @@ function StatusCard({
</td>
</tr>
)}

{version ?? (
<tr>
<td>TransactionVersion</td>
<td className="text-lg-end">{version}</td>
</tr>
)}
</TableCardBody>
</div>
);
Expand Down
16 changes: 9 additions & 7 deletions explorer/src/pages/inspector/AccountsCard.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React from "react";
import { Message, PublicKey } from "@solana/web3.js";
import { PublicKey, VersionedMessage } from "@solana/web3.js";
import { TableCardBody } from "components/common/TableCardBody";
import { AddressWithContext } from "./AddressWithContext";
import { ErrorCard } from "components/common/ErrorCard";

export function AccountsCard({ message }: { message: Message }) {
export function AccountsCard({ message }: { message: VersionedMessage }) {
const [expanded, setExpanded] = React.useState(true);

const { validMessage, error } = React.useMemo(() => {
Expand All @@ -16,9 +16,11 @@ export function AccountsCard({ message }: { message: Message }) {

if (numReadonlySignedAccounts >= numRequiredSignatures) {
return { validMessage: undefined, error: "Invalid header" };
} else if (numReadonlyUnsignedAccounts >= message.accountKeys.length) {
} else if (
numReadonlyUnsignedAccounts >= message.staticAccountKeys.length
) {
return { validMessage: undefined, error: "Invalid header" };
} else if (message.accountKeys.length === 0) {
} else if (message.staticAccountKeys.length === 0) {
return { validMessage: undefined, error: "Message has no accounts" };
}

Expand All @@ -31,7 +33,7 @@ export function AccountsCard({ message }: { message: Message }) {
const accountRows = React.useMemo(() => {
const message = validMessage;
if (!message) return;
return message.accountKeys.map((publicKey, accountIndex) => {
return message.staticAccountKeys.map((publicKey, accountIndex) => {
const {
numRequiredSignatures,
numReadonlySignedAccounts,
Expand All @@ -47,7 +49,7 @@ export function AccountsCard({ message }: { message: Message }) {
}
} else if (
accountIndex >=
message.accountKeys.length - numReadonlyUnsignedAccounts
message.staticAccountKeys.length - numReadonlyUnsignedAccounts
) {
readOnly = true;
}
Expand All @@ -71,7 +73,7 @@ export function AccountsCard({ message }: { message: Message }) {
<div className="card">
<div className="card-header">
<h3 className="card-header-title">
{`Account List (${message.accountKeys.length})`}
{`Account List excluding Address Table Lookups (${message.staticAccountKeys.length})`}
</h3>
<button
className={`btn btn-sm d-flex ${
Expand Down
10 changes: 5 additions & 5 deletions explorer/src/pages/inspector/InspectorPage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from "react";
import { Message, PACKET_DATA_SIZE } from "@solana/web3.js";
import { Message, PACKET_DATA_SIZE, VersionedMessage } from "@solana/web3.js";

import { TableCardBody } from "components/common/TableCardBody";
import { SolBalance } from "utils";
Expand All @@ -25,7 +25,7 @@ import base58 from "bs58";

export type TransactionData = {
rawMessage: Uint8Array;
message: Message;
message: VersionedMessage;
signatures?: (string | null)[];
};

Expand Down Expand Up @@ -294,7 +294,7 @@ function OverviewCard({
raw,
onClear,
}: {
message: Message;
message: VersionedMessage;
raw: Uint8Array;
onClear: () => void;
}) {
Expand Down Expand Up @@ -354,11 +354,11 @@ function OverviewCard({
</div>
</td>
<td className="text-end">
{message.accountKeys.length === 0 ? (
{message.staticAccountKeys.length === 0 ? (
"No Fee Payer"
) : (
<AddressWithContext
pubkey={message.accountKeys[0]}
pubkey={message.staticAccountKeys[0]}
validator={feePayerValidator}
/>
)}
Expand Down
Loading

0 comments on commit 8ee23fe

Please sign in to comment.