feat(message-buffer): add init msg buffer pda to script, fix msg buffer pid (#845)

This commit is contained in:
swimricky 2023-05-26 11:37:06 -07:00 committed by GitHub
parent c0956f8157
commit e670f57f89
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 391 additions and 117 deletions

View File

@ -2,7 +2,7 @@
seeds = true
skip-lint = false
[programs.localnet]
message_buffer = "Vbmv1jt4vyuqBZcpYPpnVhrqVe5e6ZPb6JxDcffRHUM"
message_buffer = "7Vbmv1jt4vyuqBZcpYPpnVhrqVe5e6ZPb6JxDcffRHUM"
mock_cpi_caller = "Dg5PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
[registry]

View File

@ -6,7 +6,9 @@
},
"dependencies": {
"@coral-xyz/anchor": "^0.27.0",
"@lumina-dev/test": "^0.0.12"
"@lumina-dev/test": "^0.0.12",
"@pythnetwork/client": "^2.17.0",
"dotenv": "^16.0.3"
},
"devDependencies": {
"@types/bn.js": "^5.1.0",

View File

@ -12,7 +12,7 @@ use {
state::*,
};
declare_id!("Vbmv1jt4vyuqBZcpYPpnVhrqVe5e6ZPb6JxDcffRHUM");
declare_id!("7Vbmv1jt4vyuqBZcpYPpnVhrqVe5e6ZPb6JxDcffRHUM");
#[program]
pub mod message_buffer {

View File

@ -0,0 +1,6 @@
MESSAGE_BUFFER_PROGRAM_ID=BZZFM8qzdWvv4ysy8dxpAzjs9WJ6iy9y1ph2YNqWYzrm
ORACLE_PROGRAM_ID=7th6GdMuo4u1zNLzFAyMY6psunHNsGjPjo8hXvcTgKei
PAYER_KEYPAIR_PATH=/keys/funding.json
CLUSTER=integration
# 522 + 85(PriceFeedMessage) + 101(TwapMessage)
INITIAL_SIZE=708

View File

@ -0,0 +1,6 @@
MESSAGE_BUFFER_PROGRAM_ID=BZZFM8qzdWvv4ysy8dxpAzjs9WJ6iy9y1ph2YNqWYzrm
PAYER_KEYPAIR_PATH=/Users/rchen/.config/solana/id.json
ENDPOINT=http://pythnet:8899
CLUSTER=localnet
# 522 + 85(PriceFeedMessage) + 101(TwapMessage)
INITIAL_SIZE=708

View File

@ -0,0 +1,9 @@
MESSAGE_BUFFER_PROGRAM_ID=7Vbmv1jt4vyuqBZcpYPpnVhrqVe5e6ZPb6JxDcffRHUM
PAYER_KEYPAIR_PATH=<ABSOLUTE_PATH_TO_KEYPAIR>
CLUSTER=pythtest-crosschain
# Whitelist admin will be initialized to same as payer
# then a separate txn will be used to set it to the following address
# after the message buffers have been initialized
# WHITELIST_ADMIN=D8y6qTbQeYQdyrgKUvZbsveQkfnUThRKZvewddr4SKNt # Pythtest Multisig authority address (PDA of executor)
# 522 + 85(PriceFeedMessage) + 101(TwapMessage) + extra buffer
INITIAL_SIZE=2048

View File

@ -0,0 +1,9 @@
MESSAGE_BUFFER_PROGRAM_ID=
# absolute path to keypair
PAYER_KEYPAIR_PATH=
# 'devnet' | 'testnet' | 'mainnet-beta' |'integration' | 'pythtest-conformance' | 'pythnet' | 'localnet' | 'pythtest-crosschain';
CLUSTER=
# optional pubkey whitelist admin to set after initializing accounts
# whitelist admin will initially be set to the payer
WHITELIST_ADMIN=
INITIAL_SIZE=

View File

@ -4,26 +4,157 @@ import { MessageBuffer } from "../target/types/message_buffer";
import messageBuffer from "../target/idl/message_buffer.json";
import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet";
import { assert } from "chai";
import { Connection, PublicKey } from "@solana/web3.js";
import {
getPythClusterApiUrl,
getPythProgramKeyForCluster,
PythCluster,
parseBaseData,
AccountType,
parseProductData,
} from "@pythnetwork/client";
import path from "path";
import dotenv from "dotenv";
import fs from "fs";
type PythClusterOrIntegration = PythCluster | "integration";
/**
* Script to initialize the message buffer program and whitelist admin
* using the integration repo setup
*
* run using the following command:
* `NODE_ENV=<env> yarn ts-node scripts/setup_message_buffer.ts`
*/
const MESSAGE = Buffer.from("message");
const payer = anchor.web3.Keypair.fromSecretKey(
// Keypair at keys/funding.json
Uint8Array.from([
235, 245, 49, 124, 125, 91, 162, 107, 245, 83, 158, 7, 86, 181, 31, 252,
215, 200, 125, 25, 126, 55, 37, 240, 205, 171, 71, 196, 2, 11, 137, 229,
131, 30, 46, 220, 89, 75, 108, 173, 185, 146, 114, 253, 109, 67, 214, 133,
117, 79, 154, 107, 133, 193, 249, 251, 40, 171, 42, 191, 192, 60, 188, 78,
])
function getPythClusterEndpoint(cluster: PythClusterOrIntegration): string {
if (cluster === "integration") {
return "http://pythnet:8899";
}
return getPythClusterApiUrl(cluster);
}
function getPythPidForCluster(
cluster: PythClusterOrIntegration
): anchor.web3.PublicKey {
if (cluster === "integration") {
return new anchor.web3.PublicKey(
"7th6GdMuo4u1zNLzFAyMY6psunHNsGjPjo8hXvcTgKei"
);
const endpoint = "http://pythnet:8899";
} else {
return getPythProgramKeyForCluster(cluster);
}
}
const getKeypairFromFile = (keypairPath: string): anchor.web3.Keypair => {
const keypairBuffer = fs.readFileSync(keypairPath);
return anchor.web3.Keypair.fromSecretKey(
Uint8Array.from(JSON.parse(keypairBuffer.toString()))
);
};
function getPythOracleCpiAuth(
messageBufferProgramId: anchor.web3.PublicKey,
pythOracleProgramId: anchor.web3.PublicKey
): anchor.web3.PublicKey {
return anchor.web3.PublicKey.findProgramAddressSync(
[Buffer.from("upd_price_write"), messageBufferProgramId.toBuffer()],
pythOracleProgramId
)[0];
}
function getMessageBufferPubkey(
pythOracleCpiAuth: anchor.web3.PublicKey,
pythPriceAccountPk: anchor.web3.PublicKey,
messageBufferProgramId: anchor.web3.PublicKey
): anchor.web3.PublicKey {
return anchor.web3.PublicKey.findProgramAddressSync(
[pythOracleCpiAuth.toBuffer(), MESSAGE, pythPriceAccountPk.toBuffer()],
messageBufferProgramId
)[0];
}
export async function getPriceAccountPubkeys(
connection: anchor.web3.Connection,
pythPublicKey: anchor.web3.PublicKey
): Promise<PublicKey[]> {
const accountList = await connection.getProgramAccounts(
pythPublicKey,
connection.commitment
);
console.info(
`fetched ${
accountList.length
} programAccounts for pythProgram: ${pythPublicKey.toString()}`
);
const priceAccountIds: PublicKey[] = [];
accountList.forEach((singleAccount) => {
const base = parseBaseData(singleAccount.account.data);
if (base) {
switch (base.type) {
case AccountType.Product:
const productData = parseProductData(singleAccount.account.data);
priceAccountIds.push(productData.priceAccountKey);
break;
default:
break;
}
}
});
return priceAccountIds;
}
async function main() {
let canAirdrop = false;
switch (process.env.NODE_ENV) {
case "local":
dotenv.config({ path: path.join(__dirname, ".env.local") });
canAirdrop = true;
break;
case "integration":
dotenv.config({ path: path.join(__dirname, ".env.integration") });
canAirdrop = true;
break;
case "pythtest":
dotenv.config({ path: path.join(__dirname, ".env.pythtest") });
break;
case "pythnet":
dotenv.config({ path: path.join(__dirname, ".env.pythnet") });
break;
default:
console.error(`Invalid NODE_ENV: ${process.env.NODE_ENV}`);
process.exit(1);
}
const cluster = process.env.CLUSTER as PythClusterOrIntegration;
const messageBufferPid = new anchor.web3.PublicKey(
process.env.MESSAGE_BUFFER_PROGRAM_ID
);
const pythOraclePid = getPythPidForCluster(cluster);
const payer = getKeypairFromFile(
path.resolve(process.env.PAYER_KEYPAIR_PATH)
);
const endpoint = getPythClusterEndpoint(cluster);
const initialSize = parseInt(process.env.INITIAL_SIZE || "", 10);
let whitelistAdmin = payer;
console.info(`
messageBufferPid: ${messageBufferPid.toString()}
pythOraclePid: ${pythOraclePid.toString()}
payer: ${payer.publicKey.toString()}
endpoint: ${endpoint}
whitelistAdmin: ${whitelistAdmin.publicKey.toString()}
initialSize: ${initialSize}
`);
console.log(`connecting to ${endpoint}`);
const connection = new anchor.web3.Connection(endpoint);
const commitment = "finalized";
const provider = new anchor.AnchorProvider(
new anchor.web3.Connection(endpoint),
connection,
new NodeWallet(payer),
{
commitment,
@ -33,9 +164,6 @@ const provider = new anchor.AnchorProvider(
);
anchor.setProvider(provider);
const messageBufferPid = new anchor.web3.PublicKey(
"BZZFM8qzdWvv4ysy8dxpAzjs9WJ6iy9y1ph2YNqWYzrm"
);
const messageBufferProgram = new Program(
messageBuffer as Idl,
@ -43,39 +171,19 @@ const messageBufferProgram = new Program(
provider
) as unknown as Program<MessageBuffer>;
const whitelistAdmin = payer;
const MESSAGE = Buffer.from("message");
const [whitelistPubkey, whitelistBump] =
anchor.web3.PublicKey.findProgramAddressSync(
[MESSAGE, Buffer.from("whitelist")],
messageBufferProgram.programId
);
const pythOraclePid = new anchor.web3.PublicKey(
"7th6GdMuo4u1zNLzFAyMY6psunHNsGjPjo8hXvcTgKei"
);
const [pythOracleCpiAuth] = anchor.web3.PublicKey.findProgramAddressSync(
[Buffer.from("upd_price_write"), messageBufferProgram.programId.toBuffer()],
const pythOracleCpiAuth = getPythOracleCpiAuth(
messageBufferProgram.programId,
pythOraclePid
);
const pythPriceAccountPk = new anchor.web3.PublicKey(
"tvNV74CEkyEhmzJYiXGgcTMLCSX8JDPVi3er5ZSTJn2"
);
const [messageBufferPda, messageBufferBump] =
anchor.web3.PublicKey.findProgramAddressSync(
[pythOracleCpiAuth.toBuffer(), MESSAGE, pythPriceAccountPk.toBuffer()],
messageBufferProgram.programId
);
async function main() {
console.log("Initializing message buffer...");
console.group();
console.log("Requesting airdrop");
if (canAirdrop) {
console.group("Requesting airdrop");
let airdropSig = await provider.connection.requestAirdrop(
payer.publicKey,
@ -90,13 +198,20 @@ async function main() {
console.log(`payerBalance: ${payerBalance}`);
console.log("Airdrop complete");
console.groupEnd();
} else {
console.log("Skipping airdrop for non-local/integration environments");
}
console.log("Initializing message buffer whitelist admin...");
let whitelist = await messageBufferProgram.account.whitelist.fetchNullable(
whitelistPubkey
);
if (whitelist === null) {
console.group("No whitelist detected. Initializing message buffer");
console.group(
"No whitelist detected. Initializing message buffer whitelist & admin"
);
const initializeTxnSig = await messageBufferProgram.methods
.initialize()
.accounts({
@ -148,12 +263,59 @@ async function main() {
console.log("Allowed Programs already set");
}
let messageBufferData = await getMessageBuffer(
provider.connection,
messageBufferPda
let priceIds = await getPriceAccountPubkeys(connection, pythOraclePid);
console.info(`fetched ${priceIds.length} priceAccountIds`);
let errorAccounts = [];
let alreadyInitializedAccounts = [];
let newlyInitializedAccounts = [];
const messageBufferKeys = priceIds.map((priceId) => {
return {
messageBufferKey: getMessageBufferPubkey(
pythOracleCpiAuth,
priceId,
messageBufferPid
),
priceAccountKey: priceId,
};
});
let accounts = await messageBufferProgram.account.messageBuffer.fetchMultiple(
messageBufferKeys.map((k) => k.messageBufferKey)
);
if (messageBufferData === null) {
console.group("Creating Message Buffer");
const msgBufferKeysAndData = messageBufferKeys.map((k, i) => {
return {
...k,
messageBufferData: accounts[i],
};
});
alreadyInitializedAccounts = msgBufferKeysAndData.filter((idAndAccount) => {
return idAndAccount.messageBufferData !== null;
});
console.log(`
${
alreadyInitializedAccounts.length
} message buffer accounts already initialized.
alreadyInitializedAccounts: ${JSON.stringify(alreadyInitializedAccounts)}`);
const priceAccountPubkeysForNewlyInitializedMessageBuffers =
msgBufferKeysAndData.filter((idAndAccount) => {
return idAndAccount.messageBufferData === null;
});
if (priceAccountPubkeysForNewlyInitializedMessageBuffers.length === 0) {
console.info(`no new message buffers to initialize. exiting...`);
process.exit(1);
}
// TODO: optimize with batching
await Promise.all(
priceAccountPubkeysForNewlyInitializedMessageBuffers.map(
async (idAndAccount) => {
const priceId = idAndAccount.priceAccountKey;
const messageBufferPda = idAndAccount.messageBufferKey;
const msgBufferPdaMetas = [
{
pubkey: messageBufferPda,
@ -162,8 +324,9 @@ async function main() {
},
];
try {
await messageBufferProgram.methods
.createBuffer(pythOracleCpiAuth, pythPriceAccountPk, 1024 * 8)
.createBuffer(pythOracleCpiAuth, priceId, initialSize)
.accounts({
whitelist: whitelistPubkey,
admin: whitelistAdmin.publicKey,
@ -172,26 +335,62 @@ async function main() {
.signers([whitelistAdmin])
.remainingAccounts(msgBufferPdaMetas)
.rpc({ skipPreflight: true });
console.log("fetching messageBuffer");
const messageBufferData = await getMessageBuffer(
provider.connection,
messageBufferPda
newlyInitializedAccounts.push({
priceId: priceId.toString(),
messageBuffer: messageBufferPda.toString(),
});
} catch (e) {
console.error(
"Error creating message buffer for price account: ",
priceId.toString()
);
console.log(`messageBufferData: ${messageBufferData.toString("utf-8")}`);
console.groupEnd();
} else {
console.log("Message Buffer already created");
console.log(`messageBufferData: ${messageBufferData.toString("utf-8")}`);
console.error(e);
errorAccounts.push({
priceId: priceId.toString(),
messageBuffer: messageBufferPda.toString(),
});
}
}
)
);
if (errorAccounts.length !== 0) {
console.error(
`Ran into errors when initializing ${errorAccounts.length} accounts`
);
console.info(`Accounts with errors: ${JSON.stringify(errorAccounts)}`);
}
console.log(`Initialized ${newlyInitializedAccounts.length} accounts`);
async function getMessageBuffer(
connection: anchor.web3.Connection,
accountKey: anchor.web3.PublicKey
): Promise<Buffer | null> {
let accountInfo = await connection.getAccountInfo(accountKey);
return accountInfo ? accountInfo.data : null;
// Update whitelist admin at the end otherwise all the message buffer PDAs
// will have to be initialized by the whitelist admin (which could be the multisig)
if (process.env.WHITELIST_ADMIN) {
whitelist = await messageBufferProgram.account.whitelist.fetchNullable(
whitelistPubkey
);
let newWhitelistAdmin = new anchor.web3.PublicKey(
process.env.WHITELIST_ADMIN
);
if (!whitelist.admin.equals(newWhitelistAdmin)) {
console.info(
`updating whitelist admin from ${whitelist.admin.toString()} to ${newWhitelistAdmin.toString()}`
);
try {
await messageBufferProgram.methods
.updateWhitelistAdmin(newWhitelistAdmin)
.accounts({
admin: whitelistAdmin.publicKey,
})
.signers([whitelistAdmin])
.rpc();
} catch (e) {
console.error(`Error when attempting to update the admin: ${e}`);
}
} else {
console.info(
`whitelist admin is already ${newWhitelistAdmin.toString()}`
);
}
}
}
void main();

View File

@ -30,6 +30,27 @@
dependencies:
regenerator-runtime "^0.13.11"
"@coral-xyz/anchor@^0.26.0":
version "0.26.0"
resolved "https://registry.yarnpkg.com/@coral-xyz/anchor/-/anchor-0.26.0.tgz#c8e4f7177e93441afd030f22d777d54d0194d7d1"
integrity sha512-PxRl+wu5YyptWiR9F2MBHOLLibm87Z4IMUBPreX+DYBtPM+xggvcPi0KAN7+kIL4IrIhXI8ma5V0MCXxSN1pHg==
dependencies:
"@coral-xyz/borsh" "^0.26.0"
"@solana/web3.js" "^1.68.0"
base64-js "^1.5.1"
bn.js "^5.1.2"
bs58 "^4.0.1"
buffer-layout "^1.2.2"
camelcase "^6.3.0"
cross-fetch "^3.1.5"
crypto-hash "^1.3.0"
eventemitter3 "^4.0.7"
js-sha256 "^0.9.0"
pako "^2.0.3"
snake-case "^3.0.4"
superstruct "^0.15.4"
toml "^3.0.0"
"@coral-xyz/anchor@^0.27.0":
version "0.27.0"
resolved "https://registry.yarnpkg.com/@coral-xyz/anchor/-/anchor-0.27.0.tgz#621e5ef123d05811b97e49973b4ed7ede27c705c"
@ -51,6 +72,14 @@
superstruct "^0.15.4"
toml "^3.0.0"
"@coral-xyz/borsh@^0.26.0":
version "0.26.0"
resolved "https://registry.yarnpkg.com/@coral-xyz/borsh/-/borsh-0.26.0.tgz#d054f64536d824634969e74138f9f7c52bbbc0d5"
integrity sha512-uCZ0xus0CszQPHYfWAqKS5swS1UxvePu83oOF+TWpUkedsNlg6p2p4azxZNSSqwXb9uXMFgxhuMBX9r3Xoi0vQ==
dependencies:
bn.js "^5.1.2"
buffer-layout "^1.2.0"
"@coral-xyz/borsh@^0.27.0":
version "0.27.0"
resolved "https://registry.yarnpkg.com/@coral-xyz/borsh/-/borsh-0.27.0.tgz#700c647ea5262b1488957ac7fb4e8acf72c72b63"
@ -107,6 +136,15 @@
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"
"@pythnetwork/client@^2.17.0":
version "2.17.0"
resolved "https://registry.yarnpkg.com/@pythnetwork/client/-/client-2.17.0.tgz#b155af06958f4b729bfee1c07130c556598cf168"
integrity sha512-hv285vehmLH6N762Z4jqvPTM+hCYnXQaUp6DMgLUpDHvE0mTbwW9PvlxYoUJZGtyeCDkgn9HrTWXPtnaXTRr+Q==
dependencies:
"@coral-xyz/anchor" "^0.26.0"
"@coral-xyz/borsh" "^0.26.0"
buffer "^6.0.1"
"@solana/buffer-layout@^4.0.0":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@solana/buffer-layout/-/buffer-layout-4.0.1.tgz#b996235eaec15b1e0b5092a8ed6028df77fa6c15"
@ -439,7 +477,7 @@ buffer@6.0.1:
base64-js "^1.3.1"
ieee754 "^1.2.1"
buffer@~6.0.3:
buffer@^6.0.1, buffer@~6.0.3:
version "6.0.3"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6"
integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==
@ -728,6 +766,11 @@ dot-case@^3.0.4:
no-case "^3.0.4"
tslib "^2.0.3"
dotenv@^16.0.3:
version "16.0.3"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.3.tgz#115aec42bac5053db3c456db30cc243a5a836a07"
integrity sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==
duplexer@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6"

View File

@ -7,10 +7,10 @@ pub(crate) type Pubkey = [u8; 32];
pub(crate) type PriceId = Pubkey;
/// Official Message Buffer Program Id
/// pubkey!("Vbmv1jt4vyuqBZcpYPpnVhrqVe5e6ZPb6JxDcffRHUM");
/// pubkey!("7Vbmv1jt4vyuqBZcpYPpnVhrqVe5e6ZPb6JxDcffRHUM");
pub const MESSAGE_BUFFER_PID: Pubkey = [
7, 83, 149, 9, 30, 102, 77, 194, 50, 151, 133, 40, 118, 10, 93, 152, 174, 44, 244, 56, 27, 47,
234, 218, 173, 153, 254, 48, 102, 178, 128, 18,
96, 121, 180, 39, 141, 35, 152, 85, 128, 70, 147, 124, 128, 196, 115, 241, 86, 159, 207, 148,
39, 234, 137, 86, 178, 4, 238, 48, 102, 178, 128, 18,
];
/// Pubkey::find_program_address(&[b"emitter"], &sysvar::accumulator::id());
@ -99,7 +99,7 @@ pub(crate) mod tests {
pythtest_accumulator_sequence_address.to_bytes()
);
let message_buffer_program = pubkey!("Vbmv1jt4vyuqBZcpYPpnVhrqVe5e6ZPb6JxDcffRHUM");
let message_buffer_program = pubkey!("7Vbmv1jt4vyuqBZcpYPpnVhrqVe5e6ZPb6JxDcffRHUM");
assert_eq!(MESSAGE_BUFFER_PID, message_buffer_program.to_bytes());
}
}