Skip to content

Commit b87ccc9

Browse files
authored
feat(governance/xc_admin): initialize publisher buffers in cli and frontend (#1923)
1 parent 91739da commit b87ccc9

File tree

5 files changed

+170
-15
lines changed

5 files changed

+170
-15
lines changed

governance/xc_admin/packages/xc_admin_cli/src/index.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,12 @@ import {
3939
MultisigParser,
4040
MultisigVault,
4141
PROGRAM_AUTHORITY_ESCROW,
42+
createDetermisticPriceStoreInitializePublisherInstruction,
43+
createPriceStoreInstruction,
4244
findDetermisticStakeAccountAddress,
4345
getMultisigCluster,
4446
getProposalInstructions,
47+
isPriceStorePublisherInitialized,
4548
} from "@pythnetwork/xc-admin-common";
4649

4750
import {
@@ -559,6 +562,73 @@ multisigCommand(
559562
);
560563
});
561564

565+
multisigCommand("init-price-store", "Init price store program").action(
566+
async (options: any) => {
567+
const vault = await loadVaultFromOptions(options);
568+
const cluster: PythCluster = options.cluster;
569+
const authorityKey = await vault.getVaultAuthorityPDA(cluster);
570+
const instruction = createPriceStoreInstruction({
571+
type: "Initialize",
572+
data: {
573+
authorityKey,
574+
payerKey: authorityKey,
575+
},
576+
});
577+
await vault.proposeInstructions(
578+
[instruction],
579+
cluster,
580+
DEFAULT_PRIORITY_FEE_CONFIG
581+
);
582+
}
583+
);
584+
585+
multisigCommand("init-price-store-buffers", "Init price store buffers").action(
586+
async (options: any) => {
587+
const vault = await loadVaultFromOptions(options);
588+
const cluster: PythCluster = options.cluster;
589+
const oracleProgramId = getPythProgramKeyForCluster(cluster);
590+
const connection = new Connection(getPythClusterApiUrl(cluster));
591+
const authorityKey = await vault.getVaultAuthorityPDA(cluster);
592+
593+
const allPythAccounts = await connection.getProgramAccounts(
594+
oracleProgramId
595+
);
596+
const allPublishers: Set<PublicKey> = new Set();
597+
for (const account of allPythAccounts) {
598+
const data = account.account.data;
599+
const base = parseBaseData(data);
600+
if (base?.type === AccountType.Price) {
601+
const parsed = parsePriceData(data);
602+
for (const component of parsed.priceComponents.slice(
603+
0,
604+
parsed.numComponentPrices
605+
)) {
606+
allPublishers.add(component.publisher);
607+
}
608+
}
609+
}
610+
611+
let instructions = [];
612+
for (const publisherKey of allPublishers) {
613+
if (await isPriceStorePublisherInitialized(connection, publisherKey)) {
614+
// Already configured.
615+
continue;
616+
}
617+
instructions.push(
618+
await createDetermisticPriceStoreInitializePublisherInstruction(
619+
authorityKey,
620+
publisherKey
621+
)
622+
);
623+
}
624+
await vault.proposeInstructions(
625+
instructions,
626+
cluster,
627+
DEFAULT_PRIORITY_FEE_CONFIG
628+
);
629+
}
630+
);
631+
562632
program
563633
.command("parse-transaction")
564634
.description("Parse a transaction sitting in the multisig")

governance/xc_admin/packages/xc_admin_common/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ export * from "./message_buffer";
1212
export * from "./executor";
1313
export * from "./chains";
1414
export * from "./deterministic_stake_accounts";
15+
export * from "./price_store";

governance/xc_admin/packages/xc_admin_common/src/price_store.ts

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
AccountMeta,
3+
Connection,
34
MAX_SEED_LENGTH,
45
PublicKey,
56
SystemProgram,
@@ -45,15 +46,28 @@ enum InstructionId {
4546
InitializePublisher = 2,
4647
}
4748

49+
export function findPriceStoreConfigAddress(): [PublicKey, number] {
50+
return PublicKey.findProgramAddressSync(
51+
[Buffer.from("CONFIG")],
52+
PRICE_STORE_PROGRAM_ID
53+
);
54+
}
55+
56+
export function findPriceStorePublisherConfigAddress(
57+
publisherKey: PublicKey
58+
): [PublicKey, number] {
59+
return PublicKey.findProgramAddressSync(
60+
[Buffer.from("PUBLISHER_CONFIG"), publisherKey.toBuffer()],
61+
PRICE_STORE_PROGRAM_ID
62+
);
63+
}
64+
4865
export function createPriceStoreInstruction(
4966
data: PriceStoreInstruction
5067
): TransactionInstruction {
5168
switch (data.type) {
5269
case "Initialize": {
53-
const [configKey, configBump] = PublicKey.findProgramAddressSync(
54-
[Buffer.from("CONFIG")],
55-
PRICE_STORE_PROGRAM_ID
56-
);
70+
const [configKey, configBump] = findPriceStoreConfigAddress();
5771
const instructionData = Buffer.concat([
5872
Buffer.from([InstructionId.Initialize, configBump]),
5973
data.data.authorityKey.toBuffer(),
@@ -82,15 +96,9 @@ export function createPriceStoreInstruction(
8296
});
8397
}
8498
case "InitializePublisher": {
85-
const [configKey, configBump] = PublicKey.findProgramAddressSync(
86-
[Buffer.from("CONFIG")],
87-
PRICE_STORE_PROGRAM_ID
88-
);
99+
const [configKey, configBump] = findPriceStoreConfigAddress();
89100
const [publisherConfigKey, publisherConfigBump] =
90-
PublicKey.findProgramAddressSync(
91-
[Buffer.from("PUBLISHER_CONFIG"), data.data.publisherKey.toBuffer()],
92-
PRICE_STORE_PROGRAM_ID
93-
);
101+
findPriceStorePublisherConfigAddress(data.data.publisherKey);
94102
const instructionData = Buffer.concat([
95103
Buffer.from([
96104
InstructionId.InitializePublisher,
@@ -273,5 +281,32 @@ export async function findDetermisticPublisherBufferAddress(
273281
return [address, seed];
274282
}
275283

284+
export async function createDetermisticPriceStoreInitializePublisherInstruction(
285+
authorityKey: PublicKey,
286+
publisherKey: PublicKey
287+
): Promise<TransactionInstruction> {
288+
const bufferKey = (
289+
await findDetermisticPublisherBufferAddress(publisherKey)
290+
)[0];
291+
return createPriceStoreInstruction({
292+
type: "InitializePublisher",
293+
data: {
294+
authorityKey,
295+
bufferKey,
296+
publisherKey,
297+
},
298+
});
299+
}
300+
301+
export async function isPriceStorePublisherInitialized(
302+
connection: Connection,
303+
publisherKey: PublicKey
304+
): Promise<boolean> {
305+
const publisherConfigKey =
306+
findPriceStorePublisherConfigAddress(publisherKey)[0];
307+
const response = await connection.getAccountInfo(publisherConfigKey);
308+
return response !== null;
309+
}
310+
276311
// Recommended buffer size, enough to hold 5000 prices.
277312
export const PRICE_STORE_BUFFER_SPACE = 100048;

governance/xc_admin/packages/xc_admin_frontend/components/PermissionDepermissionKey.tsx

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ import axios from 'axios'
1010
import { Fragment, useContext, useEffect, useState } from 'react'
1111
import toast from 'react-hot-toast'
1212
import {
13+
createDetermisticPriceStoreInitializePublisherInstruction,
1314
getMaximumNumberOfPublishers,
1415
getMultisigCluster,
16+
isPriceStorePublisherInitialized,
1517
isRemoteCluster,
1618
mapKey,
1719
PRICE_FEED_MULTISIG,
@@ -53,7 +55,7 @@ const PermissionDepermissionKey = ({
5355
const [isSubmitButtonLoading, setIsSubmitButtonLoading] = useState(false)
5456
const [priceAccounts, setPriceAccounts] = useState<PublicKey[]>([])
5557
const { cluster } = useContext(ClusterContext)
56-
const { rawConfig, dataIsLoading } = usePythContext()
58+
const { rawConfig, dataIsLoading, connection } = usePythContext()
5759
const { connected } = useWallet()
5860

5961
// get current input value
@@ -86,11 +88,12 @@ const PermissionDepermissionKey = ({
8688
? mapKey(multisigAuthority)
8789
: multisigAuthority
8890

91+
const publisherPublicKey = new PublicKey(publisherKey)
8992
for (const priceAccount of priceAccounts) {
9093
if (isPermission) {
9194
instructions.push(
9295
await pythProgramClient.methods
93-
.addPublisher(new PublicKey(publisherKey))
96+
.addPublisher(publisherPublicKey)
9497
.accounts({
9598
fundingAccount,
9699
priceAccount: priceAccount,
@@ -100,7 +103,7 @@ const PermissionDepermissionKey = ({
100103
} else {
101104
instructions.push(
102105
await pythProgramClient.methods
103-
.delPublisher(new PublicKey(publisherKey))
106+
.delPublisher(publisherPublicKey)
104107
.accounts({
105108
fundingAccount,
106109
priceAccount: priceAccount,
@@ -109,6 +112,22 @@ const PermissionDepermissionKey = ({
109112
)
110113
}
111114
}
115+
if (isPermission) {
116+
if (
117+
!connection ||
118+
!(await isPriceStorePublisherInitialized(
119+
connection,
120+
publisherPublicKey
121+
))
122+
) {
123+
instructions.push(
124+
await createDetermisticPriceStoreInitializePublisherInstruction(
125+
fundingAccount,
126+
publisherPublicKey
127+
)
128+
)
129+
}
130+
}
112131
setIsSubmitButtonLoading(true)
113132
try {
114133
const response = await axios.post(proposerServerUrl + '/api/propose', {

governance/xc_admin/packages/xc_admin_frontend/components/tabs/General.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import {
2121
PRICE_FEED_OPS_KEY,
2222
getMessageBufferAddressForPrice,
2323
getMaximumNumberOfPublishers,
24+
isPriceStorePublisherInitialized,
25+
createDetermisticPriceStoreInitializePublisherInstruction,
2426
} from '@pythnetwork/xc-admin-common'
2527
import { ClusterContext } from '../../contexts/ClusterContext'
2628
import { useMultisigContext } from '../../contexts/MultisigContext'
@@ -288,6 +290,8 @@ const General = ({ proposerServerUrl }: { proposerServerUrl: string }) => {
288290
const handleSendProposalButtonClick = async () => {
289291
if (pythProgramClient && dataChanges && !isMultisigLoading && squads) {
290292
const instructions: TransactionInstruction[] = []
293+
const publisherInitializationsVerified: PublicKey[] = []
294+
291295
for (const symbol of Object.keys(dataChanges)) {
292296
const multisigAuthority = squads.getAuthorityPDA(
293297
PRICE_FEED_MULTISIG[getMultisigCluster(cluster)],
@@ -296,6 +300,30 @@ const General = ({ proposerServerUrl }: { proposerServerUrl: string }) => {
296300
const fundingAccount = isRemote
297301
? mapKey(multisigAuthority)
298302
: multisigAuthority
303+
304+
const initPublisher = async (publisherKey: PublicKey) => {
305+
if (
306+
publisherInitializationsVerified.every(
307+
(el) => !el.equals(publisherKey)
308+
)
309+
) {
310+
if (
311+
!connection ||
312+
!(await isPriceStorePublisherInitialized(
313+
connection,
314+
publisherKey
315+
))
316+
) {
317+
instructions.push(
318+
await createDetermisticPriceStoreInitializePublisherInstruction(
319+
fundingAccount,
320+
publisherKey
321+
)
322+
)
323+
}
324+
publisherInitializationsVerified.push(publisherKey)
325+
}
326+
}
299327
const { prev, new: newChanges } = dataChanges[symbol]
300328
// if prev is undefined, it means that the symbol is new
301329
if (!prev) {
@@ -377,6 +405,7 @@ const General = ({ proposerServerUrl }: { proposerServerUrl: string }) => {
377405
})
378406
.instruction()
379407
)
408+
await initPublisher(publisherKey)
380409
}
381410

382411
// create set min publisher instruction if there are any publishers
@@ -545,6 +574,7 @@ const General = ({ proposerServerUrl }: { proposerServerUrl: string }) => {
545574
})
546575
.instruction()
547576
)
577+
await initPublisher(publisherKey)
548578
}
549579
}
550580
}

0 commit comments

Comments
 (0)