Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(governance/xc_admin): initialize publisher buffers in cli and frontend #1923

Merged
merged 1 commit into from
Sep 17, 2024
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
70 changes: 70 additions & 0 deletions governance/xc_admin/packages/xc_admin_cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,12 @@ import {
MultisigParser,
MultisigVault,
PROGRAM_AUTHORITY_ESCROW,
createDetermisticPriceStoreInitializePublisherInstruction,
createPriceStoreInstruction,
findDetermisticStakeAccountAddress,
getMultisigCluster,
getProposalInstructions,
isPriceStorePublisherInitialized,
} from "@pythnetwork/xc-admin-common";

import {
Expand Down Expand Up @@ -559,6 +562,73 @@ multisigCommand(
);
});

multisigCommand("init-price-store", "Init price store program").action(
async (options: any) => {
const vault = await loadVaultFromOptions(options);
const cluster: PythCluster = options.cluster;
const authorityKey = await vault.getVaultAuthorityPDA(cluster);
const instruction = createPriceStoreInstruction({
type: "Initialize",
data: {
authorityKey,
payerKey: authorityKey,
},
});
await vault.proposeInstructions(
[instruction],
cluster,
DEFAULT_PRIORITY_FEE_CONFIG
);
}
);

multisigCommand("init-price-store-buffers", "Init price store buffers").action(
async (options: any) => {
const vault = await loadVaultFromOptions(options);
const cluster: PythCluster = options.cluster;
const oracleProgramId = getPythProgramKeyForCluster(cluster);
const connection = new Connection(getPythClusterApiUrl(cluster));
const authorityKey = await vault.getVaultAuthorityPDA(cluster);

const allPythAccounts = await connection.getProgramAccounts(
oracleProgramId
);
const allPublishers: Set<PublicKey> = new Set();
for (const account of allPythAccounts) {
const data = account.account.data;
const base = parseBaseData(data);
if (base?.type === AccountType.Price) {
const parsed = parsePriceData(data);
for (const component of parsed.priceComponents.slice(
0,
parsed.numComponentPrices
)) {
allPublishers.add(component.publisher);
}
}
}

let instructions = [];
for (const publisherKey of allPublishers) {
if (await isPriceStorePublisherInitialized(connection, publisherKey)) {
// Already configured.
continue;
}
instructions.push(
await createDetermisticPriceStoreInitializePublisherInstruction(
authorityKey,
publisherKey
)
);
}
await vault.proposeInstructions(
instructions,
cluster,
DEFAULT_PRIORITY_FEE_CONFIG
);
}
);

program
.command("parse-transaction")
.description("Parse a transaction sitting in the multisig")
Expand Down
1 change: 1 addition & 0 deletions governance/xc_admin/packages/xc_admin_common/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ export * from "./message_buffer";
export * from "./executor";
export * from "./chains";
export * from "./deterministic_stake_accounts";
export * from "./price_store";
59 changes: 47 additions & 12 deletions governance/xc_admin/packages/xc_admin_common/src/price_store.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
AccountMeta,
Connection,
MAX_SEED_LENGTH,
PublicKey,
SystemProgram,
Expand Down Expand Up @@ -45,15 +46,28 @@ enum InstructionId {
InitializePublisher = 2,
}

export function findPriceStoreConfigAddress(): [PublicKey, number] {
return PublicKey.findProgramAddressSync(
[Buffer.from("CONFIG")],
PRICE_STORE_PROGRAM_ID
);
}

export function findPriceStorePublisherConfigAddress(
publisherKey: PublicKey
): [PublicKey, number] {
return PublicKey.findProgramAddressSync(
[Buffer.from("PUBLISHER_CONFIG"), publisherKey.toBuffer()],
PRICE_STORE_PROGRAM_ID
);
}

export function createPriceStoreInstruction(
data: PriceStoreInstruction
): TransactionInstruction {
switch (data.type) {
case "Initialize": {
const [configKey, configBump] = PublicKey.findProgramAddressSync(
[Buffer.from("CONFIG")],
PRICE_STORE_PROGRAM_ID
);
const [configKey, configBump] = findPriceStoreConfigAddress();
const instructionData = Buffer.concat([
Buffer.from([InstructionId.Initialize, configBump]),
data.data.authorityKey.toBuffer(),
Expand Down Expand Up @@ -82,15 +96,9 @@ export function createPriceStoreInstruction(
});
}
case "InitializePublisher": {
const [configKey, configBump] = PublicKey.findProgramAddressSync(
[Buffer.from("CONFIG")],
PRICE_STORE_PROGRAM_ID
);
const [configKey, configBump] = findPriceStoreConfigAddress();
const [publisherConfigKey, publisherConfigBump] =
PublicKey.findProgramAddressSync(
[Buffer.from("PUBLISHER_CONFIG"), data.data.publisherKey.toBuffer()],
PRICE_STORE_PROGRAM_ID
);
findPriceStorePublisherConfigAddress(data.data.publisherKey);
const instructionData = Buffer.concat([
Buffer.from([
InstructionId.InitializePublisher,
Expand Down Expand Up @@ -273,5 +281,32 @@ export async function findDetermisticPublisherBufferAddress(
return [address, seed];
}

export async function createDetermisticPriceStoreInitializePublisherInstruction(
authorityKey: PublicKey,
publisherKey: PublicKey
): Promise<TransactionInstruction> {
const bufferKey = (
await findDetermisticPublisherBufferAddress(publisherKey)
)[0];
return createPriceStoreInstruction({
type: "InitializePublisher",
data: {
authorityKey,
bufferKey,
publisherKey,
},
});
}

export async function isPriceStorePublisherInitialized(
connection: Connection,
publisherKey: PublicKey
): Promise<boolean> {
const publisherConfigKey =
findPriceStorePublisherConfigAddress(publisherKey)[0];
const response = await connection.getAccountInfo(publisherConfigKey);
return response !== null;
}

// Recommended buffer size, enough to hold 5000 prices.
export const PRICE_STORE_BUFFER_SPACE = 100048;
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ import axios from 'axios'
import { Fragment, useContext, useEffect, useState } from 'react'
import toast from 'react-hot-toast'
import {
createDetermisticPriceStoreInitializePublisherInstruction,
getMaximumNumberOfPublishers,
getMultisigCluster,
isPriceStorePublisherInitialized,
isRemoteCluster,
mapKey,
PRICE_FEED_MULTISIG,
Expand Down Expand Up @@ -53,7 +55,7 @@ const PermissionDepermissionKey = ({
const [isSubmitButtonLoading, setIsSubmitButtonLoading] = useState(false)
const [priceAccounts, setPriceAccounts] = useState<PublicKey[]>([])
const { cluster } = useContext(ClusterContext)
const { rawConfig, dataIsLoading } = usePythContext()
const { rawConfig, dataIsLoading, connection } = usePythContext()
const { connected } = useWallet()

// get current input value
Expand Down Expand Up @@ -86,11 +88,12 @@ const PermissionDepermissionKey = ({
? mapKey(multisigAuthority)
: multisigAuthority

const publisherPublicKey = new PublicKey(publisherKey)
for (const priceAccount of priceAccounts) {
if (isPermission) {
instructions.push(
await pythProgramClient.methods
.addPublisher(new PublicKey(publisherKey))
.addPublisher(publisherPublicKey)
.accounts({
fundingAccount,
priceAccount: priceAccount,
Expand All @@ -100,7 +103,7 @@ const PermissionDepermissionKey = ({
} else {
instructions.push(
await pythProgramClient.methods
.delPublisher(new PublicKey(publisherKey))
.delPublisher(publisherPublicKey)
.accounts({
fundingAccount,
priceAccount: priceAccount,
Expand All @@ -109,6 +112,22 @@ const PermissionDepermissionKey = ({
)
}
}
if (isPermission) {
if (
!connection ||
!(await isPriceStorePublisherInitialized(
connection,
publisherPublicKey
))
) {
instructions.push(
await createDetermisticPriceStoreInitializePublisherInstruction(
fundingAccount,
publisherPublicKey
)
)
}
}
setIsSubmitButtonLoading(true)
try {
const response = await axios.post(proposerServerUrl + '/api/propose', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import {
PRICE_FEED_OPS_KEY,
getMessageBufferAddressForPrice,
getMaximumNumberOfPublishers,
isPriceStorePublisherInitialized,
createDetermisticPriceStoreInitializePublisherInstruction,
} from '@pythnetwork/xc-admin-common'
import { ClusterContext } from '../../contexts/ClusterContext'
import { useMultisigContext } from '../../contexts/MultisigContext'
Expand Down Expand Up @@ -288,6 +290,8 @@ const General = ({ proposerServerUrl }: { proposerServerUrl: string }) => {
const handleSendProposalButtonClick = async () => {
if (pythProgramClient && dataChanges && !isMultisigLoading && squads) {
const instructions: TransactionInstruction[] = []
const publisherInitializationsVerified: PublicKey[] = []

for (const symbol of Object.keys(dataChanges)) {
const multisigAuthority = squads.getAuthorityPDA(
PRICE_FEED_MULTISIG[getMultisigCluster(cluster)],
Expand All @@ -296,6 +300,30 @@ const General = ({ proposerServerUrl }: { proposerServerUrl: string }) => {
const fundingAccount = isRemote
? mapKey(multisigAuthority)
: multisigAuthority

const initPublisher = async (publisherKey: PublicKey) => {
if (
publisherInitializationsVerified.every(
(el) => !el.equals(publisherKey)
)
) {
if (
!connection ||
!(await isPriceStorePublisherInitialized(
connection,
publisherKey
))
) {
instructions.push(
await createDetermisticPriceStoreInitializePublisherInstruction(
fundingAccount,
publisherKey
)
)
}
publisherInitializationsVerified.push(publisherKey)
}
}
const { prev, new: newChanges } = dataChanges[symbol]
// if prev is undefined, it means that the symbol is new
if (!prev) {
Expand Down Expand Up @@ -377,6 +405,7 @@ const General = ({ proposerServerUrl }: { proposerServerUrl: string }) => {
})
.instruction()
)
await initPublisher(publisherKey)
}

// create set min publisher instruction if there are any publishers
Expand Down Expand Up @@ -545,6 +574,7 @@ const General = ({ proposerServerUrl }: { proposerServerUrl: string }) => {
})
.instruction()
)
await initPublisher(publisherKey)
}
}
}
Expand Down
Loading