[xc-admin] batch wormhole messages (#615)

* Add

* Cleanup

* Add unit test
This commit is contained in:
guibescos 2023-02-21 11:29:03 +01:00 committed by GitHub
parent 6f49dfbb4b
commit ecb0e174d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 66 additions and 19 deletions

View File

@ -14,13 +14,13 @@ import {
TransactionInstruction, TransactionInstruction,
} from "@solana/web3.js"; } from "@solana/web3.js";
import { import {
batchIntoExecutorPayload,
batchIntoTransactions, batchIntoTransactions,
getSizeOfCompressedU16, getSizeOfCompressedU16,
getSizeOfExecutorInstructions,
getSizeOfTransaction, getSizeOfTransaction,
MultisigInstructionProgram, MAX_EXECUTOR_PAYLOAD_SIZE,
MultisigParser,
} from ".."; } from "..";
import { PythMultisigInstruction } from "../multisig_transaction/PythMultisigInstruction";
it("Unit test compressed u16 size", async () => { it("Unit test compressed u16 size", async () => {
expect(getSizeOfCompressedU16(127)).toBe(1); expect(getSizeOfCompressedU16(127)).toBe(1);
@ -115,10 +115,7 @@ it("Unit test for getSizeOfTransaction", async () => {
); );
} }
const txToSend: Transaction[] = batchIntoTransactions( const txToSend: Transaction[] = batchIntoTransactions(ixsToSend);
ixsToSend,
payer.publicKey
);
expect( expect(
txToSend.map((tx) => tx.instructions.length).reduce((a, b) => a + b) txToSend.map((tx) => tx.instructions.length).reduce((a, b) => a + b)
).toBe(ixsToSend.length); ).toBe(ixsToSend.length);
@ -135,4 +132,16 @@ it("Unit test for getSizeOfTransaction", async () => {
getSizeOfTransaction(tx.instructions) getSizeOfTransaction(tx.instructions)
); );
} }
const batches: TransactionInstruction[][] =
batchIntoExecutorPayload(ixsToSend);
expect(batches.map((batch) => batch.length).reduce((a, b) => a + b)).toBe(
ixsToSend.length
);
expect(
batches.every(
(batch) =>
getSizeOfExecutorInstructions(batch) <= MAX_EXECUTOR_PAYLOAD_SIZE
)
).toBeTruthy();
}); });

View File

@ -19,6 +19,8 @@ import {
import { ExecutePostedVaa } from "./governance_payload/ExecutePostedVaa"; import { ExecutePostedVaa } from "./governance_payload/ExecutePostedVaa";
import { OPS_KEY } from "./multisig"; import { OPS_KEY } from "./multisig";
export const MAX_EXECUTOR_PAYLOAD_SIZE = PACKET_DATA_SIZE - 687; // Bigger payloads won't fit in one addInstruction call when adding to the proposal
type SquadInstruction = { type SquadInstruction = {
instruction: TransactionInstruction; instruction: TransactionInstruction;
authorityIndex?: number; authorityIndex?: number;
@ -43,7 +45,7 @@ export async function proposeInstructions(
): Promise<PublicKey> { ): Promise<PublicKey> {
const msAccount = await squad.getMultisig(vault); const msAccount = await squad.getMultisig(vault);
let ixToSend: TransactionInstruction[] = []; let ixToSend: TransactionInstruction[] = [];
const createProposal = ixToSend.push( ixToSend.push(
await squad.buildCreateTransaction( await squad.buildCreateTransaction(
msAccount.publicKey, msAccount.publicKey,
msAccount.authorityIndex, msAccount.authorityIndex,
@ -60,12 +62,14 @@ export async function proposeInstructions(
if (!wormholeAddress) { if (!wormholeAddress) {
throw new Error("Need wormhole address"); throw new Error("Need wormhole address");
} }
for (let i = 0; i < instructions.length; i++) {
const batches = batchIntoExecutorPayload(instructions);
for (const [i, batch] of batches.entries()) {
const squadIx = await wrapAsRemoteInstruction( const squadIx = await wrapAsRemoteInstruction(
squad, squad,
vault, vault,
newProposalAddress, newProposalAddress,
instructions[i], batch,
i + 1, i + 1,
wormholeAddress wormholeAddress
); );
@ -100,7 +104,7 @@ export async function proposeInstructions(
ixToSend.push(await squad.buildApproveTransaction(vault, newProposalAddress)); ixToSend.push(await squad.buildApproveTransaction(vault, newProposalAddress));
const txToSend = batchIntoTransactions(ixToSend, squad.wallet.publicKey); const txToSend = batchIntoTransactions(ixToSend);
await new AnchorProvider( await new AnchorProvider(
squad.connection, squad.connection,
squad.wallet, squad.wallet,
@ -113,12 +117,38 @@ export async function proposeInstructions(
return newProposalAddress; return newProposalAddress;
} }
/**
* Batch instructions into batches for inclusion in a remote executor payload
*/
export function batchIntoExecutorPayload(
instructions: TransactionInstruction[]
): TransactionInstruction[][] {
let i = 0;
const batches: TransactionInstruction[][] = [];
while (i < instructions.length) {
let j = i + 2;
while (
j < instructions.length &&
getSizeOfExecutorInstructions(instructions.slice(i, j)) <=
MAX_EXECUTOR_PAYLOAD_SIZE
) {
j += 1;
}
const batch: TransactionInstruction[] = [];
for (let k = i; k < j - 1; k += 1) {
batch.push(instructions[k]);
}
i = j - 1;
batches.push(batch);
}
return batches;
}
/** /**
* Batch instructions into transactions * Batch instructions into transactions
*/ */
export function batchIntoTransactions( export function batchIntoTransactions(
instructions: TransactionInstruction[], instructions: TransactionInstruction[]
feePayer: PublicKey
): Transaction[] { ): Transaction[] {
let i = 0; let i = 0;
const txToSend: Transaction[] = []; const txToSend: Transaction[] = [];
@ -131,7 +161,6 @@ export function batchIntoTransactions(
j += 1; j += 1;
} }
const tx = new Transaction(); const tx = new Transaction();
tx.feePayer = feePayer;
for (let k = i; k < j - 1; k += 1) { for (let k = i; k < j - 1; k += 1) {
tx.add(instructions[k]); tx.add(instructions[k]);
} }
@ -141,6 +170,16 @@ export function batchIntoTransactions(
return txToSend; return txToSend;
} }
/** Get the size of instructions when serialized as in a remote executor payload */
export function getSizeOfExecutorInstructions(
instructions: TransactionInstruction[]
) {
return instructions
.map((ix) => {
return 32 + 4 + ix.keys.length * 34 + 4 + ix.data.length;
})
.reduce((a, b) => a + b);
}
/** /**
* Get the size of a transaction that would contain the provided array of instructions * Get the size of a transaction that would contain the provided array of instructions
*/ */
@ -170,6 +209,7 @@ export function getSizeOfTransaction(
ix.data.length ix.data.length
) )
.reduce((a, b) => a + b, 0); .reduce((a, b) => a + b, 0);
return ( return (
1 + 1 +
signers.size * 64 + signers.size * 64 +
@ -194,7 +234,7 @@ export function getSizeOfCompressedU16(n: number) {
* @param squad Squads client * @param squad Squads client
* @param vault vault public key (the id of the multisig where these instructions should be proposed) * @param vault vault public key (the id of the multisig where these instructions should be proposed)
* @param proposalAddress address of the proposal * @param proposalAddress address of the proposal
* @param instruction instruction to be wrapped in a Wormhole message * @param instructions instructions to be wrapped in a Wormhole message
* @param instructionIndex index of the instruction within the proposal * @param instructionIndex index of the instruction within the proposal
* @param wormholeAddress address of the Wormhole bridge * @param wormholeAddress address of the Wormhole bridge
* @returns an instruction to be proposed * @returns an instruction to be proposed
@ -203,7 +243,7 @@ export async function wrapAsRemoteInstruction(
squad: Squads, squad: Squads,
vault: PublicKey, vault: PublicKey,
proposalAddress: PublicKey, proposalAddress: PublicKey,
instruction: TransactionInstruction, instructions: TransactionInstruction[],
instructionIndex: number, instructionIndex: number,
wormholeAddress: PublicKey wormholeAddress: PublicKey
): Promise<SquadInstruction> { ): Promise<SquadInstruction> {
@ -225,9 +265,7 @@ export async function wrapAsRemoteInstruction(
provider provider
); );
const buffer: Buffer = new ExecutePostedVaa("pythnet", [ const buffer: Buffer = new ExecutePostedVaa("pythnet", instructions).encode();
instruction,
]).encode();
const accounts = getPostMessageAccounts(wormholeAddress, emitter, messagePDA); const accounts = getPostMessageAccounts(wormholeAddress, emitter, messagePDA);