From c159dca828146678f67794a1111b7968bd6187f9 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Wed, 31 Aug 2022 11:13:41 -0400 Subject: [PATCH] feat: implement message compilation using CompiledKeys --- web3.js/src/message/v0.ts | 47 ++++++++++++ web3.js/test/message-tests/v0.test.ts | 105 ++++++++++++++++++++++++++ 2 files changed, 152 insertions(+) diff --git a/web3.js/src/message/v0.ts b/web3.js/src/message/v0.ts index ea770df3a9b203..800ad162c163be 100644 --- a/web3.js/src/message/v0.ts +++ b/web3.js/src/message/v0.ts @@ -12,6 +12,10 @@ import {PublicKey, PUBLIC_KEY_LENGTH} from '../publickey'; import * as shortvec from '../utils/shortvec-encoding'; import assert from '../utils/assert'; import {PACKET_DATA_SIZE, VERSION_PREFIX_MASK} from '../transaction/constants'; +import {TransactionInstruction} from '../transaction'; +import {AddressLookupTableAccount} from '../programs'; +import {CompiledKeys} from './compiled-keys'; +import {AccountKeysFromLookups, MessageAccountKeys} from './account-keys'; /** * Message constructor arguments @@ -29,6 +33,13 @@ export type MessageV0Args = { addressTableLookups: MessageAddressTableLookup[]; }; +export type CompileV0Args = { + payerKey: PublicKey; + instructions: Array; + recentBlockhash: Blockhash; + addressLookupTableAccounts?: Array; +}; + export class MessageV0 { header: MessageHeader; staticAccountKeys: Array; @@ -48,6 +59,42 @@ export class MessageV0 { return 0; } + static compile(args: CompileV0Args): MessageV0 { + const compiledKeys = CompiledKeys.compile(args.instructions, args.payerKey); + + const addressTableLookups = new Array(); + const accountKeysFromLookups: AccountKeysFromLookups = { + writable: new Array(), + readonly: new Array(), + }; + const lookupTableAccounts = args.addressLookupTableAccounts || []; + for (const lookupTable of lookupTableAccounts) { + const extractResult = compiledKeys.extractTableLookup(lookupTable); + if (extractResult !== undefined) { + const [addressTableLookup, {writable, readonly}] = extractResult; + addressTableLookups.push(addressTableLookup); + accountKeysFromLookups.writable.push(...writable); + accountKeysFromLookups.readonly.push(...readonly); + } + } + + const [header, staticAccountKeys] = compiledKeys.getMessageComponents(); + const accountKeys = new MessageAccountKeys( + staticAccountKeys, + accountKeysFromLookups, + ); + const compiledInstructions = accountKeys.compileInstructions( + args.instructions, + ); + return new MessageV0({ + header, + staticAccountKeys, + recentBlockhash: args.recentBlockhash, + compiledInstructions, + addressTableLookups, + }); + } + serialize(): Uint8Array { const encodedStaticAccountKeysLength = Array(); shortvec.encodeLength( diff --git a/web3.js/test/message-tests/v0.test.ts b/web3.js/test/message-tests/v0.test.ts index 9cd3b189735b73..8538b64ccb5fb5 100644 --- a/web3.js/test/message-tests/v0.test.ts +++ b/web3.js/test/message-tests/v0.test.ts @@ -1,9 +1,114 @@ +import bs58 from 'bs58'; import {expect} from 'chai'; +import {sha256} from '@noble/hashes/sha256'; import {MessageV0} from '../../src/message'; +import {TransactionInstruction} from '../../src/transaction'; import {PublicKey} from '../../src/publickey'; +import {AddressLookupTableAccount} from '../../src/programs'; + +function createTestKeys(count: number): Array { + return new Array(count).fill(0).map(() => PublicKey.unique()); +} + +function createTestLookupTable( + addresses: Array, +): AddressLookupTableAccount { + const U64_MAX = 2n ** 64n - 1n; + return new AddressLookupTableAccount({ + key: PublicKey.unique(), + state: { + lastExtendedSlot: 0, + lastExtendedSlotStartIndex: 0, + deactivationSlot: U64_MAX, + authority: PublicKey.unique(), + addresses, + }, + }); +} describe('MessageV0', () => { + it('compile', () => { + const keys = createTestKeys(7); + const recentBlockhash = bs58.encode(sha256('test')); + const payerKey = keys[0]; + const instructions = [ + new TransactionInstruction({ + programId: keys[4], + keys: [ + {pubkey: keys[1], isSigner: true, isWritable: true}, + {pubkey: keys[2], isSigner: false, isWritable: false}, + {pubkey: keys[3], isSigner: false, isWritable: false}, + ], + data: Buffer.alloc(1), + }), + new TransactionInstruction({ + programId: keys[1], + keys: [ + {pubkey: keys[2], isSigner: true, isWritable: false}, + {pubkey: keys[3], isSigner: false, isWritable: true}, + ], + data: Buffer.alloc(2), + }), + new TransactionInstruction({ + programId: keys[3], + keys: [ + {pubkey: keys[5], isSigner: false, isWritable: true}, + {pubkey: keys[6], isSigner: false, isWritable: false}, + ], + data: Buffer.alloc(3), + }), + ]; + + const lookupTable = createTestLookupTable(keys); + const message = MessageV0.compile({ + payerKey, + recentBlockhash, + instructions, + addressLookupTableAccounts: [lookupTable], + }); + + expect(message.staticAccountKeys).to.eql([ + payerKey, // payer is first + keys[1], // other writable signer + keys[2], // sole readonly signer + keys[3], // sole writable non-signer + keys[4], // sole readonly non-signer + ]); + expect(message.header).to.eql({ + numRequiredSignatures: 3, + numReadonlySignedAccounts: 1, + numReadonlyUnsignedAccounts: 1, + }); + // only keys 5 and 6 are eligible to be referenced by a lookup table + // because they are not invoked and are not signers + expect(message.addressTableLookups).to.eql([ + { + accountKey: lookupTable.key, + writableIndexes: [5], + readonlyIndexes: [6], + }, + ]); + expect(message.compiledInstructions).to.eql([ + { + programIdIndex: 4, + accountKeyIndexes: [1, 2, 3], + data: new Uint8Array(1), + }, + { + programIdIndex: 1, + accountKeyIndexes: [2, 3], + data: new Uint8Array(2), + }, + { + programIdIndex: 3, + accountKeyIndexes: [5, 6], + data: new Uint8Array(3), + }, + ]); + expect(message.recentBlockhash).to.eq(recentBlockhash); + }); + it('serialize and deserialize', () => { const messageV0 = new MessageV0({ header: {