Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ import {
TransactionInstruction,
} from "@solana/web3.js";
import {
batchIntoExecutorPayload,
batchIntoTransactions,
getSizeOfCompressedU16,
getSizeOfExecutorInstructions,
getSizeOfTransaction,
MultisigInstructionProgram,
MultisigParser,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Drive-by import cleanup

MAX_EXECUTOR_PAYLOAD_SIZE,
} from "..";
import { PythMultisigInstruction } from "../multisig_transaction/PythMultisigInstruction";

it("Unit test compressed u16 size", async () => {
expect(getSizeOfCompressedU16(127)).toBe(1);
Expand Down Expand Up @@ -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);
Expand All @@ -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();
});
62 changes: 50 additions & 12 deletions governance/xc_admin/packages/xc_admin_common/src/propose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
Expand All @@ -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
);
Expand Down Expand Up @@ -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,
Expand All @@ -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
Copy link
Contributor Author

@guibescos guibescos Feb 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Drive-by remove unused argument.

instructions: TransactionInstruction[]
): Transaction[] {
let i = 0;
const txToSend: Transaction[] = [];
Expand All @@ -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]);
}
Expand All @@ -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
*/
Expand Down Expand Up @@ -170,6 +209,7 @@ export function getSizeOfTransaction(
ix.data.length
)
.reduce((a, b) => a + b, 0);

return (
1 +
signers.size * 64 +
Expand All @@ -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
Expand All @@ -203,7 +243,7 @@ export async function wrapAsRemoteInstruction(
squad: Squads,
vault: PublicKey,
proposalAddress: PublicKey,
instruction: TransactionInstruction,
instructions: TransactionInstruction[],
instructionIndex: number,
wormholeAddress: PublicKey
): Promise<SquadInstruction> {
Expand All @@ -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);

Expand Down