Skip to content

Commit

Permalink
refactor(experimental): sysvars package: recent blockhashes
Browse files Browse the repository at this point in the history
This commit introduces the `RecentBlockhashes` sysvar to the `@solana/sysvars` package.
  • Loading branch information
buffalojoec committed Mar 14, 2024
1 parent d607ab4 commit c58f445
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 1 deletion.
50 changes: 50 additions & 0 deletions packages/sysvars/src/__tests__/recent-blockhashes-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import type { GetAccountInfoApi } from '@solana/rpc-api';
import type { Rpc } from '@solana/rpc-spec';

import { fetchSysvarRecentBlockhashes, getSysvarRecentBlockhashesCodec } from '../recent-blockhashes';
import { createLocalhostSolanaRpc } from './__setup__';

describe('recent blockhashes', () => {
let rpc: Rpc<GetAccountInfoApi>;
beforeEach(() => {
rpc = createLocalhostSolanaRpc();
});
it('decode', () => {
// prettier-ignore
const recentBlockhashesState = new Uint8Array([
2, 0, 0, 0, // array length
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
134, 74, 2, 0, 0, 0, 0, 0, // lamportsPerSignature
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
134, 74, 2, 0, 0, 0, 0, 0, // lamportsPerSignature
]);
expect(getSysvarRecentBlockhashesCodec().decode(recentBlockhashesState)).toMatchObject([
{
blockhash: '4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi',
feeCalculator: {
lamportsPerSignature: 150_150n,
},
},
{
blockhash: '8qbHbw2BbbTHBW1sbeqakYXVKRQM8Ne7pLK7m6CVfeR',
feeCalculator: {
lamportsPerSignature: 150_150n,
},
},
]);
});
it('fetch', async () => {
expect.assertions(1);
const recentBlockhashes = await fetchSysvarRecentBlockhashes(rpc);
expect(recentBlockhashes).toMatchObject(
expect.arrayContaining([
{
blockhash: expect.any(String),
feeCalculator: {
lamportsPerSignature: expect.any(BigInt),
},
},
]),
);
});
});
20 changes: 20 additions & 0 deletions packages/sysvars/src/__tests__/sysvar-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
SYSVAR_EPOCH_SCHEDULE_ADDRESS,
SYSVAR_FEES_ADDRESS,
SYSVAR_LAST_RESTART_SLOT_ADDRESS,
SYSVAR_RECENT_BLOCKHASHES_ADDRESS,
} from '../sysvar';
import { createLocalhostSolanaRpc } from './__setup__';

Expand Down Expand Up @@ -103,4 +104,23 @@ describe('sysvar account', () => {
});
});
});
describe('recent blockhashes', () => {
it('fetch encoded', async () => {
expect.assertions(3);
await assertValidEncodedSysvarAccount(SYSVAR_RECENT_BLOCKHASHES_ADDRESS);
});
it('fetch JSON-parsed', async () => {
expect.assertions(3);
await assertValidJsonParsedSysvarAccount(SYSVAR_RECENT_BLOCKHASHES_ADDRESS, {
data: expect.arrayContaining([
{
blockhash: expect.any(String),
feeCalculator: {
lamportsPerSignature: expect.any(String), // JsonParsed converts to string
},
},
]),
});
});
});
});
9 changes: 9 additions & 0 deletions packages/sysvars/src/__typetests__/sysvar-typetest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { fetchSysvarEpochRewards, type SysvarEpochRewards } from '../epoch-rewar
import { fetchSysvarEpochSchedule, type SysvarEpochSchedule } from '../epoch-schedule';
import { fetchSysvarFees, type SysvarFees } from '../fees';
import { fetchSysvarLastRestartSlot, type SysvarLastRestartSlot } from '../last-restart-slot';
import { fetchSysvarRecentBlockhashes, type SysvarRecentBlockhashes } from '../recent-blockhashes';
import { fetchEncodedSysvarAccount, fetchJsonParsedSysvarAccount, SYSVAR_CLOCK_ADDRESS } from '../sysvar';

const rpc = null as unknown as Parameters<typeof fetchEncodedSysvarAccount>[0];
Expand Down Expand Up @@ -88,3 +89,11 @@ const rpc = null as unknown as Parameters<typeof fetchEncodedSysvarAccount>[0];
// @ts-expect-error Returns a `SysvarLastRestartSlot`.
fetchSysvarLastRestartSlot(rpc) satisfies Promise<{ foo: string }>;
}

// `fetchSysvarRecentBlockhashes`
{
// Returns a `SysvarRecentBlockhashes`.
fetchSysvarRecentBlockhashes(rpc) satisfies Promise<SysvarRecentBlockhashes>;
// @ts-expect-error Returns a `SysvarRecentBlockhashes`.
fetchSysvarRecentBlockhashes(rpc) satisfies Promise<{ foo: string }>;
}
1 change: 1 addition & 0 deletions packages/sysvars/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ export * from './epoch-rewards';
export * from './epoch-schedule';
export * from './fees';
export * from './last-restart-slot';
export * from './recent-blockhashes';
export * from './sysvar';
75 changes: 75 additions & 0 deletions packages/sysvars/src/recent-blockhashes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { assertAccountExists, decodeAccount, type FetchAccountConfig } from '@solana/accounts';
import {
combineCodec,
getArrayDecoder,
getArrayEncoder,
getStructDecoder,
getStructEncoder,
type VariableSizeCodec,
type VariableSizeDecoder,
type VariableSizeEncoder,
} from '@solana/codecs';
import type { GetAccountInfoApi } from '@solana/rpc-api';
import type { Rpc } from '@solana/rpc-spec';
import {
type Blockhash,
getBlockhashDecoder,
getBlockhashEncoder,
getLamportsDecoder,
getLamportsEncoder,
type LamportsUnsafeBeyond2Pow53Minus1,
} from '@solana/rpc-types';

import { fetchEncodedSysvarAccount, SYSVAR_RECENT_BLOCKHASHES_ADDRESS } from './sysvar';

type FeeCalculator = Readonly<{
lamportsPerSignature: LamportsUnsafeBeyond2Pow53Minus1;
}>;
type Entry = Readonly<{
blockhash: Blockhash;
feeCalculator: FeeCalculator;
}>;

/**
* The `RecentBlockhashes` sysvar.
*
* Information about recent blocks and their fee calculators.
*/
export type SysvarRecentBlockhashes = Entry[];

export function getSysvarRecentBlockhashesEncoder(): VariableSizeEncoder<SysvarRecentBlockhashes> {
return getArrayEncoder(
getStructEncoder([
['blockhash', getBlockhashEncoder()],
['feeCalculator', getStructEncoder([['lamportsPerSignature', getLamportsEncoder()]])],
]),
);
}

export function getSysvarRecentBlockhashesDecoder(): VariableSizeDecoder<SysvarRecentBlockhashes> {
return getArrayDecoder(
getStructDecoder([
['blockhash', getBlockhashDecoder()],
['feeCalculator', getStructDecoder([['lamportsPerSignature', getLamportsDecoder()]])],
]),
);
}

export function getSysvarRecentBlockhashesCodec(): VariableSizeCodec<SysvarRecentBlockhashes> {
return combineCodec(getSysvarRecentBlockhashesEncoder(), getSysvarRecentBlockhashesDecoder());
}

/**
* Fetch the `RecentBlockhashes` sysvar.
*
* Information about recent blocks and their fee calculators.
*/
export async function fetchSysvarRecentBlockhashes(
rpc: Rpc<GetAccountInfoApi>,
config?: FetchAccountConfig,
): Promise<SysvarRecentBlockhashes> {
const account = await fetchEncodedSysvarAccount(rpc, SYSVAR_RECENT_BLOCKHASHES_ADDRESS, config);
assertAccountExists(account);
const decoded = decodeAccount(account, getSysvarRecentBlockhashesDecoder());
return decoded.data;
}
5 changes: 4 additions & 1 deletion packages/sysvars/src/sysvar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,17 @@ export const SYSVAR_INSTRUCTIONS_ADDRESS =
'Sysvar1nstructions1111111111111111111111111' as Address<'Sysvar1nstructions1111111111111111111111111'>;
export const SYSVAR_LAST_RESTART_SLOT_ADDRESS =
'SysvarLastRestartS1ot1111111111111111111111' as Address<'SysvarLastRestartS1ot1111111111111111111111'>;
export const SYSVAR_RECENT_BLOCKHASHES_ADDRESS =
'SysvarRecentB1ockHashes11111111111111111111' as Address<'SysvarRecentB1ockHashes11111111111111111111'>;

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_LAST_RESTART_SLOT_ADDRESS;
| typeof SYSVAR_LAST_RESTART_SLOT_ADDRESS
| typeof SYSVAR_RECENT_BLOCKHASHES_ADDRESS;

/**
* Fetch an encoded sysvar account.
Expand Down

0 comments on commit c58f445

Please sign in to comment.