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 24, 2022
1 parent c1e8008 commit 2b11ac8
Show file tree
Hide file tree
Showing 21 changed files with 538 additions and 134 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
13 changes: 8 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,12 @@ 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();
const accountKeys = message.getAccountKeys({
accountKeysFromLookups: tx.meta?.loadedAddresses,
});
message.compiledInstructions.forEach((ix) => {
ix.accountKeyIndexes.forEach((index) => {
const address = accountKeys.get(index)!.toBase58();
txSet.set(address, message.isAccountWritable(index));
});
});
Expand Down
22 changes: 16 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,11 @@ export function BlockHistoryCard({ block }: { block: BlockResponse }) {
});

const invocations = new Map<string, number>();
const accountKeys = tx.transaction.message.getAccountKeys({
accountKeysFromLookups: tx.meta?.loadedAddresses,
});
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 +146,15 @@ 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 accountKeys = tx.transaction.message.getAccountKeys({
accountKeysFromLookups: tx.meta?.loadedAddresses,
});
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
21 changes: 15 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,23 @@ 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 accountKeys = tx.transaction.message.getAccountKeys({
accountKeysFromLookups: tx.meta?.loadedAddresses,
});
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
21 changes: 17 additions & 4 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 !== undefined && (
<tr>
<td>Transaction Version</td>
<td className="text-lg-end text-uppercase">{version}</td>
</tr>
)}
</TableCardBody>
</div>
);
Expand Down Expand Up @@ -414,14 +422,19 @@ function AccountsCard({ signature }: SignatureProps) {
{index === 0 && (
<span className="badge bg-info-soft me-1">Fee Payer</span>
)}
{account.writable && (
<span className="badge bg-info-soft me-1">Writable</span>
)}
{account.signer && (
<span className="badge bg-info-soft me-1">Signer</span>
)}
{account.writable && (
<span className="badge bg-danger-soft me-1">Writable</span>
)}
{message.instructions.find((ix) => ix.programId.equals(pubkey)) && (
<span className="badge bg-info-soft me-1">Program</span>
<span className="badge bg-warning-soft me-1">Program</span>
)}
{account.source === "lookupTable" && (
<span className="badge bg-gray-soft me-1">
Address Table Lookup
</span>
)}
</td>
</tr>
Expand Down
Loading

0 comments on commit 2b11ac8

Please sign in to comment.