[xc-admin] batch wormhole messages (#615)
* Add * Cleanup * Add unit test
This commit is contained in:
parent
6f49dfbb4b
commit
ecb0e174d4
|
@ -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();
|
||||||
});
|
});
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue