From d607ab4b5ea451d945114e7e1567f49bcb0241ef Mon Sep 17 00:00:00 2001 From: Joe C Date: Thu, 14 Mar 2024 12:02:33 -0500 Subject: [PATCH] refactor(experimental): sysvars package: last restart slot This commit introduces the `LastRestartSlot` sysvar to the `@solana/sysvars` package. --- .../src/__tests__/last-restart-slot-test.ts | 25 +++++++ packages/sysvars/src/__tests__/sysvar-test.ts | 15 ++++ .../src/__typetests__/sysvar-typetest.ts | 9 +++ packages/sysvars/src/index.ts | 1 + packages/sysvars/src/last-restart-slot.ts | 72 +++++++++++++++++++ packages/sysvars/src/sysvar.ts | 5 +- 6 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 packages/sysvars/src/__tests__/last-restart-slot-test.ts create mode 100644 packages/sysvars/src/last-restart-slot.ts diff --git a/packages/sysvars/src/__tests__/last-restart-slot-test.ts b/packages/sysvars/src/__tests__/last-restart-slot-test.ts new file mode 100644 index 00000000000..761b358b8ef --- /dev/null +++ b/packages/sysvars/src/__tests__/last-restart-slot-test.ts @@ -0,0 +1,25 @@ +import type { GetAccountInfoApi } from '@solana/rpc-api'; +import type { Rpc } from '@solana/rpc-spec'; + +import { fetchSysvarLastRestartSlot, getSysvarLastRestartSlotCodec } from '../last-restart-slot'; +import { createLocalhostSolanaRpc } from './__setup__'; + +describe('last restart slot', () => { + let rpc: Rpc; + beforeEach(() => { + rpc = createLocalhostSolanaRpc(); + }); + it('decode', () => { + const lastRestartSlotState = new Uint8Array([119, 233, 246, 16, 0, 0, 0, 0]); + expect(getSysvarLastRestartSlotCodec().decode(lastRestartSlotState)).toMatchObject({ + lastRestartSlot: 284_617_079n, + }); + }); + it('fetch', async () => { + expect.assertions(1); + const lastRestartSlot = await fetchSysvarLastRestartSlot(rpc); + expect(lastRestartSlot).toMatchObject({ + lastRestartSlot: expect.any(BigInt), + }); + }); +}); diff --git a/packages/sysvars/src/__tests__/sysvar-test.ts b/packages/sysvars/src/__tests__/sysvar-test.ts index 09cb3c7cd53..efd368354b0 100644 --- a/packages/sysvars/src/__tests__/sysvar-test.ts +++ b/packages/sysvars/src/__tests__/sysvar-test.ts @@ -7,6 +7,7 @@ import { SYSVAR_CLOCK_ADDRESS, SYSVAR_EPOCH_SCHEDULE_ADDRESS, SYSVAR_FEES_ADDRESS, + SYSVAR_LAST_RESTART_SLOT_ADDRESS, } from '../sysvar'; import { createLocalhostSolanaRpc } from './__setup__'; @@ -88,4 +89,18 @@ describe('sysvar account', () => { }); }); // `Instructions` does not exist on-chain. + describe('last restart slot', () => { + it('fetch encoded', async () => { + expect.assertions(3); + await assertValidEncodedSysvarAccount(SYSVAR_LAST_RESTART_SLOT_ADDRESS); + }); + it('fetch JSON-parsed', async () => { + expect.assertions(3); + await assertValidJsonParsedSysvarAccount(SYSVAR_LAST_RESTART_SLOT_ADDRESS, { + data: { + lastRestartSlot: expect.any(BigInt), + }, + }); + }); + }); }); diff --git a/packages/sysvars/src/__typetests__/sysvar-typetest.ts b/packages/sysvars/src/__typetests__/sysvar-typetest.ts index 4e4ec657b56..b5818a79df0 100644 --- a/packages/sysvars/src/__typetests__/sysvar-typetest.ts +++ b/packages/sysvars/src/__typetests__/sysvar-typetest.ts @@ -6,6 +6,7 @@ import { fetchSysvarClock, type SysvarClock } from '../clock'; import { fetchSysvarEpochRewards, type SysvarEpochRewards } from '../epoch-rewards'; import { fetchSysvarEpochSchedule, type SysvarEpochSchedule } from '../epoch-schedule'; import { fetchSysvarFees, type SysvarFees } from '../fees'; +import { fetchSysvarLastRestartSlot, type SysvarLastRestartSlot } from '../last-restart-slot'; import { fetchEncodedSysvarAccount, fetchJsonParsedSysvarAccount, SYSVAR_CLOCK_ADDRESS } from '../sysvar'; const rpc = null as unknown as Parameters[0]; @@ -79,3 +80,11 @@ const rpc = null as unknown as Parameters[0]; // @ts-expect-error Returns a `SysvarFees`. fetchSysvarFees(rpc) satisfies Promise<{ foo: string }>; } + +// `fetchSysvarLastRestartSlot` +{ + // Returns a `SysvarLastRestartSlot`. + fetchSysvarLastRestartSlot(rpc) satisfies Promise; + // @ts-expect-error Returns a `SysvarLastRestartSlot`. + fetchSysvarLastRestartSlot(rpc) satisfies Promise<{ foo: string }>; +} diff --git a/packages/sysvars/src/index.ts b/packages/sysvars/src/index.ts index e10a2a90aed..24870c8623f 100644 --- a/packages/sysvars/src/index.ts +++ b/packages/sysvars/src/index.ts @@ -2,4 +2,5 @@ export * from './clock'; export * from './epoch-rewards'; export * from './epoch-schedule'; export * from './fees'; +export * from './last-restart-slot'; export * from './sysvar'; diff --git a/packages/sysvars/src/last-restart-slot.ts b/packages/sysvars/src/last-restart-slot.ts new file mode 100644 index 00000000000..77e988f6f59 --- /dev/null +++ b/packages/sysvars/src/last-restart-slot.ts @@ -0,0 +1,72 @@ +import { assertAccountExists, decodeAccount, type FetchAccountConfig } from '@solana/accounts'; +import { + combineCodec, + type FixedSizeCodec, + type FixedSizeDecoder, + type FixedSizeEncoder, + getStructDecoder, + getStructEncoder, + getU64Decoder, + getU64Encoder, +} from '@solana/codecs'; +import type { GetAccountInfoApi } from '@solana/rpc-api'; +import type { Rpc } from '@solana/rpc-spec'; +import type { Slot } from '@solana/rpc-types'; + +import { fetchEncodedSysvarAccount, SYSVAR_LAST_RESTART_SLOT_ADDRESS } from './sysvar'; + +type SysvarLastRestartSlotSize = 8; + +/** + * The `LastRestartSlot` sysvar. + * + * Information about the last restart slot (hard fork). + * + * The `LastRestartSlot` sysvar provides access to the last restart slot kept in the + * bank fork for the slot on the fork that executes the current transaction. + * In case there was no fork it returns `0`. + */ +export type SysvarLastRestartSlot = Readonly<{ + lastRestartSlot: Slot; +}>; + +export function getSysvarLastRestartSlotEncoder(): FixedSizeEncoder { + return getStructEncoder([['lastRestartSlot', getU64Encoder()]]) as FixedSizeEncoder< + SysvarLastRestartSlot, + SysvarLastRestartSlotSize + >; +} + +export function getSysvarLastRestartSlotDecoder(): FixedSizeDecoder { + return getStructDecoder([['lastRestartSlot', getU64Decoder()]]) as FixedSizeDecoder< + SysvarLastRestartSlot, + SysvarLastRestartSlotSize + >; +} + +export function getSysvarLastRestartSlotCodec(): FixedSizeCodec< + SysvarLastRestartSlot, + SysvarLastRestartSlot, + SysvarLastRestartSlotSize +> { + return combineCodec(getSysvarLastRestartSlotEncoder(), getSysvarLastRestartSlotDecoder()); +} + +/** + * Fetch the `LastRestartSlot` sysvar. + * + * Information about the last restart slot (hard fork). + * + * The `LastRestartSlot` sysvar provides access to the last restart slot kept in the + * bank fork for the slot on the fork that executes the current transaction. + * In case there was no fork it returns `0`. + */ +export async function fetchSysvarLastRestartSlot( + rpc: Rpc, + config?: FetchAccountConfig, +): Promise { + const account = await fetchEncodedSysvarAccount(rpc, SYSVAR_LAST_RESTART_SLOT_ADDRESS, config); + assertAccountExists(account); + const decoded = decodeAccount(account, getSysvarLastRestartSlotDecoder()); + return decoded.data; +} diff --git a/packages/sysvars/src/sysvar.ts b/packages/sysvars/src/sysvar.ts index 0f404e9ea99..4f1a7aa524f 100644 --- a/packages/sysvars/src/sysvar.ts +++ b/packages/sysvars/src/sysvar.ts @@ -20,13 +20,16 @@ export const SYSVAR_FEES_ADDRESS = 'SysvarFees111111111111111111111111111111111' as Address<'SysvarFees111111111111111111111111111111111'>; export const SYSVAR_INSTRUCTIONS_ADDRESS = 'Sysvar1nstructions1111111111111111111111111' as Address<'Sysvar1nstructions1111111111111111111111111'>; +export const SYSVAR_LAST_RESTART_SLOT_ADDRESS = + 'SysvarLastRestartS1ot1111111111111111111111' as Address<'SysvarLastRestartS1ot1111111111111111111111'>; type SysvarAddress = | typeof SYSVAR_CLOCK_ADDRESS | typeof SYSVAR_EPOCH_REWARDS_ADDRESS | typeof SYSVAR_EPOCH_SCHEDULE_ADDRESS | typeof SYSVAR_FEES_ADDRESS - | typeof SYSVAR_INSTRUCTIONS_ADDRESS; + | typeof SYSVAR_INSTRUCTIONS_ADDRESS + | typeof SYSVAR_LAST_RESTART_SLOT_ADDRESS; /** * Fetch an encoded sysvar account.