Skip to content

Commit

Permalink
feat: implement message compilation using CompiledKeys
Browse files Browse the repository at this point in the history
  • Loading branch information
jstarry committed Sep 1, 2022
1 parent d8829f3 commit c159dca
Show file tree
Hide file tree
Showing 2 changed files with 152 additions and 0 deletions.
47 changes: 47 additions & 0 deletions web3.js/src/message/v0.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -29,6 +33,13 @@ export type MessageV0Args = {
addressTableLookups: MessageAddressTableLookup[];
};

export type CompileV0Args = {
payerKey: PublicKey;
instructions: Array<TransactionInstruction>;
recentBlockhash: Blockhash;
addressLookupTableAccounts?: Array<AddressLookupTableAccount>;
};

export class MessageV0 {
header: MessageHeader;
staticAccountKeys: Array<PublicKey>;
Expand All @@ -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<MessageAddressTableLookup>();
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<number>();
shortvec.encodeLength(
Expand Down
105 changes: 105 additions & 0 deletions web3.js/test/message-tests/v0.test.ts
Original file line number Diff line number Diff line change
@@ -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<PublicKey> {
return new Array(count).fill(0).map(() => PublicKey.unique());
}

function createTestLookupTable(
addresses: Array<PublicKey>,
): 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: {
Expand Down

0 comments on commit c159dca

Please sign in to comment.