[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,
} from "@solana/web3.js";
import {
batchIntoExecutorPayload,
batchIntoTransactions,
getSizeOfCompressedU16,
getSizeOfExecutorInstructions,
getSizeOfTransaction,
MultisigInstructionProgram,
MultisigParser,
MAX_EXECUTOR_PAYLOAD_SIZE,
} from "..";
import { PythMultisigInstruction } from "../multisig_transaction/PythMultisigInstruction";
it("Unit test compressed u16 size", async () => {
expect(getSizeOfCompressedU16(127)).toBe(1);
@ -115,10 +115,7 @@ it("Unit test for getSizeOfTransaction", async () => {
);
}
const txToSend: Transaction[] = batchIntoTransactions(
ixsToSend,
payer.publicKey
);
const txToSend: Transaction[] = batchIntoTransactions(ixsToSend);
expect(
txToSend.map((tx) => tx.instructions.length).reduce((a, b) => a + b)
).toBe(ixsToSend.length);
@ -135,4 +132,16 @@ it("Unit test for getSizeOfTransaction", async () => {
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 { 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 = {
instruction: TransactionInstruction;
authorityIndex?: number;
@ -43,7 +45,7 @@ export async function proposeInstructions(
): Promise<PublicKey> {
const msAccount = await squad.getMultisig(vault);
let ixToSend: TransactionInstruction[] = [];
const createProposal = ixToSend.push(
ixToSend.push(
await squad.buildCreateTransaction(
msAccount.publicKey,
msAccount.authorityIndex,
@ -60,12 +62,14 @@ export async function proposeInstructions(
if (!wormholeAddress) {
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(
squad,
vault,
newProposalAddress,
instructions[i],
batch,
i + 1,
wormholeAddress
);
@ -100,7 +104,7 @@ export async function proposeInstructions(
ixToSend.push(await squad.buildApproveTransaction(vault, newProposalAddress));
const txToSend = batchIntoTransactions(ixToSend, squad.wallet.publicKey);
const txToSend = batchIntoTransactions(ixToSend);
await new AnchorProvider(
squad.connection,
squad.wallet,
@ -113,12 +117,38 @@ export async function proposeInstructions(
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
*/
export function batchIntoTransactions(
instructions: TransactionInstruction[],
feePayer: PublicKey
instructions: TransactionInstruction[]
): Transaction[] {
let i = 0;
const txToSend: Transaction[] = [];
@ -131,7 +161,6 @@ export function batchIntoTransactions(
j += 1;
}
const tx = new Transaction();
tx.feePayer = feePayer;
for (let k = i; k < j - 1; k += 1) {
tx.add(instructions[k]);
}
@ -141,6 +170,16 @@ export function batchIntoTransactions(
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
*/
@ -170,6 +209,7 @@ export function getSizeOfTransaction(
ix.data.length
)
.reduce((a, b) => a + b, 0);
return (
1 +
signers.size * 64 +
@ -194,7 +234,7 @@ export function getSizeOfCompressedU16(n: number) {
* @param squad Squads client
* @param vault vault public key (the id of the multisig where these instructions should be proposed)
* @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 wormholeAddress address of the Wormhole bridge
* @returns an instruction to be proposed
@ -203,7 +243,7 @@ export async function wrapAsRemoteInstruction(
squad: Squads,
vault: PublicKey,
proposalAddress: PublicKey,
instruction: TransactionInstruction,
instructions: TransactionInstruction[],
instructionIndex: number,
wormholeAddress: PublicKey
): Promise<SquadInstruction> {
@ -225,9 +265,7 @@ export async function wrapAsRemoteInstruction(
provider
);
const buffer: Buffer = new ExecutePostedVaa("pythnet", [
instruction,
]).encode();
const buffer: Buffer = new ExecutePostedVaa("pythnet", instructions).encode();
const accounts = getPostMessageAccounts(wormholeAddress, emitter, messagePDA);