explorer: Add details pages for upgradeable loader accounts (#15836)
This commit is contained in:
parent
0c9ca5522c
commit
c4f98f9c73
|
@ -0,0 +1,267 @@
|
|||
import React from "react";
|
||||
import { TableCardBody } from "components/common/TableCardBody";
|
||||
import { lamportsToSolString } from "utils";
|
||||
import { Account, useFetchAccountInfo } from "providers/accounts";
|
||||
import { Address } from "components/common/Address";
|
||||
import {
|
||||
ProgramAccountInfo,
|
||||
ProgramBufferAccountInfo,
|
||||
ProgramDataAccountInfo,
|
||||
UpgradeableLoaderAccount,
|
||||
} from "validators/accounts/upgradeable-program";
|
||||
import { Slot } from "components/common/Slot";
|
||||
import { addressLabel } from "utils/tx";
|
||||
import { useCluster } from "providers/cluster";
|
||||
import { ErrorCard } from "components/common/ErrorCard";
|
||||
|
||||
export function UpgradeableLoaderAccountSection({
|
||||
account,
|
||||
parsedData,
|
||||
programData,
|
||||
}: {
|
||||
account: Account;
|
||||
parsedData: UpgradeableLoaderAccount;
|
||||
programData: ProgramDataAccountInfo | undefined;
|
||||
}) {
|
||||
switch (parsedData.type) {
|
||||
case "program": {
|
||||
if (programData === undefined) {
|
||||
return <ErrorCard text="Invalid Upgradeable Program account" />;
|
||||
}
|
||||
return (
|
||||
<UpgradeableProgramSection
|
||||
account={account}
|
||||
programAccount={parsedData.info}
|
||||
programData={programData}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case "programData": {
|
||||
return (
|
||||
<UpgradeableProgramDataSection
|
||||
account={account}
|
||||
programData={parsedData.info}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case "buffer": {
|
||||
return (
|
||||
<UpgradeableProgramBufferSection
|
||||
account={account}
|
||||
programBuffer={parsedData.info}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function UpgradeableProgramSection({
|
||||
account,
|
||||
programAccount,
|
||||
programData,
|
||||
}: {
|
||||
account: Account;
|
||||
programAccount: ProgramAccountInfo;
|
||||
programData: ProgramDataAccountInfo;
|
||||
}) {
|
||||
const refresh = useFetchAccountInfo();
|
||||
const { cluster } = useCluster();
|
||||
const label = addressLabel(account.pubkey.toBase58(), cluster);
|
||||
return (
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
<h3 className="card-header-title mb-0 d-flex align-items-center">
|
||||
Program Account
|
||||
</h3>
|
||||
<button
|
||||
className="btn btn-white btn-sm"
|
||||
onClick={() => refresh(account.pubkey)}
|
||||
>
|
||||
<span className="fe fe-refresh-cw mr-2"></span>
|
||||
Refresh
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<TableCardBody>
|
||||
<tr>
|
||||
<td>Address</td>
|
||||
<td className="text-lg-right">
|
||||
<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">
|
||||
{lamportsToSolString(account.lamports || 0)}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Executable</td>
|
||||
<td className="text-lg-right">Yes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Executable Data</td>
|
||||
<td className="text-lg-right">
|
||||
<Address pubkey={programAccount.programData} alignRight link />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Upgradeable</td>
|
||||
<td className="text-lg-right">
|
||||
{programData.authority !== null ? "Yes" : "No"}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Last Deployed Slot</td>
|
||||
<td className="text-lg-right">
|
||||
<Slot slot={programData.slot} link />
|
||||
</td>
|
||||
</tr>
|
||||
{programData.authority !== null && (
|
||||
<tr>
|
||||
<td>Upgrade Authority</td>
|
||||
<td className="text-lg-right">
|
||||
<Address pubkey={programData.authority} alignRight link />
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</TableCardBody>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function UpgradeableProgramDataSection({
|
||||
account,
|
||||
programData,
|
||||
}: {
|
||||
account: Account;
|
||||
programData: ProgramDataAccountInfo;
|
||||
}) {
|
||||
const refresh = useFetchAccountInfo();
|
||||
return (
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
<h3 className="card-header-title mb-0 d-flex align-items-center">
|
||||
Program Executable Data Account
|
||||
</h3>
|
||||
<button
|
||||
className="btn btn-white btn-sm"
|
||||
onClick={() => refresh(account.pubkey)}
|
||||
>
|
||||
<span className="fe fe-refresh-cw mr-2"></span>
|
||||
Refresh
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<TableCardBody>
|
||||
<tr>
|
||||
<td>Address</td>
|
||||
<td className="text-lg-right">
|
||||
<Address pubkey={account.pubkey} alignRight raw />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Balance (SOL)</td>
|
||||
<td className="text-lg-right text-uppercase">
|
||||
{lamportsToSolString(account.lamports || 0)}
|
||||
</td>
|
||||
</tr>
|
||||
{account.details?.space !== undefined && (
|
||||
<tr>
|
||||
<td>Data (Bytes)</td>
|
||||
<td className="text-lg-right">{account.details.space}</td>
|
||||
</tr>
|
||||
)}
|
||||
<tr>
|
||||
<td>Upgradeable</td>
|
||||
<td className="text-lg-right">
|
||||
{programData.authority !== null ? "Yes" : "No"}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Last Deployed Slot</td>
|
||||
<td className="text-lg-right">
|
||||
<Slot slot={programData.slot} link />
|
||||
</td>
|
||||
</tr>
|
||||
{programData.authority !== null && (
|
||||
<tr>
|
||||
<td>Upgrade Authority</td>
|
||||
<td className="text-lg-right">
|
||||
<Address pubkey={programData.authority} alignRight link />
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</TableCardBody>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function UpgradeableProgramBufferSection({
|
||||
account,
|
||||
programBuffer,
|
||||
}: {
|
||||
account: Account;
|
||||
programBuffer: ProgramBufferAccountInfo;
|
||||
}) {
|
||||
const refresh = useFetchAccountInfo();
|
||||
return (
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
<h3 className="card-header-title mb-0 d-flex align-items-center">
|
||||
Program Deploy Buffer Account
|
||||
</h3>
|
||||
<button
|
||||
className="btn btn-white btn-sm"
|
||||
onClick={() => refresh(account.pubkey)}
|
||||
>
|
||||
<span className="fe fe-refresh-cw mr-2"></span>
|
||||
Refresh
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<TableCardBody>
|
||||
<tr>
|
||||
<td>Address</td>
|
||||
<td className="text-lg-right">
|
||||
<Address pubkey={account.pubkey} alignRight raw />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Balance (SOL)</td>
|
||||
<td className="text-lg-right text-uppercase">
|
||||
{lamportsToSolString(account.lamports || 0)}
|
||||
</td>
|
||||
</tr>
|
||||
{account.details?.space !== undefined && (
|
||||
<tr>
|
||||
<td>Data (Bytes)</td>
|
||||
<td className="text-lg-right">{account.details.space}</td>
|
||||
</tr>
|
||||
)}
|
||||
{programBuffer.authority !== null && (
|
||||
<tr>
|
||||
<td>Deploy Authority</td>
|
||||
<td className="text-lg-right">
|
||||
<Address pubkey={programBuffer.authority} alignRight link />
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
{account.details && (
|
||||
<tr>
|
||||
<td>Owner</td>
|
||||
<td className="text-lg-right">
|
||||
<Address pubkey={account.details.owner} alignRight link />
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</TableCardBody>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1,93 +0,0 @@
|
|||
import React from "react";
|
||||
import { TableCardBody } from "components/common/TableCardBody";
|
||||
import { lamportsToSolString } from "utils";
|
||||
import { Account, useFetchAccountInfo } from "providers/accounts";
|
||||
import { Address } from "components/common/Address";
|
||||
import {
|
||||
ProgramAccountInfo,
|
||||
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,
|
||||
programAccount,
|
||||
programData,
|
||||
}: {
|
||||
account: Account;
|
||||
programAccount: ProgramAccountInfo;
|
||||
programData: ProgramDataAccountInfo;
|
||||
}) {
|
||||
const refresh = useFetchAccountInfo();
|
||||
const { cluster } = useCluster();
|
||||
const label = addressLabel(account.pubkey.toBase58(), cluster);
|
||||
return (
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
<h3 className="card-header-title mb-0 d-flex align-items-center">
|
||||
Program Account
|
||||
</h3>
|
||||
<button
|
||||
className="btn btn-white btn-sm"
|
||||
onClick={() => refresh(account.pubkey)}
|
||||
>
|
||||
<span className="fe fe-refresh-cw mr-2"></span>
|
||||
Refresh
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<TableCardBody>
|
||||
<tr>
|
||||
<td>Address</td>
|
||||
<td className="text-lg-right">
|
||||
<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">
|
||||
{lamportsToSolString(account.lamports || 0)}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Executable</td>
|
||||
<td className="text-lg-right">Yes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Executable Data</td>
|
||||
<td className="text-lg-right">
|
||||
<Address pubkey={programAccount.programData} alignRight link />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Upgradeable</td>
|
||||
<td className="text-lg-right">
|
||||
{programData.authority !== null ? "Yes" : "No"}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Last Deployed Slot</td>
|
||||
<td className="text-lg-right">
|
||||
<Slot slot={programData.slot} link />
|
||||
</td>
|
||||
</tr>
|
||||
{programData.authority !== null && (
|
||||
<tr>
|
||||
<td>Upgrade Authority</td>
|
||||
<td className="text-lg-right">
|
||||
<Address pubkey={programData.authority} alignRight link />
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</TableCardBody>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -28,7 +28,7 @@ import { StakeHistoryCard } from "components/account/StakeHistoryCard";
|
|||
import { BlockhashesCard } from "components/account/BlockhashesCard";
|
||||
import { ConfigAccountSection } from "components/account/ConfigAccountSection";
|
||||
import { useFlaggedAccounts } from "providers/accounts/flagged-accounts";
|
||||
import { UpgradeableProgramSection } from "components/account/UpgradeableProgramSection";
|
||||
import { UpgradeableLoaderAccountSection } from "components/account/UpgradeableLoaderAccountSection";
|
||||
import { useTokenRegistry } from "providers/mints/token-registry";
|
||||
|
||||
const TABS_LOOKUP: { [id: string]: Tab } = {
|
||||
|
@ -177,9 +177,9 @@ function InfoSection({ account }: { account: Account }) {
|
|||
|
||||
if (data && data.program === "bpf-upgradeable-loader") {
|
||||
return (
|
||||
<UpgradeableProgramSection
|
||||
<UpgradeableLoaderAccountSection
|
||||
account={account}
|
||||
programAccount={data.programAccount}
|
||||
parsedData={data.parsed}
|
||||
programData={data.programData}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -20,10 +20,9 @@ import { SysvarAccount } from "validators/accounts/sysvar";
|
|||
import { ConfigAccount } from "validators/accounts/config";
|
||||
import { FlaggedAccountsProvider } from "./flagged-accounts";
|
||||
import {
|
||||
ProgramAccount,
|
||||
ProgramAccountInfo,
|
||||
ProgramDataAccount,
|
||||
ProgramDataAccountInfo,
|
||||
UpgradeableLoaderAccount,
|
||||
} from "validators/accounts/upgradeable-program";
|
||||
export { useAccountHistory } from "./history";
|
||||
|
||||
|
@ -33,10 +32,10 @@ export type StakeProgramData = {
|
|||
activation?: StakeActivationData;
|
||||
};
|
||||
|
||||
export type UpgradeableProgramAccountData = {
|
||||
export type UpgradeableLoaderAccountData = {
|
||||
program: "bpf-upgradeable-loader";
|
||||
programData: ProgramDataAccountInfo;
|
||||
programAccount: ProgramAccountInfo;
|
||||
parsed: UpgradeableLoaderAccount;
|
||||
programData?: ProgramDataAccountInfo;
|
||||
};
|
||||
|
||||
export type TokenProgramData = {
|
||||
|
@ -65,7 +64,7 @@ export type ConfigProgramData = {
|
|||
};
|
||||
|
||||
export type ProgramData =
|
||||
| UpgradeableProgramAccountData
|
||||
| UpgradeableLoaderAccountData
|
||||
| StakeProgramData
|
||||
| TokenProgramData
|
||||
| VoteProgramData
|
||||
|
@ -154,35 +153,32 @@ async function fetchAccountInfo(
|
|||
const info = create(result.data.parsed, ParsedInfo);
|
||||
switch (result.data.program) {
|
||||
case "bpf-upgradeable-loader": {
|
||||
let programAccount: ProgramAccountInfo;
|
||||
let programData: ProgramDataAccountInfo;
|
||||
const parsed = create(info, UpgradeableLoaderAccount);
|
||||
|
||||
if (info.type === "programData") {
|
||||
break;
|
||||
}
|
||||
|
||||
const parsed = create(info, ProgramAccount);
|
||||
programAccount = parsed.info;
|
||||
const result = (
|
||||
await connection.getParsedAccountInfo(parsed.info.programData)
|
||||
).value;
|
||||
if (
|
||||
result &&
|
||||
"parsed" in result.data &&
|
||||
result.data.program === "bpf-upgradeable-loader"
|
||||
) {
|
||||
const info = create(result.data.parsed, ParsedInfo);
|
||||
programData = create(info, ProgramDataAccount).info;
|
||||
} else {
|
||||
throw new Error(
|
||||
`invalid program data account for program: ${pubkey.toBase58()}`
|
||||
);
|
||||
// Fetch program data to get program upgradeability info
|
||||
let programData: ProgramDataAccountInfo | undefined;
|
||||
if (parsed.type === "program") {
|
||||
const result = (
|
||||
await connection.getParsedAccountInfo(parsed.info.programData)
|
||||
).value;
|
||||
if (
|
||||
result &&
|
||||
"parsed" in result.data &&
|
||||
result.data.program === "bpf-upgradeable-loader"
|
||||
) {
|
||||
const info = create(result.data.parsed, ParsedInfo);
|
||||
programData = create(info, ProgramDataAccount).info;
|
||||
} else {
|
||||
throw new Error(
|
||||
`invalid program data account for program: ${pubkey.toBase58()}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
data = {
|
||||
program: result.data.program,
|
||||
parsed,
|
||||
programData,
|
||||
programAccount,
|
||||
};
|
||||
|
||||
break;
|
||||
|
|
|
@ -7,7 +7,7 @@ type Tags =
|
|||
| undefined;
|
||||
|
||||
export function reportError(err: Error, tags: Tags) {
|
||||
console.error(err);
|
||||
console.error(err, err.message);
|
||||
try {
|
||||
Sentry.captureException(err, {
|
||||
tags,
|
||||
|
|
|
@ -1,6 +1,16 @@
|
|||
/* eslint-disable @typescript-eslint/no-redeclare */
|
||||
|
||||
import { type, number, literal, nullable, Infer } from "superstruct";
|
||||
import {
|
||||
type,
|
||||
number,
|
||||
literal,
|
||||
nullable,
|
||||
Infer,
|
||||
union,
|
||||
coerce,
|
||||
create,
|
||||
} from "superstruct";
|
||||
import { ParsedInfo } from "validators";
|
||||
import { PublicKeyFromString } from "validators/pubkey";
|
||||
|
||||
export type ProgramAccountInfo = Infer<typeof ProgramAccountInfo>;
|
||||
|
@ -26,3 +36,48 @@ export const ProgramDataAccount = type({
|
|||
type: literal("programData"),
|
||||
info: ProgramDataAccountInfo,
|
||||
});
|
||||
|
||||
export type ProgramBufferAccountInfo = Infer<typeof ProgramBufferAccountInfo>;
|
||||
export const ProgramBufferAccountInfo = type({
|
||||
authority: nullable(PublicKeyFromString),
|
||||
// don't care about data yet
|
||||
});
|
||||
|
||||
export type ProgramBufferAccount = Infer<typeof ProgramBufferAccount>;
|
||||
export const ProgramBufferAccount = type({
|
||||
type: literal("buffer"),
|
||||
info: ProgramBufferAccountInfo,
|
||||
});
|
||||
|
||||
export type UpgradeableLoaderAccount = Infer<typeof UpgradeableLoaderAccount>;
|
||||
export const UpgradeableLoaderAccount = coerce(
|
||||
union([ProgramAccount, ProgramDataAccount, ProgramBufferAccount]),
|
||||
ParsedInfo,
|
||||
(value) => {
|
||||
// Coercions like `PublicKeyFromString` are not applied within
|
||||
// union validators so we use this custom coercion as a workaround.
|
||||
switch (value.type) {
|
||||
case "program": {
|
||||
return {
|
||||
type: value.type,
|
||||
info: create(value.info, ProgramAccountInfo),
|
||||
};
|
||||
}
|
||||
case "programData": {
|
||||
return {
|
||||
type: value.type,
|
||||
info: create(value.info, ProgramDataAccountInfo),
|
||||
};
|
||||
}
|
||||
case "buffer": {
|
||||
return {
|
||||
type: value.type,
|
||||
info: create(value.info, ProgramBufferAccountInfo),
|
||||
};
|
||||
}
|
||||
default: {
|
||||
throw new Error(`Unknown program account type: ${value.type}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue