Skip to content

Commit

Permalink
solana: populate an address lookup table to reduce tx size
Browse files Browse the repository at this point in the history
  • Loading branch information
kcsongor authored and johnsaigle committed Jun 6, 2024
1 parent 5bb6ffb commit 547ca80
Show file tree
Hide file tree
Showing 13 changed files with 880 additions and 17 deletions.
9 changes: 9 additions & 0 deletions sdk/__tests__/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,15 @@ async function deploySolana(ctx: Ctx): Promise<Ctx> {
await signSendWait(ctx.context, initTxs, signer);
console.log("Initialized ntt at", manager.program.programId.toString());

// NOTE: this is a hack. The next instruction will fail if we don't wait
// here, because the address lookup table is not yet available, despite
// the transaction having been confirmed.
// Looks like a bug, but I haven't investigated further. In practice, this
// won't be an issue, becase the address lookup table will have been
// created well before anyone is trying to use it, but we might want to be
// mindful in the deploy script too.
await new Promise((resolve) => setTimeout(resolve, 400));

const registrTxs = manager.registerTransceiver({
payer: keypair,
owner: keypair,
Expand Down
138 changes: 134 additions & 4 deletions sdk/solana/src/ntt.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { Program } from "@coral-xyz/anchor";
import { Program, web3 } from "@coral-xyz/anchor";
import * as splToken from "@solana/spl-token";
import {
createAssociatedTokenAccountInstruction,
getAssociatedTokenAddressSync,
} from "@solana/spl-token";
import {
AddressLookupTableAccount,
AddressLookupTableProgram,
Connection,
Keypair,
PublicKey,
Expand Down Expand Up @@ -71,6 +73,7 @@ export class SolanaNtt<N extends Network, C extends SolanaChains>
program: Program<NttBindings.NativeTokenTransfer>;
config?: NttBindings.Config;
quoter?: NttQuoter<N, C>;
addressLookupTable?: AddressLookupTableAccount;

constructor(
readonly network: N,
Expand Down Expand Up @@ -263,6 +266,100 @@ export class SolanaNtt<N extends Network, C extends SolanaChains>
{ transaction: tx, signers: [] },
"Ntt.Initialize"
);

yield* this.initializeOrUpdateLUT({ payer: args.payer });
}

// This function should be called after each upgrade. If there's nothing to
// do, it won't actually submit a transaction, so it's cheap to call.
async *initializeOrUpdateLUT(args: { payer: Keypair }) {
// TODO: find a more robust way of fetching a recent slot
const slot = (await this.program.provider.connection.getSlot()) - 1;

const [_, lutAddress] = web3.AddressLookupTableProgram.createLookupTable({
authority: this.pdas.lutAuthority(),
payer: args.payer.publicKey,
recentSlot: slot,
});

const whAccs = utils.getWormholeDerivedAccounts(
this.program.programId,
this.core.address
);
const config = await this.getConfig();

const entries = {
config: this.pdas.configAccount(),
custody: config.custody,
tokenProgram: config.tokenProgram,
mint: config.mint,
tokenAuthority: this.pdas.tokenAuthority(),
outboxRateLimit: this.pdas.outboxRateLimitAccount(),
wormhole: {
bridge: whAccs.wormholeBridge,
feeCollector: whAccs.wormholeFeeCollector,
sequence: whAccs.wormholeSequence,
program: this.core.address,
systemProgram: SystemProgram.programId,
clock: web3.SYSVAR_CLOCK_PUBKEY,
rent: web3.SYSVAR_RENT_PUBKEY,
},
};

// collect all pubkeys in entries recursively
const collectPubkeys = (obj: any): Array<PublicKey> => {
const pubkeys = new Array<PublicKey>();
for (const key in obj) {
const value = obj[key];
if (value instanceof PublicKey) {
pubkeys.push(value);
} else if (typeof value === "object") {
pubkeys.push(...collectPubkeys(value));
}
}
return pubkeys;
};
const pubkeys = collectPubkeys(entries).map((pk) => pk.toBase58());

var existingLut: web3.AddressLookupTableAccount | null = null;
try {
existingLut = await this.getAddressLookupTable(false);
} catch {
// swallow errors here, it just means that lut doesn't exist
}

if (existingLut !== null) {
const existingPubkeys =
existingLut.state.addresses?.map((a) => a.toBase58()) ?? [];

// if pubkeys contains keys that are not in the existing LUT, we need to
// add them to the LUT
const missingPubkeys = pubkeys.filter(
(pk) => !existingPubkeys.includes(pk)
);

if (missingPubkeys.length === 0) {
return existingLut;
}
}

const ix = await this.program.methods
.initializeLut(new BN(slot))
.accountsStrict({
payer: args.payer.publicKey,
authority: this.pdas.lutAuthority(),
lutAddress,
lut: this.pdas.lutAccount(),
lutProgram: AddressLookupTableProgram.programId,
systemProgram: SystemProgram.programId,
entries,
})
.instruction();

const tx = new Transaction().add(ix);
tx.feePayer = args.payer.publicKey;

yield this.createUnsignedTx({ transaction: tx }, "Ntt.InitializeLUT");
}

async *registerTransceiver(args: {
Expand Down Expand Up @@ -294,7 +391,7 @@ export class SolanaNtt<N extends Network, C extends SolanaChains>
);
const broadcastIx = await this.program.methods
.broadcastWormholeId()
.accounts({
.accountsStrict({
payer: args.payer.publicKey,
config: this.pdas.configAccount(),
mint: config.mint,
Expand All @@ -306,6 +403,8 @@ export class SolanaNtt<N extends Network, C extends SolanaChains>
sequence: whAccs.wormholeSequence,
program: this.core.address,
systemProgram: SystemProgram.programId,
clock: web3.SYSVAR_CLOCK_PUBKEY,
rent: web3.SYSVAR_RENT_PUBKEY,
},
})
.instruction();
Expand Down Expand Up @@ -346,7 +445,7 @@ export class SolanaNtt<N extends Network, C extends SolanaChains>
.instruction(),
this.program.methods
.broadcastWormholePeer({ chainId: toChainId(peer.chain) })
.accounts({
.accountsStrict({
payer: sender,
config: this.pdas.configAccount(),
peer: this.pdas.transceiverPeerAccount(peer.chain),
Expand All @@ -357,6 +456,9 @@ export class SolanaNtt<N extends Network, C extends SolanaChains>
feeCollector: whAccs.wormholeFeeCollector,
sequence: whAccs.wormholeSequence,
program: this.core.address,
clock: web3.SYSVAR_CLOCK_PUBKEY,
rent: web3.SYSVAR_RENT_PUBKEY,
systemProgram: SystemProgram.programId,
},
})
.instruction(),
Expand Down Expand Up @@ -751,7 +853,7 @@ export class SolanaNtt<N extends Network, C extends SolanaChains>
.releaseWormholeOutbound({
revertOnDelay: args.revertOnDelay,
})
.accounts({
.accountsStrict({
payer: args.payer,
config: { config: this.pdas.configAccount() },
outboxItem: args.outboxItem,
Expand All @@ -764,6 +866,8 @@ export class SolanaNtt<N extends Network, C extends SolanaChains>
sequence: whAccs.wormholeSequence,
program: this.core.address,
systemProgram: SystemProgram.programId,
clock: web3.SYSVAR_CLOCK_PUBKEY,
rent: web3.SYSVAR_RENT_PUBKEY,
},
})
.instruction();
Expand Down Expand Up @@ -943,6 +1047,32 @@ export class SolanaNtt<N extends Network, C extends SolanaChains>
}
}

async getAddressLookupTable(
useCache = true
): Promise<AddressLookupTableAccount> {
if (!useCache || !this.addressLookupTable) {
const lut = await this.program.account.lut.fetchNullable(
this.pdas.lutAccount()
);
if (!lut)
throw new Error(
"Address lookup table not found. Did you forget to call initializeLUT?"
);

const response = await this.connection.getAddressLookupTable(lut.address);
if (response.value === null) throw new Error("Could not fetch LUT");

this.addressLookupTable = response.value;
}

if (!this.addressLookupTable)
throw new Error(
"Address lookup table not found. Did you forget to call initializeLUT?"
);

return this.addressLookupTable;
}

createUnsignedTx(
txReq: SolanaTransaction,
description: string,
Expand Down
4 changes: 4 additions & 0 deletions sdk/solana/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ export const nttAddresses = (programId: PublicKeyInitData) => {
derivePda(["transceiver_message", chainToBytes(chain), id], programId);
const wormholeMessageAccount = (outboxItem: PublicKey): PublicKey =>
derivePda(["message", outboxItem.toBytes()], programId);
const lutAccount = (): PublicKey => derivePda("lut", programId);
const lutAuthority = (): PublicKey => derivePda("lut_authority", programId);
const sessionAuthority = (sender: PublicKey, args: TransferArgs): PublicKey =>
derivePda(
[
Expand Down Expand Up @@ -142,6 +144,8 @@ export const nttAddresses = (programId: PublicKeyInitData) => {
transceiverPeerAccount,
transceiverMessageAccount,
registeredTransceiver,
lutAccount,
lutAuthority,
};
};

Expand Down
1 change: 1 addition & 0 deletions solana/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions solana/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ anchor-spl = "0.29.0"
solana-program = "=1.18.10"
solana-program-runtime = "=1.18.10"
solana-program-test = "=1.18.10"
solana-address-lookup-table-program = "=1.18.10"
spl-token = "4.0.0"
spl-token-2022 = "3.0.2"

Expand Down
Loading

0 comments on commit 547ca80

Please sign in to comment.