Skip to content

Commit

Permalink
feat(governance/xc_admin): initialize publisher buffers in cli and fr…
Browse files Browse the repository at this point in the history
…ontend (#1923)
  • Loading branch information
Riateche authored Sep 17, 2024
1 parent 91739da commit b87ccc9
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 15 deletions.
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

0 comments on commit b87ccc9

Please sign in to comment.