feat: update `Connection` to support versioned transactions (#27068)
feat: update Connection to support versioned transactions
This commit is contained in:
parent
0eec25be1a
commit
292b2a1bfe
|
@ -32,8 +32,12 @@ import {NonceAccount} from './nonce-account';
|
|||
import {PublicKey} from './publickey';
|
||||
import {Signer} from './keypair';
|
||||
import {MS_PER_SLOT} from './timing';
|
||||
import {Transaction, TransactionStatus} from './transaction';
|
||||
import {Message} from './message';
|
||||
import {
|
||||
Transaction,
|
||||
TransactionStatus,
|
||||
TransactionVersion,
|
||||
} from './transaction';
|
||||
import {Message, MessageHeader, MessageV0, VersionedMessage} from './message';
|
||||
import {AddressLookupTableAccount} from './programs/address-lookup-table/state';
|
||||
import assert from './utils/assert';
|
||||
import {sleep} from './utils/sleep';
|
||||
|
@ -395,6 +399,32 @@ function notificationResultAndContext<T, U>(value: Struct<T, U>) {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
function versionedMessageFromResponse(
|
||||
version: TransactionVersion | undefined,
|
||||
response: MessageResponse,
|
||||
): VersionedMessage {
|
||||
if (version === 0) {
|
||||
return new MessageV0({
|
||||
header: response.header,
|
||||
staticAccountKeys: response.accountKeys.map(
|
||||
accountKey => new PublicKey(accountKey),
|
||||
),
|
||||
recentBlockhash: response.recentBlockhash,
|
||||
compiledInstructions: response.instructions.map(ix => ({
|
||||
programIdIndex: ix.programIdIndex,
|
||||
accountKeyIndexes: ix.accounts,
|
||||
data: bs58.decode(ix.data),
|
||||
})),
|
||||
addressTableLookups: response.addressTableLookups!,
|
||||
});
|
||||
} else {
|
||||
return new Message(response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The level of commitment desired when querying state
|
||||
* <pre>
|
||||
|
@ -457,6 +487,14 @@ export type GetBalanceConfig = {
|
|||
export type GetBlockConfig = {
|
||||
/** The level of finality desired */
|
||||
commitment?: Finality;
|
||||
};
|
||||
|
||||
/**
|
||||
* Configuration object for changing `getBlock` query behavior
|
||||
*/
|
||||
export type GetVersionedBlockConfig = {
|
||||
/** The level of finality desired */
|
||||
commitment?: Finality;
|
||||
/** The max transaction version to return in responses. If the requested transaction is a higher version, an error will be returned */
|
||||
maxSupportedTransactionVersion?: number;
|
||||
};
|
||||
|
@ -537,6 +575,14 @@ export type GetSlotLeaderConfig = {
|
|||
export type GetTransactionConfig = {
|
||||
/** The level of finality desired */
|
||||
commitment?: Finality;
|
||||
};
|
||||
|
||||
/**
|
||||
* Configuration object for changing `getTransaction` query behavior
|
||||
*/
|
||||
export type GetVersionedTransactionConfig = {
|
||||
/** The level of finality desired */
|
||||
commitment?: Finality;
|
||||
/** The max transaction version to return in responses. If the requested transaction is a higher version, an error will be returned */
|
||||
maxSupportedTransactionVersion?: number;
|
||||
};
|
||||
|
@ -869,6 +915,8 @@ export type ConfirmedTransactionMeta = {
|
|||
postTokenBalances?: Array<TokenBalance> | null;
|
||||
/** The error result of transaction processing */
|
||||
err: TransactionError | null;
|
||||
/** The collection of addresses loaded using address lookup tables */
|
||||
loadedAddresses?: LoadedAddresses;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -890,6 +938,38 @@ export type TransactionResponse = {
|
|||
blockTime?: number | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* A processed transaction from the RPC API
|
||||
*/
|
||||
export type VersionedTransactionResponse = {
|
||||
/** The slot during which the transaction was processed */
|
||||
slot: number;
|
||||
/** The transaction */
|
||||
transaction: {
|
||||
/** The transaction message */
|
||||
message: VersionedMessage;
|
||||
/** The transaction signatures */
|
||||
signatures: string[];
|
||||
};
|
||||
/** Metadata produced from the transaction */
|
||||
meta: ConfirmedTransactionMeta | null;
|
||||
/** The unix timestamp of when the transaction was processed */
|
||||
blockTime?: number | null;
|
||||
/** The transaction version */
|
||||
version?: TransactionVersion;
|
||||
};
|
||||
|
||||
/**
|
||||
* A processed transaction message from the RPC API
|
||||
*/
|
||||
type MessageResponse = {
|
||||
accountKeys: string[];
|
||||
header: MessageHeader;
|
||||
instructions: CompiledInstruction[];
|
||||
recentBlockhash: string;
|
||||
addressTableLookups?: ParsedAddressTableLookup[];
|
||||
};
|
||||
|
||||
/**
|
||||
* A confirmed transaction on the ledger
|
||||
*
|
||||
|
@ -942,6 +1022,18 @@ export type ParsedInstruction = {
|
|||
parsed: any;
|
||||
};
|
||||
|
||||
/**
|
||||
* A parsed address table lookup
|
||||
*/
|
||||
export type ParsedAddressTableLookup = {
|
||||
/** Address lookup table account key */
|
||||
accountKey: PublicKey;
|
||||
/** Parsed instruction info */
|
||||
writableIndexes: number[];
|
||||
/** Parsed instruction info */
|
||||
readonlyIndexes: number[];
|
||||
};
|
||||
|
||||
/**
|
||||
* A parsed transaction message
|
||||
*/
|
||||
|
@ -952,6 +1044,8 @@ export type ParsedMessage = {
|
|||
instructions: (ParsedInstruction | PartiallyDecodedInstruction)[];
|
||||
/** Recent blockhash */
|
||||
recentBlockhash: string;
|
||||
/** Address table lookups used to load additional accounts */
|
||||
addressTableLookups?: ParsedAddressTableLookup[] | null;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -983,6 +1077,8 @@ export type ParsedTransactionWithMeta = {
|
|||
meta: ParsedTransactionMeta | null;
|
||||
/** The unix timestamp of when the transaction was processed */
|
||||
blockTime?: number | null;
|
||||
/** The version of the transaction message */
|
||||
version?: TransactionVersion;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -1006,6 +1102,47 @@ export type BlockResponse = {
|
|||
};
|
||||
/** Metadata produced from the transaction */
|
||||
meta: ConfirmedTransactionMeta | null;
|
||||
/** The transaction version */
|
||||
version?: TransactionVersion;
|
||||
}>;
|
||||
/** Vector of block rewards */
|
||||
rewards?: Array<{
|
||||
/** Public key of reward recipient */
|
||||
pubkey: string;
|
||||
/** Reward value in lamports */
|
||||
lamports: number;
|
||||
/** Account balance after reward is applied */
|
||||
postBalance: number | null;
|
||||
/** Type of reward received */
|
||||
rewardType: string | null;
|
||||
}>;
|
||||
/** The unix timestamp of when the block was processed */
|
||||
blockTime: number | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* A processed block fetched from the RPC API
|
||||
*/
|
||||
export type VersionedBlockResponse = {
|
||||
/** Blockhash of this block */
|
||||
blockhash: Blockhash;
|
||||
/** Blockhash of this block's parent */
|
||||
previousBlockhash: Blockhash;
|
||||
/** Slot index of this block's parent */
|
||||
parentSlot: number;
|
||||
/** Vector of transactions with status meta and original message */
|
||||
transactions: Array<{
|
||||
/** The transaction */
|
||||
transaction: {
|
||||
/** The transaction message */
|
||||
message: VersionedMessage;
|
||||
/** The transaction signatures */
|
||||
signatures: string[];
|
||||
};
|
||||
/** Metadata produced from the transaction */
|
||||
meta: ConfirmedTransactionMeta | null;
|
||||
/** The transaction version */
|
||||
version?: TransactionVersion;
|
||||
}>;
|
||||
/** Vector of block rewards */
|
||||
rewards?: Array<{
|
||||
|
@ -1728,6 +1865,12 @@ const GetSignatureStatusesRpcResult = jsonRpcResultAndContext(
|
|||
*/
|
||||
const GetMinimumBalanceForRentExemptionRpcResult = jsonRpcResult(number());
|
||||
|
||||
const AddressTableLookupStruct = pick({
|
||||
accountKey: PublicKeyFromString,
|
||||
writableIndexes: array(number()),
|
||||
readonlyIndexes: array(number()),
|
||||
});
|
||||
|
||||
const ConfirmedTransactionResult = pick({
|
||||
signatures: array(string()),
|
||||
message: pick({
|
||||
|
@ -1745,6 +1888,7 @@ const ConfirmedTransactionResult = pick({
|
|||
}),
|
||||
),
|
||||
recentBlockhash: string(),
|
||||
addressTableLookups: optional(array(AddressTableLookupStruct)),
|
||||
}),
|
||||
});
|
||||
|
||||
|
@ -1805,6 +1949,7 @@ const ParsedConfirmedTransactionResult = pick({
|
|||
),
|
||||
instructions: array(ParsedOrRawInstruction),
|
||||
recentBlockhash: string(),
|
||||
addressTableLookups: optional(nullable(array(AddressTableLookupStruct))),
|
||||
}),
|
||||
});
|
||||
|
||||
|
@ -1874,6 +2019,8 @@ const ParsedConfirmedTransactionMetaResult = pick({
|
|||
loadedAddresses: optional(LoadedAddressesResult),
|
||||
});
|
||||
|
||||
const TransactionVersionStruct = union([literal(0), literal('legacy')]);
|
||||
|
||||
/**
|
||||
* Expected JSON RPC response for the "getBlock" message
|
||||
*/
|
||||
|
@ -1887,6 +2034,7 @@ const GetBlockRpcResult = jsonRpcResult(
|
|||
pick({
|
||||
transaction: ConfirmedTransactionResult,
|
||||
meta: nullable(ConfirmedTransactionMetaResult),
|
||||
version: optional(TransactionVersionStruct),
|
||||
}),
|
||||
),
|
||||
rewards: optional(
|
||||
|
@ -1962,6 +2110,7 @@ const GetTransactionRpcResult = jsonRpcResult(
|
|||
meta: ConfirmedTransactionMetaResult,
|
||||
blockTime: optional(nullable(number())),
|
||||
transaction: ConfirmedTransactionResult,
|
||||
version: optional(TransactionVersionStruct),
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
@ -1976,6 +2125,7 @@ const GetParsedTransactionRpcResult = jsonRpcResult(
|
|||
transaction: ParsedConfirmedTransactionResult,
|
||||
meta: nullable(ParsedConfirmedTransactionMetaResult),
|
||||
blockTime: optional(nullable(number())),
|
||||
version: optional(TransactionVersionStruct),
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
@ -3644,11 +3794,32 @@ export class Connection {
|
|||
|
||||
/**
|
||||
* Fetch a processed block from the cluster.
|
||||
*
|
||||
* @deprecated Instead, call `getBlock` using a `GetVersionedBlockConfig` by
|
||||
* setting the `maxSupportedTransactionVersion` property.
|
||||
*/
|
||||
async getBlock(
|
||||
slot: number,
|
||||
rawConfig?: GetBlockConfig,
|
||||
): Promise<BlockResponse | null> {
|
||||
): Promise<BlockResponse | null>;
|
||||
|
||||
/**
|
||||
* Fetch a processed block from the cluster.
|
||||
*/
|
||||
// eslint-disable-next-line no-dupe-class-members
|
||||
async getBlock(
|
||||
slot: number,
|
||||
rawConfig?: GetVersionedBlockConfig,
|
||||
): Promise<VersionedBlockResponse | null>;
|
||||
|
||||
/**
|
||||
* Fetch a processed block from the cluster.
|
||||
*/
|
||||
// eslint-disable-next-line no-dupe-class-members
|
||||
async getBlock(
|
||||
slot: number,
|
||||
rawConfig?: GetVersionedBlockConfig,
|
||||
): Promise<VersionedBlockResponse | null> {
|
||||
const {commitment, config} = extractCommitmentFromConfig(rawConfig);
|
||||
const args = this._buildArgsAtLeastConfirmed(
|
||||
[slot],
|
||||
|
@ -3668,16 +3839,14 @@ export class Connection {
|
|||
|
||||
return {
|
||||
...result,
|
||||
transactions: result.transactions.map(({transaction, meta}) => {
|
||||
const message = new Message(transaction.message);
|
||||
return {
|
||||
meta,
|
||||
transaction: {
|
||||
...transaction,
|
||||
message,
|
||||
},
|
||||
};
|
||||
}),
|
||||
transactions: result.transactions.map(({transaction, meta, version}) => ({
|
||||
meta,
|
||||
transaction: {
|
||||
...transaction,
|
||||
message: versionedMessageFromResponse(version, transaction.message),
|
||||
},
|
||||
version,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -3739,11 +3908,33 @@ export class Connection {
|
|||
|
||||
/**
|
||||
* Fetch a confirmed or finalized transaction from the cluster.
|
||||
*
|
||||
* @deprecated Instead, call `getTransaction` using a
|
||||
* `GetVersionedTransactionConfig` by setting the
|
||||
* `maxSupportedTransactionVersion` property.
|
||||
*/
|
||||
async getTransaction(
|
||||
signature: string,
|
||||
rawConfig?: GetTransactionConfig,
|
||||
): Promise<TransactionResponse | null> {
|
||||
): Promise<TransactionResponse | null>;
|
||||
|
||||
/**
|
||||
* Fetch a confirmed or finalized transaction from the cluster.
|
||||
*/
|
||||
// eslint-disable-next-line no-dupe-class-members
|
||||
async getTransaction(
|
||||
signature: string,
|
||||
rawConfig: GetVersionedTransactionConfig,
|
||||
): Promise<VersionedTransactionResponse | null>;
|
||||
|
||||
/**
|
||||
* Fetch a confirmed or finalized transaction from the cluster.
|
||||
*/
|
||||
// eslint-disable-next-line no-dupe-class-members
|
||||
async getTransaction(
|
||||
signature: string,
|
||||
rawConfig?: GetVersionedTransactionConfig,
|
||||
): Promise<VersionedTransactionResponse | null> {
|
||||
const {commitment, config} = extractCommitmentFromConfig(rawConfig);
|
||||
const args = this._buildArgsAtLeastConfirmed(
|
||||
[signature],
|
||||
|
@ -3764,7 +3955,10 @@ export class Connection {
|
|||
...result,
|
||||
transaction: {
|
||||
...result.transaction,
|
||||
message: new Message(result.transaction.message),
|
||||
message: versionedMessageFromResponse(
|
||||
result.version,
|
||||
result.transaction.message,
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -3774,8 +3968,8 @@ export class Connection {
|
|||
*/
|
||||
async getParsedTransaction(
|
||||
signature: TransactionSignature,
|
||||
commitmentOrConfig?: GetTransactionConfig | Finality,
|
||||
): Promise<ParsedConfirmedTransaction | null> {
|
||||
commitmentOrConfig?: GetVersionedTransactionConfig | Finality,
|
||||
): Promise<ParsedTransactionWithMeta | null> {
|
||||
const {commitment, config} =
|
||||
extractCommitmentFromConfig(commitmentOrConfig);
|
||||
const args = this._buildArgsAtLeastConfirmed(
|
||||
|
@ -3797,8 +3991,8 @@ export class Connection {
|
|||
*/
|
||||
async getParsedTransactions(
|
||||
signatures: TransactionSignature[],
|
||||
commitmentOrConfig?: GetTransactionConfig | Finality,
|
||||
): Promise<(ParsedConfirmedTransaction | null)[]> {
|
||||
commitmentOrConfig?: GetVersionedTransactionConfig | Finality,
|
||||
): Promise<(ParsedTransactionWithMeta | null)[]> {
|
||||
const {commitment, config} =
|
||||
extractCommitmentFromConfig(commitmentOrConfig);
|
||||
const batch = signatures.map(signature => {
|
||||
|
@ -3829,11 +4023,37 @@ export class Connection {
|
|||
/**
|
||||
* Fetch transaction details for a batch of confirmed transactions.
|
||||
* Similar to {@link getParsedTransactions} but returns a {@link TransactionResponse}.
|
||||
*
|
||||
* @deprecated Instead, call `getTransactions` using a
|
||||
* `GetVersionedTransactionConfig` by setting the
|
||||
* `maxSupportedTransactionVersion` property.
|
||||
*/
|
||||
async getTransactions(
|
||||
signatures: TransactionSignature[],
|
||||
commitmentOrConfig?: GetTransactionConfig | Finality,
|
||||
): Promise<(TransactionResponse | null)[]> {
|
||||
): Promise<(TransactionResponse | null)[]>;
|
||||
|
||||
/**
|
||||
* Fetch transaction details for a batch of confirmed transactions.
|
||||
* Similar to {@link getParsedTransactions} but returns a {@link
|
||||
* VersionedTransactionResponse}.
|
||||
*/
|
||||
// eslint-disable-next-line no-dupe-class-members
|
||||
async getTransactions(
|
||||
signatures: TransactionSignature[],
|
||||
commitmentOrConfig: GetVersionedTransactionConfig | Finality,
|
||||
): Promise<(VersionedTransactionResponse | null)[]>;
|
||||
|
||||
/**
|
||||
* Fetch transaction details for a batch of confirmed transactions.
|
||||
* Similar to {@link getParsedTransactions} but returns a {@link
|
||||
* VersionedTransactionResponse}.
|
||||
*/
|
||||
// eslint-disable-next-line no-dupe-class-members
|
||||
async getTransactions(
|
||||
signatures: TransactionSignature[],
|
||||
commitmentOrConfig: GetVersionedTransactionConfig | Finality,
|
||||
): Promise<(VersionedTransactionResponse | null)[]> {
|
||||
const {commitment, config} =
|
||||
extractCommitmentFromConfig(commitmentOrConfig);
|
||||
const batch = signatures.map(signature => {
|
||||
|
@ -3862,7 +4082,10 @@ export class Connection {
|
|||
...result,
|
||||
transaction: {
|
||||
...result.transaction,
|
||||
message: new Message(result.transaction.message),
|
||||
message: versionedMessageFromResponse(
|
||||
result.version,
|
||||
result.transaction.message,
|
||||
),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
@ -3256,7 +3256,6 @@ describe('Connection', function () {
|
|||
11111,
|
||||
);
|
||||
|
||||
console.log('create mint');
|
||||
const mintPubkey2 = await splToken.createMint(
|
||||
connection as any,
|
||||
payerKeypair,
|
||||
|
@ -4249,30 +4248,44 @@ describe('Connection', function () {
|
|||
expect(version['solana-core']).to.be.ok;
|
||||
}).timeout(20 * 1000);
|
||||
|
||||
it('getAddressLookupTable', async () => {
|
||||
let lookupTableKey: PublicKey;
|
||||
const lookupTableAddresses = new Array(10)
|
||||
.fill(0)
|
||||
.map(() => Keypair.generate().publicKey);
|
||||
|
||||
describe('address lookup table program', () => {
|
||||
const connection = new Connection(url);
|
||||
const payer = Keypair.generate();
|
||||
|
||||
await helpers.airdrop({
|
||||
connection,
|
||||
address: payer.publicKey,
|
||||
amount: LAMPORTS_PER_SOL,
|
||||
before(async () => {
|
||||
await helpers.airdrop({
|
||||
connection,
|
||||
address: payer.publicKey,
|
||||
amount: 10 * LAMPORTS_PER_SOL,
|
||||
});
|
||||
});
|
||||
|
||||
const lookupTableAddresses = new Array(10)
|
||||
.fill(0)
|
||||
.map(() => Keypair.generate().publicKey);
|
||||
it('createLookupTable', async () => {
|
||||
const recentSlot = await connection.getSlot('finalized');
|
||||
|
||||
const recentSlot = await connection.getSlot('finalized');
|
||||
const [createIx, lookupTableKey] =
|
||||
AddressLookupTableProgram.createLookupTable({
|
||||
recentSlot,
|
||||
payer: payer.publicKey,
|
||||
authority: payer.publicKey,
|
||||
let createIx: TransactionInstruction;
|
||||
[createIx, lookupTableKey] =
|
||||
AddressLookupTableProgram.createLookupTable({
|
||||
recentSlot,
|
||||
payer: payer.publicKey,
|
||||
authority: payer.publicKey,
|
||||
});
|
||||
|
||||
await helpers.processTransaction({
|
||||
connection,
|
||||
transaction: new Transaction().add(createIx),
|
||||
signers: [payer],
|
||||
commitment: 'processed',
|
||||
});
|
||||
});
|
||||
|
||||
// create, extend, and fetch
|
||||
{
|
||||
const transaction = new Transaction().add(createIx).add(
|
||||
it('extendLookupTable', async () => {
|
||||
const transaction = new Transaction().add(
|
||||
AddressLookupTableProgram.extendLookupTable({
|
||||
lookupTable: lookupTableKey,
|
||||
addresses: lookupTableAddresses,
|
||||
|
@ -4280,44 +4293,32 @@ describe('Connection', function () {
|
|||
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
|
||||
{
|
||||
it('freezeLookupTable', async () => {
|
||||
const transaction = new Transaction().add(
|
||||
AddressLookupTableProgram.freezeLookupTable({
|
||||
lookupTable: lookupTableKey,
|
||||
authority: payer.publicKey,
|
||||
}),
|
||||
);
|
||||
|
||||
await helpers.processTransaction({
|
||||
connection,
|
||||
transaction,
|
||||
signers: [payer],
|
||||
commitment: 'processed',
|
||||
});
|
||||
});
|
||||
|
||||
it('getAddressLookupTable', async () => {
|
||||
const lookupTableResponse = await connection.getAddressLookupTable(
|
||||
lookupTableKey,
|
||||
{
|
||||
|
@ -4331,50 +4332,31 @@ describe('Connection', function () {
|
|||
}
|
||||
expect(lookupTableAccount.isActive()).to.be.true;
|
||||
expect(lookupTableAccount.state.authority).to.be.undefined;
|
||||
}
|
||||
expect(lookupTableAccount.state.addresses).to.eql(lookupTableAddresses);
|
||||
});
|
||||
});
|
||||
|
||||
it('sendRawTransaction with v0 transaction', async () => {
|
||||
describe('v0 transaction', () => {
|
||||
const connection = new Connection(url);
|
||||
const payer = Keypair.generate();
|
||||
|
||||
await helpers.airdrop({
|
||||
connection,
|
||||
address: payer.publicKey,
|
||||
amount: 10 * LAMPORTS_PER_SOL,
|
||||
before(async () => {
|
||||
await helpers.airdrop({
|
||||
connection,
|
||||
address: payer.publicKey,
|
||||
amount: 10 * LAMPORTS_PER_SOL,
|
||||
});
|
||||
});
|
||||
|
||||
const lookupTableAddresses = [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 lookup table
|
||||
{
|
||||
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',
|
||||
});
|
||||
|
||||
// wait for lookup table to be usable
|
||||
before(async () => {
|
||||
const lookupTableResponse = await connection.getAddressLookupTable(
|
||||
lookupTableKey,
|
||||
{
|
||||
commitment: 'processed',
|
||||
},
|
||||
);
|
||||
|
||||
const lookupTableAccount = lookupTableResponse.value;
|
||||
if (!lookupTableAccount) {
|
||||
expect(lookupTableAccount).to.be.ok;
|
||||
|
@ -4383,7 +4365,7 @@ describe('Connection', function () {
|
|||
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
const latestSlot = await connection.getSlot('processed');
|
||||
const latestSlot = await connection.getSlot('confirmed');
|
||||
if (latestSlot > lookupTableAccount.state.lastExtendedSlot) {
|
||||
break;
|
||||
} else {
|
||||
|
@ -4391,15 +4373,23 @@ describe('Connection', function () {
|
|||
await sleep(500);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// create, serialize, send and confirm versioned transaction
|
||||
{
|
||||
let signature;
|
||||
let addressTableLookups;
|
||||
it('send and confirm', async () => {
|
||||
const {blockhash, lastValidBlockHeight} =
|
||||
await connection.getLatestBlockhash();
|
||||
const transferIxData = encodeData(SYSTEM_INSTRUCTION_LAYOUTS.Transfer, {
|
||||
lamports: BigInt(LAMPORTS_PER_SOL),
|
||||
});
|
||||
addressTableLookups = [
|
||||
{
|
||||
accountKey: lookupTableKey,
|
||||
writableIndexes: [0],
|
||||
readonlyIndexes: [],
|
||||
},
|
||||
];
|
||||
const transaction = new VersionedTransaction(
|
||||
new MessageV0({
|
||||
header: {
|
||||
|
@ -4416,20 +4406,14 @@ describe('Connection', function () {
|
|||
data: transferIxData,
|
||||
},
|
||||
],
|
||||
addressTableLookups: [
|
||||
{
|
||||
accountKey: lookupTableKey,
|
||||
writableIndexes: [0],
|
||||
readonlyIndexes: [],
|
||||
},
|
||||
],
|
||||
addressTableLookups,
|
||||
}),
|
||||
);
|
||||
transaction.sign([payer]);
|
||||
const signature = bs58.encode(transaction.signatures[0]);
|
||||
signature = bs58.encode(transaction.signatures[0]);
|
||||
const serializedTransaction = transaction.serialize();
|
||||
await connection.sendRawTransaction(serializedTransaction, {
|
||||
preflightCommitment: 'processed',
|
||||
preflightCommitment: 'confirmed',
|
||||
});
|
||||
|
||||
await connection.confirmTransaction(
|
||||
|
@ -4438,16 +4422,106 @@ describe('Connection', function () {
|
|||
blockhash,
|
||||
lastValidBlockHeight,
|
||||
},
|
||||
'processed',
|
||||
'confirmed',
|
||||
);
|
||||
|
||||
const transferToKey = lookupTableAddresses[0];
|
||||
const transferToAccount = await connection.getAccountInfo(
|
||||
transferToKey,
|
||||
'processed',
|
||||
'confirmed',
|
||||
);
|
||||
expect(transferToAccount?.lamports).to.be.eq(LAMPORTS_PER_SOL);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('getTransaction (failure)', async () => {
|
||||
await expect(
|
||||
connection.getTransaction(signature, {
|
||||
commitment: 'confirmed',
|
||||
}),
|
||||
).to.be.rejectedWith(
|
||||
'failed to get transaction: Transaction version (0) is not supported',
|
||||
);
|
||||
});
|
||||
|
||||
let transactionSlot;
|
||||
it('getTransaction', async () => {
|
||||
// fetch v0 transaction
|
||||
const fetchedTransaction = await connection.getTransaction(signature, {
|
||||
commitment: 'confirmed',
|
||||
maxSupportedTransactionVersion: 0,
|
||||
});
|
||||
if (fetchedTransaction === null) {
|
||||
expect(fetchedTransaction).to.not.be.null;
|
||||
return;
|
||||
}
|
||||
transactionSlot = fetchedTransaction.slot;
|
||||
expect(fetchedTransaction.version).to.eq(0);
|
||||
expect(fetchedTransaction.meta?.loadedAddresses).to.eql({
|
||||
readonly: [],
|
||||
writable: [lookupTableAddresses[0]],
|
||||
});
|
||||
expect(
|
||||
fetchedTransaction.transaction.message.addressTableLookups,
|
||||
).to.eql(addressTableLookups);
|
||||
});
|
||||
|
||||
it('getParsedTransaction (failure)', async () => {
|
||||
await expect(
|
||||
connection.getParsedTransaction(signature, {
|
||||
commitment: 'confirmed',
|
||||
}),
|
||||
).to.be.rejectedWith(
|
||||
'failed to get transaction: Transaction version (0) is not supported',
|
||||
);
|
||||
});
|
||||
|
||||
it('getParsedTransaction', async () => {
|
||||
const parsedTransaction = await connection.getParsedTransaction(
|
||||
signature,
|
||||
{
|
||||
commitment: 'confirmed',
|
||||
maxSupportedTransactionVersion: 0,
|
||||
},
|
||||
);
|
||||
expect(parsedTransaction).to.not.be.null;
|
||||
expect(parsedTransaction?.version).to.eq(0);
|
||||
expect(parsedTransaction?.meta?.loadedAddresses).to.eql({
|
||||
readonly: [],
|
||||
writable: [lookupTableAddresses[0]],
|
||||
});
|
||||
expect(
|
||||
parsedTransaction?.transaction.message.addressTableLookups,
|
||||
).to.eql(addressTableLookups);
|
||||
});
|
||||
|
||||
it('getBlock (failure)', async () => {
|
||||
await expect(
|
||||
connection.getBlock(transactionSlot, {
|
||||
maxSupportedTransactionVersion: undefined,
|
||||
commitment: 'confirmed',
|
||||
}),
|
||||
).to.be.rejectedWith(
|
||||
'failed to get confirmed block: Transaction version (0) is not supported',
|
||||
);
|
||||
});
|
||||
|
||||
it('getBlock', async () => {
|
||||
const block = await connection.getBlock(transactionSlot, {
|
||||
maxSupportedTransactionVersion: 0,
|
||||
commitment: 'confirmed',
|
||||
});
|
||||
expect(block).to.not.be.null;
|
||||
if (block === null) throw new Error(); // unreachable
|
||||
|
||||
let foundTx = false;
|
||||
for (const tx of block.transactions) {
|
||||
if (tx.transaction.signatures[0] === signature) {
|
||||
foundTx = true;
|
||||
expect(tx.version).to.eq(0);
|
||||
}
|
||||
}
|
||||
expect(foundTx).to.be.true;
|
||||
});
|
||||
}).timeout(5 * 1000);
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue