feat: add getAddressLookupTable method to Connection (#27127)
This commit is contained in:
parent
0ca8239ef7
commit
dcef8ec100
|
@ -0,0 +1,39 @@
|
|||
import * as BufferLayout from '@solana/buffer-layout';
|
||||
|
||||
export interface IAccountStateData {
|
||||
readonly typeIndex: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export type AccountType<TInputData extends IAccountStateData> = {
|
||||
/** The account type index (from solana upstream program) */
|
||||
index: number;
|
||||
/** The BufferLayout to use to build data */
|
||||
layout: BufferLayout.Layout<TInputData>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decode account data buffer using an AccountType
|
||||
* @internal
|
||||
*/
|
||||
export function decodeData<TAccountStateData extends IAccountStateData>(
|
||||
type: AccountType<TAccountStateData>,
|
||||
data: Uint8Array,
|
||||
): TAccountStateData {
|
||||
let decoded: TAccountStateData;
|
||||
try {
|
||||
decoded = type.layout.decode(data);
|
||||
} catch (err) {
|
||||
throw new Error('invalid instruction; ' + err);
|
||||
}
|
||||
|
||||
if (decoded.typeIndex !== type.index) {
|
||||
throw new Error(
|
||||
`invalid account data; account type mismatch ${decoded.typeIndex} != ${type.index}`,
|
||||
);
|
||||
}
|
||||
|
||||
return decoded;
|
||||
}
|
|
@ -24,7 +24,6 @@ import type {Struct} from 'superstruct';
|
|||
import {Client as RpcWebSocketClient} from 'rpc-websockets';
|
||||
import RpcClient from 'jayson/lib/client/browser';
|
||||
|
||||
import {URL} from './utils/url-impl';
|
||||
import {AgentManager} from './agent-manager';
|
||||
import {EpochSchedule} from './epoch-schedule';
|
||||
import {SendTransactionError, SolanaJSONRPCError} from './errors';
|
||||
|
@ -35,6 +34,7 @@ import {Signer} from './keypair';
|
|||
import {MS_PER_SLOT} from './timing';
|
||||
import {Transaction, TransactionStatus} from './transaction';
|
||||
import {Message} from './message';
|
||||
import {AddressLookupTableAccount} from './programs/address-lookup-table/state';
|
||||
import assert from './utils/assert';
|
||||
import {sleep} from './utils/sleep';
|
||||
import {toBuffer} from './utils/to-buffer';
|
||||
|
@ -43,6 +43,7 @@ import {
|
|||
TransactionExpiredTimeoutError,
|
||||
} from './transaction/expiry-custom-errors';
|
||||
import {makeWebsocketUrl} from './utils/makeWebsocketUrl';
|
||||
import {URL} from './utils/url-impl';
|
||||
import type {Blockhash} from './blockhash';
|
||||
import type {FeeCalculator} from './fee-calculator';
|
||||
import type {TransactionSignature} from './transaction';
|
||||
|
@ -4218,6 +4219,29 @@ export class Connection {
|
|||
return res.result;
|
||||
}
|
||||
|
||||
async getAddressLookupTable(
|
||||
accountKey: PublicKey,
|
||||
config?: GetAccountInfoConfig,
|
||||
): Promise<RpcResponseAndContext<AddressLookupTableAccount | null>> {
|
||||
const {context, value: accountInfo} = await this.getAccountInfoAndContext(
|
||||
accountKey,
|
||||
config,
|
||||
);
|
||||
|
||||
let value = null;
|
||||
if (accountInfo !== null) {
|
||||
value = new AddressLookupTableAccount({
|
||||
key: accountKey,
|
||||
state: AddressLookupTableAccount.deserialize(accountInfo.data),
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
context,
|
||||
value,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the contents of a Nonce account from the cluster, return with context
|
||||
*/
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import {toBufferLE} from 'bigint-buffer';
|
||||
import * as BufferLayout from '@solana/buffer-layout';
|
||||
|
||||
import * as Layout from '../layout';
|
||||
import {PublicKey} from '../publickey';
|
||||
import * as bigintLayout from '../utils/bigint';
|
||||
import {SystemProgram} from './system';
|
||||
import {TransactionInstruction} from '../transaction';
|
||||
import {decodeData, encodeData, IInstructionInputData} from '../instruction';
|
||||
import * as Layout from '../../layout';
|
||||
import {PublicKey} from '../../publickey';
|
||||
import * as bigintLayout from '../../utils/bigint';
|
||||
import {SystemProgram} from '../system';
|
||||
import {TransactionInstruction} from '../../transaction';
|
||||
import {decodeData, encodeData, IInstructionInputData} from '../../instruction';
|
||||
|
||||
export * from './state';
|
||||
|
||||
export type CreateLookupTableParams = {
|
||||
/** Account used to derive and control the new address lookup table. */
|
|
@ -0,0 +1,84 @@
|
|||
import * as BufferLayout from '@solana/buffer-layout';
|
||||
|
||||
import assert from '../../utils/assert';
|
||||
import * as Layout from '../../layout';
|
||||
import {PublicKey} from '../../publickey';
|
||||
import {u64} from '../../utils/bigint';
|
||||
import {decodeData} from '../../account-data';
|
||||
|
||||
export type AddressLookupTableState = {
|
||||
deactivationSlot: bigint;
|
||||
lastExtendedSlot: number;
|
||||
lastExtendedSlotStartIndex: number;
|
||||
authority?: PublicKey;
|
||||
addresses: Array<PublicKey>;
|
||||
};
|
||||
|
||||
export type AddressLookupTableAccountArgs = {
|
||||
key: PublicKey;
|
||||
state: AddressLookupTableState;
|
||||
};
|
||||
|
||||
/// The serialized size of lookup table metadata
|
||||
const LOOKUP_TABLE_META_SIZE = 56;
|
||||
|
||||
export class AddressLookupTableAccount {
|
||||
key: PublicKey;
|
||||
state: AddressLookupTableState;
|
||||
|
||||
constructor(args: AddressLookupTableAccountArgs) {
|
||||
this.key = args.key;
|
||||
this.state = args.state;
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
const U64_MAX = 2n ** 64n - 1n;
|
||||
return this.state.deactivationSlot === U64_MAX;
|
||||
}
|
||||
|
||||
static deserialize(accountData: Uint8Array): AddressLookupTableState {
|
||||
const meta = decodeData(LookupTableMetaLayout, accountData);
|
||||
|
||||
const serializedAddressesLen = accountData.length - LOOKUP_TABLE_META_SIZE;
|
||||
assert(serializedAddressesLen >= 0, 'lookup table is invalid');
|
||||
assert(serializedAddressesLen % 32 === 0, 'lookup table is invalid');
|
||||
|
||||
const numSerializedAddresses = serializedAddressesLen / 32;
|
||||
const {addresses} = BufferLayout.struct<{addresses: Array<Uint8Array>}>([
|
||||
BufferLayout.seq(Layout.publicKey(), numSerializedAddresses, 'addresses'),
|
||||
]).decode(accountData.slice(LOOKUP_TABLE_META_SIZE));
|
||||
|
||||
return {
|
||||
deactivationSlot: meta.deactivationSlot,
|
||||
lastExtendedSlot: meta.lastExtendedSlot,
|
||||
lastExtendedSlotStartIndex: meta.lastExtendedStartIndex,
|
||||
authority:
|
||||
meta.authority.length !== 0
|
||||
? new PublicKey(meta.authority[0])
|
||||
: undefined,
|
||||
addresses: addresses.map(address => new PublicKey(address)),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const LookupTableMetaLayout = {
|
||||
index: 1,
|
||||
layout: BufferLayout.struct<{
|
||||
typeIndex: number;
|
||||
deactivationSlot: bigint;
|
||||
lastExtendedSlot: number;
|
||||
lastExtendedStartIndex: number;
|
||||
authority: Array<Uint8Array>;
|
||||
}>([
|
||||
BufferLayout.u32('typeIndex'),
|
||||
u64('deactivationSlot'),
|
||||
BufferLayout.nu64('lastExtendedSlot'),
|
||||
BufferLayout.u8('lastExtendedStartIndex'),
|
||||
BufferLayout.u8(), // option
|
||||
BufferLayout.seq(
|
||||
Layout.publicKey(),
|
||||
BufferLayout.offset(BufferLayout.u8(), -1),
|
||||
'authority',
|
||||
),
|
||||
]),
|
||||
};
|
|
@ -18,6 +18,7 @@ import {
|
|||
sendAndConfirmTransaction,
|
||||
Keypair,
|
||||
Message,
|
||||
AddressLookupTableProgram,
|
||||
} from '../src';
|
||||
import invariant from '../src/utils/assert';
|
||||
import {MOCK_PORT, url} from './url';
|
||||
|
@ -4243,5 +4244,90 @@ describe('Connection', function () {
|
|||
const version = await connection.getVersion();
|
||||
expect(version['solana-core']).to.be.ok;
|
||||
}).timeout(20 * 1000);
|
||||
|
||||
it('getAddressLookupTable', async () => {
|
||||
const payer = Keypair.generate();
|
||||
|
||||
await helpers.airdrop({
|
||||
connection,
|
||||
address: payer.publicKey,
|
||||
amount: LAMPORTS_PER_SOL,
|
||||
});
|
||||
|
||||
const lookupTableAddresses = new Array(10)
|
||||
.fill(0)
|
||||
.map(() => Keypair.generate().publicKey);
|
||||
|
||||
const recentSlot = await connection.getSlot('finalized');
|
||||
const [createIx, lookupTableKey] =
|
||||
AddressLookupTableProgram.createLookupTable({
|
||||
recentSlot,
|
||||
payer: payer.publicKey,
|
||||
authority: payer.publicKey,
|
||||
});
|
||||
|
||||
// create, extend, and fetch
|
||||
{
|
||||
const transaction = new Transaction().add(createIx).add(
|
||||
AddressLookupTableProgram.extendLookupTable({
|
||||
lookupTable: lookupTableKey,
|
||||
addresses: lookupTableAddresses,
|
||||
authority: payer.publicKey,
|
||||
payer: payer.publicKey,
|
||||
}),
|
||||
);
|
||||
await helpers.processTransaction({
|
||||
connection,
|
||||
transaction,
|
||||
signers: [payer],
|
||||
commitment: 'processed',
|
||||
});
|
||||
|
||||
const lookupTableResponse = await connection.getAddressLookupTable(
|
||||
lookupTableKey,
|
||||
{
|
||||
commitment: 'processed',
|
||||
},
|
||||
);
|
||||
const lookupTableAccount = lookupTableResponse.value;
|
||||
if (!lookupTableAccount) {
|
||||
expect(lookupTableAccount).to.be.ok;
|
||||
return;
|
||||
}
|
||||
expect(lookupTableAccount.isActive()).to.be.true;
|
||||
expect(lookupTableAccount.state.authority).to.eql(payer.publicKey);
|
||||
expect(lookupTableAccount.state.addresses).to.eql(lookupTableAddresses);
|
||||
}
|
||||
|
||||
// freeze and fetch
|
||||
{
|
||||
const transaction = new Transaction().add(
|
||||
AddressLookupTableProgram.freezeLookupTable({
|
||||
lookupTable: lookupTableKey,
|
||||
authority: payer.publicKey,
|
||||
}),
|
||||
);
|
||||
await helpers.processTransaction({
|
||||
connection,
|
||||
transaction,
|
||||
signers: [payer],
|
||||
commitment: 'processed',
|
||||
});
|
||||
|
||||
const lookupTableResponse = await connection.getAddressLookupTable(
|
||||
lookupTableKey,
|
||||
{
|
||||
commitment: 'processed',
|
||||
},
|
||||
);
|
||||
const lookupTableAccount = lookupTableResponse.value;
|
||||
if (!lookupTableAccount) {
|
||||
expect(lookupTableAccount).to.be.ok;
|
||||
return;
|
||||
}
|
||||
expect(lookupTableAccount.isActive()).to.be.true;
|
||||
expect(lookupTableAccount.state.authority).to.be.undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue