From 70e1a40d02edc474e21da98c92f5ff201b9275ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Thu, 17 Nov 2022 14:17:57 +0900 Subject: [PATCH] Merge proposeOffchainConfig into proposeConfig --- .../src/commands/contracts/ocr2/index.ts | 2 - .../contracts/ocr2/initialize.flow.ts | 10 - .../contracts/ocr2/inspection/inspect.ts | 2 +- .../contracts/ocr2/proposal/acceptProposal.ts | 6 +- .../commands/contracts/ocr2/proposeConfig.ts | 219 +++++++++++- .../contracts/ocr2/proposeOffchainConfig.ts | 322 ------------------ .../commands/contracts/ocr2/setup.dev.flow.ts | 14 - .../src/lib/encoding.ts | 2 +- 8 files changed, 220 insertions(+), 357 deletions(-) delete mode 100644 gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/ocr2/proposeOffchainConfig.ts diff --git a/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/ocr2/index.ts b/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/ocr2/index.ts index 0a668ce73..076bb03e9 100644 --- a/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/ocr2/index.ts +++ b/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/ocr2/index.ts @@ -16,7 +16,6 @@ import { makeUpgradeProgramCommand } from '../../abstract/upgrade' import Fund from './fund' import CreateProposal from './proposal/createProposal' import ProposeConfig from './proposeConfig' -import ProposeOffchainConfig from './proposeOffchainConfig' import FinalizeProposal from './proposal/finalizeProposal' import Close from './close' import WithdrawFunds from './withdrawFunds' @@ -38,7 +37,6 @@ export default [ CreateProposal, FinalizeProposal, ProposeConfig, - ProposeOffchainConfig, ReadState, SetBillingAccessController, SetRequesterAccessController, diff --git a/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/ocr2/initialize.flow.ts b/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/ocr2/initialize.flow.ts index ccbca6132..ff6340d2f 100644 --- a/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/ocr2/initialize.flow.ts +++ b/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/ocr2/initialize.flow.ts @@ -9,7 +9,6 @@ import OCR2Inspect from './inspection/inspect' import CreateFeed from '../store/createFeed' import SetWriter from '../store/setWriter' import CreateProposal from './proposal/createProposal' -import ProposeOffchainConfig from './proposeOffchainConfig' import ProposeConfig from './proposeConfig' import FinalizeProposal from './proposal/finalizeProposal' import AcceptProposal from './proposal/acceptProposal' @@ -80,15 +79,6 @@ export default class OCR2InitializeFlow extends FlowCommand }, args: [FlowCommand.ID.contract(this.stepIds.OCR_2)], }, - { - id: this.stepIds.PROPOSE_OFFCHAIN, - name: 'Propose Offchain Config', - command: ProposeOffchainConfig, - flags: { - proposalId: FlowCommand.ID.data(this.stepIds.PROPOSAL, 'proposal'), - }, - args: [FlowCommand.ID.contract(this.stepIds.OCR_2)], - }, { name: 'Finalize Proposal', command: FinalizeProposal, diff --git a/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/ocr2/inspection/inspect.ts b/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/ocr2/inspection/inspect.ts index 3ba82c1dd..ea7a15ba9 100644 --- a/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/ocr2/inspection/inspect.ts +++ b/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/ocr2/inspection/inspect.ts @@ -4,7 +4,7 @@ import { SolanaCommand, TransactionResponse } from '@chainlink/gauntlet-solana' import { Keypair, PublicKey, SystemProgram } from '@solana/web3.js' import { CONTRACT_LIST, getContract } from '../../../../lib/contracts' import { deserializeConfig } from '../../../../lib/encoding' -import WriteOffchainConfig, { OffchainConfig } from '../proposeOffchainConfig' +import WriteOffchainConfig, { OffchainConfig } from '../proposeConfig' import { toComparableLongNumber, toComparableNumber, toComparablePubKey } from '../../../../lib/inspection' import RDD from '../../../../lib/rdd' diff --git a/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/ocr2/proposal/acceptProposal.ts b/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/ocr2/proposal/acceptProposal.ts index 2b3eefca9..8d0242b17 100644 --- a/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/ocr2/proposal/acceptProposal.ts +++ b/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/ocr2/proposal/acceptProposal.ts @@ -6,9 +6,9 @@ import { PublicKey } from '@solana/web3.js' import { getAssociatedTokenAddress, TOKEN_PROGRAM_ID } from '@solana/spl-token' import { utils } from '@project-serum/anchor' import { CONTRACT_LIST, getContract } from '../../../../lib/contracts' -import ProposeOffchainConfig, { OffchainConfig } from '../proposeOffchainConfig' +import ProposeConfig, { OffchainConfig } from '../proposeConfig' import { serializeOffchainConfig, deserializeConfig } from '../../../../lib/encoding' -import { prepareOffchainConfigForDiff } from '../proposeOffchainConfig' +import { prepareOffchainConfigForDiff } from '../proposeConfig' import RDD from '../../../../lib/rdd' import { printDiff } from '../../../../lib/diff' @@ -98,7 +98,7 @@ export default class AcceptProposal extends SolanaCommand { })) .sort((a, b) => Buffer.compare(_toHex(a.signer), _toHex(b.signer))) - const offchainConfig = ProposeOffchainConfig.makeInputFromRDD(rdd, this.args[0]) + const offchainConfig = ProposeConfig.makeInputFromRDD(rdd, this.args[0]) const f = aggregator.config.f diff --git a/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/ocr2/proposeConfig.ts b/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/ocr2/proposeConfig.ts index 9503e075b..9f576f96e 100644 --- a/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/ocr2/proposeConfig.ts +++ b/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/ocr2/proposeConfig.ts @@ -1,12 +1,92 @@ import { Result } from '@chainlink/gauntlet-core' -import { logger, BN, prompt } from '@chainlink/gauntlet-core/dist/utils' +import { logger, BN, time, prompt, longs } from '@chainlink/gauntlet-core/dist/utils' import { SolanaCommand, TransactionResponse } from '@chainlink/gauntlet-solana' import { PublicKey } from '@solana/web3.js' -import { ORACLES_MAX_LENGTH } from '../../../lib/constants' +import { MAX_TRANSACTION_BYTES, ORACLES_MAX_LENGTH } from '../../../lib/constants' import { CONTRACT_LIST, getContract } from '../../../lib/contracts' +import { divideIntoChunks } from '../../../lib/utils' +import { serializeOffchainConfig, deserializeConfig } from '../../../lib/encoding' import RDD from '../../../lib/rdd' import { printDiff } from '../../../lib/diff' +export type OffchainConfig = { + deltaProgressNanoseconds: number + deltaResendNanoseconds: number + deltaRoundNanoseconds: number + deltaGraceNanoseconds: number + deltaStageNanoseconds: number + rMax: number + s: number[] + offchainPublicKeys: string[] + peerIds: string[] + reportingPluginConfig: { + alphaReportInfinite: boolean + alphaReportPpb: number + alphaAcceptInfinite: boolean + alphaAcceptPpb: number + deltaCNanoseconds: number + } + maxDurationQueryNanoseconds: number + maxDurationObservationNanoseconds: number + maxDurationReportNanoseconds: number + maxDurationShouldAcceptFinalizedReportNanoseconds: number + maxDurationShouldTransmitAcceptedReportNanoseconds: number + configPublicKeys: string[] +} + +const validateConfig = (input: OffchainConfig): boolean => { + const _isNegative = (v: number): boolean => new BN(v).lt(new BN(0)) + const nonNegativeValues = [ + 'deltaProgressNanoseconds', + 'deltaResendNanoseconds', + 'deltaRoundNanoseconds', + 'deltaGraceNanoseconds', + 'deltaStageNanoseconds', + 'maxDurationQueryNanoseconds', + 'maxDurationObservationNanoseconds', + 'maxDurationReportNanoseconds', + 'maxDurationShouldAcceptFinalizedReportNanoseconds', + 'maxDurationShouldTransmitAcceptedReportNanoseconds', + ] + for (let prop in nonNegativeValues) { + if (_isNegative(input[prop])) throw new Error(`${prop} must be non-negative`) + } + const safeIntervalNanoseconds = new BN(200).mul(time.Millisecond).toNumber() + if (input.deltaProgressNanoseconds < safeIntervalNanoseconds) + throw new Error( + `deltaProgressNanoseconds (${input.deltaProgressNanoseconds} ns) is set below the resource exhaustion safe interval (${safeIntervalNanoseconds} ns)`, + ) + if (input.deltaResendNanoseconds < safeIntervalNanoseconds) + throw new Error( + `deltaResendNanoseconds (${input.deltaResendNanoseconds} ns) is set below the resource exhaustion safe interval (${safeIntervalNanoseconds} ns)`, + ) + + if (input.deltaRoundNanoseconds >= input.deltaProgressNanoseconds) + throw new Error( + `deltaRoundNanoseconds (${input.deltaRoundNanoseconds}) must be less than deltaProgressNanoseconds (${input.deltaProgressNanoseconds})`, + ) + const sumMaxDurationsReportGeneration = new BN(input.maxDurationQueryNanoseconds) + .add(new BN(input.maxDurationObservationNanoseconds)) + .add(new BN(input.maxDurationReportNanoseconds)) + + if (sumMaxDurationsReportGeneration.gte(new BN(input.deltaProgressNanoseconds))) + throw new Error( + `sum of MaxDurationQuery/Observation/Report (${sumMaxDurationsReportGeneration}) must be less than deltaProgressNanoseconds (${input.deltaProgressNanoseconds})`, + ) + + if (input.rMax <= 0 || input.rMax >= 255) + throw new Error(`rMax (${input.rMax}) must be greater than zero and less than 255`) + + if (input.s.length >= 1000) throw new Error(`Length of S (${input.s.length}) must be less than 1000`) + for (let i = 0; i < input.s.length; i++) { + const s = input.s[i] + if (s < 0 || s > ORACLES_MAX_LENGTH) + throw new Error(`S[${i}] (${s}) must be between 0 and Max Oracles (${ORACLES_MAX_LENGTH})`) + } + + return true +} + type Input = { oracles: { signer: string @@ -14,9 +94,19 @@ type Input = { payee: string }[] f: number | string + offchainConfig: OffchainConfig + userSecret?: string proposalId: string } +export const prepareOffchainConfigForDiff = (config: OffchainConfig, extra?: Object): Object => { + return longs.longsInObjToNumbers({ + ...config, + ...(extra || {}), + offchainPublicKeys: config.offchainPublicKeys?.map((key) => Buffer.from(key).toString('hex')), + }) as Object +} + const _toHex = (a: string) => Buffer.from(a, 'hex') export default class ProposeConfig extends SolanaCommand { @@ -27,12 +117,67 @@ export default class ProposeConfig extends SolanaCommand { ] input: Input + randomSecret: string + + static makeInputFromRDD = (rdd: any, stateAddress: string): OffchainConfig => { + const aggregator = rdd.contracts[stateAddress] + const config = aggregator.config + + const _getSigner = (o) => o.ocr2OnchainPublicKey[0].replace('ocr2on_solana_', '') + const aggregatorOperators: any[] = aggregator.oracles + .map((o) => rdd.operators[o.operator]) + .sort((a, b) => Buffer.compare(_toHex(_getSigner(a)), _toHex(_getSigner(b)))) + const operatorsPublicKeys = aggregatorOperators.map((o) => + o.ocr2OffchainPublicKey[0].replace('ocr2off_solana_', ''), + ) + const operatorsPeerIds = aggregatorOperators.map((o) => o.peerId[0]) + const operatorConfigPublicKeys = aggregatorOperators.map((o) => + o.ocr2ConfigPublicKey[0].replace('ocr2cfg_solana_', ''), + ) + + const input: OffchainConfig = { + deltaProgressNanoseconds: time.durationToNanoseconds(config.deltaProgress).toNumber(), + deltaResendNanoseconds: time.durationToNanoseconds(config.deltaResend).toNumber(), + deltaRoundNanoseconds: time.durationToNanoseconds(config.deltaRound).toNumber(), + deltaGraceNanoseconds: time.durationToNanoseconds(config.deltaGrace).toNumber(), + deltaStageNanoseconds: time.durationToNanoseconds(config.deltaStage).toNumber(), + rMax: config.rMax, + s: config.s, + offchainPublicKeys: operatorsPublicKeys, + peerIds: operatorsPeerIds, + reportingPluginConfig: { + alphaReportInfinite: config.reportingPluginConfig.alphaReportInfinite, + alphaReportPpb: Number(config.reportingPluginConfig.alphaReportPpb), + alphaAcceptInfinite: config.reportingPluginConfig.alphaAcceptInfinite, + alphaAcceptPpb: Number(config.reportingPluginConfig.alphaAcceptPpb), + deltaCNanoseconds: time.durationToNanoseconds(config.reportingPluginConfig.deltaC).toNumber(), + }, + maxDurationQueryNanoseconds: time.durationToNanoseconds(config.maxDurationQuery).toNumber(), + maxDurationObservationNanoseconds: time.durationToNanoseconds(config.maxDurationObservation).toNumber(), + maxDurationReportNanoseconds: time.durationToNanoseconds(config.maxDurationReport).toNumber(), + maxDurationShouldAcceptFinalizedReportNanoseconds: time + .durationToNanoseconds(config.maxDurationShouldAcceptFinalizedReport) + .toNumber(), + maxDurationShouldTransmitAcceptedReportNanoseconds: time + .durationToNanoseconds(config.maxDurationShouldTransmitAcceptedReport) + .toNumber(), + configPublicKeys: operatorConfigPublicKeys, + } + return input + } makeInput = (userInput): Input => { if (userInput) return userInput as Input const rdd = RDD.load(this.flags.network, this.flags.rdd) const aggregator = rdd.contracts[this.args[0]] + const userSecret = this.flags.secret + if (!!userSecret) { + logger.info(`Using given random secret: ${userSecret}`) + } + + const offchainConfig = ProposeConfig.makeInputFromRDD(rdd, this.args[0]) + const aggregatorOperators: any[] = aggregator.oracles.map((o) => rdd.operators[o.operator]) const oracles = aggregatorOperators .map((operator) => ({ @@ -47,6 +192,7 @@ export default class ProposeConfig extends SolanaCommand { return { oracles, f, + offchainConfig, proposalId: this.flags.proposalId || this.flags.configProposal, } } @@ -58,6 +204,11 @@ export default class ProposeConfig extends SolanaCommand { 'Please provide Config Proposal ID with flag "proposalId" or "configProposal"', ) this.requireArgs('Please provide an aggregator address') + this.require( + // TODO: should be able to just rely on random secret? + !!process.env.SECRET, + 'Please specify the Gauntlet secret words e.g. SECRET="awe fluke polygon tonic lilly acuity onyx debra bound gilbert wane"', + ) } buildCommand = async (flags, args) => { @@ -114,7 +265,44 @@ export default class ProposeConfig extends SolanaCommand { .remainingAccounts(payees) .instruction() - return [configIx, payeesIx] + // proposeOffchainConfig + + const maxBufferSize = this.flags.bufferSize || MAX_TRANSACTION_BYTES + // process.env.SECRET is required on the command + const { offchainConfig, userSecret } = this.input + const { offchainConfig: serializedOffchainConfig, randomSecret } = await serializeOffchainConfig( + offchainConfig, + process.env.SECRET!, + userSecret, + ) + this.randomSecret = randomSecret + + validateConfig(this.input.offchainConfig) + + logger.info(`Offchain config size: ${serializedOffchainConfig.byteLength}`) + this.require(serializedOffchainConfig.byteLength < 4096, 'Offchain config must be lower than 4096 bytes') + + // There's a byte limit per transaction. Write the config in chunks + const offchainConfigChunks = divideIntoChunks(serializedOffchainConfig, maxBufferSize) + if (offchainConfigChunks.length > 1) { + logger.info( + `Config size (${serializedOffchainConfig.byteLength} bytes) is bigger than transaction limit. It needs to be configured using ${offchainConfigChunks.length} transactions`, + ) + } + + const offchainConfigIxs = await Promise.all( + offchainConfigChunks.map((buffer) => + this.program.methods + .writeOffchainConfig(buffer) + .accounts({ + proposal: proposal, + authority: signer, + }) + .instruction(), + ), + ) + + return [configIx, payeesIx, ...offchainConfigIxs] } beforeExecute = async () => { @@ -142,6 +330,25 @@ export default class ProposeConfig extends SolanaCommand { logger.info(`Proposed Config for contract ${this.args[0]}:`) printDiff(contractConfig, proposedConfig) + // Config in contract + const contractOffchainConfig = deserializeConfig( + Buffer.from(contractState.offchainConfig.xs).slice(0, contractState.offchainConfig.len.toNumber()), + ) + const contractOffchainConfigForDiff = prepareOffchainConfigForDiff(contractOffchainConfig) + const proposedConfigForDiff = prepareOffchainConfigForDiff(this.input.offchainConfig) + + logger.info(`Proposed OffchainConfig for contract ${this.args[0]}`) + printDiff(contractOffchainConfigForDiff, proposedConfigForDiff) + + logger.info( + `Important: Save this secret + - If you run this command for the same configuration, use the same RANDOM SECRET + - You will need to provide the secret to approve the config proposal + Provide it with --secret flag`, + ) + logger.info(`${this.randomSecret}`) + logger.line() + await prompt('Continue?') } @@ -169,7 +376,11 @@ export default class ProposeConfig extends SolanaCommand { logger.success(`Config set on tx ${txhash}`) return { - responses: [ // TODO: map over responses + data: { + secret: this.randomSecret, + }, + responses: [ + // TODO: map over responses { tx: this.wrapResponse(txhash, this.args[0]), contract: this.args[0], diff --git a/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/ocr2/proposeOffchainConfig.ts b/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/ocr2/proposeOffchainConfig.ts deleted file mode 100644 index cc0e5afd0..000000000 --- a/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/ocr2/proposeOffchainConfig.ts +++ /dev/null @@ -1,322 +0,0 @@ -import { Result } from '@chainlink/gauntlet-core' -import { logger, prompt, time, BN, longs } from '@chainlink/gauntlet-core/dist/utils' -import { SolanaCommand, TransactionResponse } from '@chainlink/gauntlet-solana' -import { PublicKey, TransactionInstruction } from '@solana/web3.js' -import { MAX_TRANSACTION_BYTES, ORACLES_MAX_LENGTH } from '../../../lib/constants' -import { CONTRACT_LIST, getContract } from '../../../lib/contracts' -import { divideIntoChunks } from '../../../lib/utils' -import { serializeOffchainConfig, deserializeConfig } from '../../../lib/encoding' -import RDD from '../../../lib/rdd' -import { printDiff } from '../../../lib/diff' - -export type OffchainConfig = { - deltaProgressNanoseconds: number - deltaResendNanoseconds: number - deltaRoundNanoseconds: number - deltaGraceNanoseconds: number - deltaStageNanoseconds: number - rMax: number - s: number[] - offchainPublicKeys: string[] - peerIds: string[] - reportingPluginConfig: { - alphaReportInfinite: boolean - alphaReportPpb: number - alphaAcceptInfinite: boolean - alphaAcceptPpb: number - deltaCNanoseconds: number - } - maxDurationQueryNanoseconds: number - maxDurationObservationNanoseconds: number - maxDurationReportNanoseconds: number - maxDurationShouldAcceptFinalizedReportNanoseconds: number - maxDurationShouldTransmitAcceptedReportNanoseconds: number - configPublicKeys: string[] -} - -type Input = { - proposalId: string - offchainConfig: OffchainConfig - userSecret?: string -} - -type ContractInput = { - serializedOffchainConfig: Buffer - randomSecret: string -} - -export const prepareOffchainConfigForDiff = (config: OffchainConfig, extra?: Object): Object => { - return longs.longsInObjToNumbers({ - ...config, - ...(extra || {}), - offchainPublicKeys: config.offchainPublicKeys?.map((key) => Buffer.from(key).toString('hex')), - }) as Object -} - -export default class ProposeOffchainConfig extends SolanaCommand { - static id = 'ocr2:propose_offchain_config' - static category = CONTRACT_LIST.OCR_2 - static examples = [ - 'yarn gauntlet ocr2:propose_offchain_config --network=devnet --rdd=[PATH_TO_RDD] --proposalId= ', - ] - - input: Input - contractInput: ContractInput - - constructor(flags, args) { - super(flags, args) - - this.require( - !!this.flags.proposalId || !!this.flags.configProposal, - 'Please provide Config Proposal ID with flag "proposalId" or "configProposal"', - ) - this.requireArgs('Please provide a valid aggregator address as arg') - this.require( - !!process.env.SECRET, - 'Please specify the Gauntlet secret words e.g. SECRET="awe fluke polygon tonic lilly acuity onyx debra bound gilbert wane"', - ) - } - - static makeInputFromRDD = (rdd: any, stateAddress: string): OffchainConfig => { - const aggregator = rdd.contracts[stateAddress] - const config = aggregator.config - - const _toHex = (a: string) => Buffer.from(a, 'hex') - const _getSigner = (o) => o.ocr2OnchainPublicKey[0].replace('ocr2on_solana_', '') - const aggregatorOperators: any[] = aggregator.oracles - .map((o) => rdd.operators[o.operator]) - .sort((a, b) => Buffer.compare(_toHex(_getSigner(a)), _toHex(_getSigner(b)))) - const operatorsPublicKeys = aggregatorOperators.map((o) => - o.ocr2OffchainPublicKey[0].replace('ocr2off_solana_', ''), - ) - const operatorsPeerIds = aggregatorOperators.map((o) => o.peerId[0]) - const operatorConfigPublicKeys = aggregatorOperators.map((o) => - o.ocr2ConfigPublicKey[0].replace('ocr2cfg_solana_', ''), - ) - - const input: OffchainConfig = { - deltaProgressNanoseconds: time.durationToNanoseconds(config.deltaProgress).toNumber(), - deltaResendNanoseconds: time.durationToNanoseconds(config.deltaResend).toNumber(), - deltaRoundNanoseconds: time.durationToNanoseconds(config.deltaRound).toNumber(), - deltaGraceNanoseconds: time.durationToNanoseconds(config.deltaGrace).toNumber(), - deltaStageNanoseconds: time.durationToNanoseconds(config.deltaStage).toNumber(), - rMax: config.rMax, - s: config.s, - offchainPublicKeys: operatorsPublicKeys, - peerIds: operatorsPeerIds, - reportingPluginConfig: { - alphaReportInfinite: config.reportingPluginConfig.alphaReportInfinite, - alphaReportPpb: Number(config.reportingPluginConfig.alphaReportPpb), - alphaAcceptInfinite: config.reportingPluginConfig.alphaAcceptInfinite, - alphaAcceptPpb: Number(config.reportingPluginConfig.alphaAcceptPpb), - deltaCNanoseconds: time.durationToNanoseconds(config.reportingPluginConfig.deltaC).toNumber(), - }, - maxDurationQueryNanoseconds: time.durationToNanoseconds(config.maxDurationQuery).toNumber(), - maxDurationObservationNanoseconds: time.durationToNanoseconds(config.maxDurationObservation).toNumber(), - maxDurationReportNanoseconds: time.durationToNanoseconds(config.maxDurationReport).toNumber(), - maxDurationShouldAcceptFinalizedReportNanoseconds: time - .durationToNanoseconds(config.maxDurationShouldAcceptFinalizedReport) - .toNumber(), - maxDurationShouldTransmitAcceptedReportNanoseconds: time - .durationToNanoseconds(config.maxDurationShouldTransmitAcceptedReport) - .toNumber(), - configPublicKeys: operatorConfigPublicKeys, - } - return input - } - - makeInput = async (userInput: any): Promise => { - if (userInput) return userInput as Input - const rdd = RDD.load(this.flags.network, this.flags.rdd) - - const ocr2 = getContract(CONTRACT_LIST.OCR_2, '') - const address = ocr2.programId.toString() - this.program = this.loadProgram(ocr2.idl, address) - - const userSecret = this.flags.secret - if (!!userSecret) { - logger.info(`Using given random secret: ${userSecret}`) - } - - const offchainConfig = ProposeOffchainConfig.makeInputFromRDD(rdd, this.args[0]) - - return { - offchainConfig, - proposalId: this.flags.proposalId || this.flags.configProposal, - userSecret, - } - } - - makeContractInput = async (input: Input): Promise => { - // process.env.SECRET is required on the command - const { offchainConfig, userSecret } = input - const { offchainConfig: serializedOffchainConfig, randomSecret } = await serializeOffchainConfig( - offchainConfig, - process.env.SECRET!, - userSecret, - ) - - return { - serializedOffchainConfig, - randomSecret, - } - } - - validateConfig = (input: OffchainConfig): boolean => { - const _isNegative = (v: number): boolean => new BN(v).lt(new BN(0)) - const nonNegativeValues = [ - 'deltaProgressNanoseconds', - 'deltaResendNanoseconds', - 'deltaRoundNanoseconds', - 'deltaGraceNanoseconds', - 'deltaStageNanoseconds', - 'maxDurationQueryNanoseconds', - 'maxDurationObservationNanoseconds', - 'maxDurationReportNanoseconds', - 'maxDurationShouldAcceptFinalizedReportNanoseconds', - 'maxDurationShouldTransmitAcceptedReportNanoseconds', - ] - for (let prop in nonNegativeValues) { - if (_isNegative(input[prop])) throw new Error(`${prop} must be non-negative`) - } - const safeIntervalNanoseconds = new BN(200).mul(time.Millisecond).toNumber() - if (input.deltaProgressNanoseconds < safeIntervalNanoseconds) - throw new Error( - `deltaProgressNanoseconds (${input.deltaProgressNanoseconds} ns) is set below the resource exhaustion safe interval (${safeIntervalNanoseconds} ns)`, - ) - if (input.deltaResendNanoseconds < safeIntervalNanoseconds) - throw new Error( - `deltaResendNanoseconds (${input.deltaResendNanoseconds} ns) is set below the resource exhaustion safe interval (${safeIntervalNanoseconds} ns)`, - ) - - if (input.deltaRoundNanoseconds >= input.deltaProgressNanoseconds) - throw new Error( - `deltaRoundNanoseconds (${input.deltaRoundNanoseconds}) must be less than deltaProgressNanoseconds (${input.deltaProgressNanoseconds})`, - ) - const sumMaxDurationsReportGeneration = new BN(input.maxDurationQueryNanoseconds) - .add(new BN(input.maxDurationObservationNanoseconds)) - .add(new BN(input.maxDurationReportNanoseconds)) - - if (sumMaxDurationsReportGeneration.gte(new BN(input.deltaProgressNanoseconds))) - throw new Error( - `sum of MaxDurationQuery/Observation/Report (${sumMaxDurationsReportGeneration}) must be less than deltaProgressNanoseconds (${input.deltaProgressNanoseconds})`, - ) - - if (input.rMax <= 0 || input.rMax >= 255) - throw new Error(`rMax (${input.rMax}) must be greater than zero and less than 255`) - - if (input.s.length >= 1000) throw new Error(`Length of S (${input.s.length}) must be less than 1000`) - for (let i = 0; i < input.s.length; i++) { - const s = input.s[i] - if (s < 0 || s > ORACLES_MAX_LENGTH) - throw new Error(`S[${i}] (${s}) must be between 0 and Max Oracles (${ORACLES_MAX_LENGTH})`) - } - - return true - } - - makeRawTransaction = async (signer: PublicKey): Promise => { - const proposal = new PublicKey(this.input.proposalId) - const maxBufferSize = this.flags.bufferSize || MAX_TRANSACTION_BYTES - this.validateConfig(this.input.offchainConfig) - - logger.info(`Offchain config size: ${this.contractInput.serializedOffchainConfig.byteLength}`) - this.require( - this.contractInput.serializedOffchainConfig.byteLength < 4096, - 'Offchain config must be lower than 4096 bytes', - ) - - // There's a byte limit per transaction. Write the config in chunks - const offchainConfigChunks = divideIntoChunks(this.contractInput.serializedOffchainConfig, maxBufferSize) - if (offchainConfigChunks.length > 1) { - logger.info( - `Config size (${this.contractInput.serializedOffchainConfig.byteLength} bytes) is bigger than transaction limit. It needs to be configured using ${offchainConfigChunks.length} transactions`, - ) - } - - const ixs = await Promise.all( - offchainConfigChunks.map((buffer) => - this.program.methods - .writeOffchainConfig(buffer) - .accounts({ - proposal: proposal, - authority: signer, - }) - .instruction(), - ), - ) - - return ixs - } - - buildCommand = async (flags, args) => { - const ocr2 = getContract(CONTRACT_LIST.OCR_2, '') - this.program = this.loadProgram(ocr2.idl, ocr2.programId.toString()) - this.input = await this.makeInput(flags.input) - this.contractInput = await this.makeContractInput(this.input) - - return this - } - - beforeExecute = async () => { - const state = new PublicKey(this.args[0]) - const contractState = (await this.program.account.state.fetch(state)) as any - - // Config in contract - const contractOffchainConfig = deserializeConfig( - Buffer.from(contractState.offchainConfig.xs).slice(0, contractState.offchainConfig.len.toNumber()), - ) - const contractOffchainConfigForDiff = prepareOffchainConfigForDiff(contractOffchainConfig) - - // Proposed config - const proposedConfig = deserializeConfig(this.contractInput.serializedOffchainConfig) - const proposedConfigForDiff = prepareOffchainConfigForDiff(proposedConfig) - - logger.info(`Proposed OffchainConfig for contract ${this.args[0]}`) - printDiff(contractOffchainConfigForDiff, proposedConfigForDiff) - - logger.info( - `Important: Save this secret - - If you run this command for the same configuration, use the same RANDOM SECRET - - You will need to provide the secret to approve the config proposal - Provide it with --secret flag`, - ) - logger.info(`${this.contractInput.randomSecret}`) - logger.line() - - await prompt('Continue?') - } - - execute = async () => { - await this.buildCommand(this.flags, this.args) - - const signer = this.wallet.publicKey - await this.beforeExecute() - - const state = new PublicKey(this.args[0]) - const rawTx = await this.makeRawTransaction(signer) - const startingPoint = new BN(this.flags.instruction || 0).toNumber() - await prompt(`Start writing offchain config from ${startingPoint}/${rawTx.length - 1}?`) - - const txs: string[] = [] - for (let i = startingPoint; i < rawTx.length; i++) { - await this.simulateTx(signer, [rawTx[i]]) - logger.loading(`Sending ${i}/${rawTx.length - 1}...`) - const txhash = await this.signAndSendRawTx([rawTx[i]]) - txs.push(txhash) - } - logger.success(`Last tx Write offchain config set on tx ${txs[txs.length - 1]}`) - - return { - data: { - secret: this.contractInput.randomSecret, - }, - responses: [ - { - tx: this.wrapResponse(txs[txs.length - 1], state.toString(), { state: state.toString() }), - contract: state.toString(), - }, - ], - } as Result - } -} diff --git a/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/ocr2/setup.dev.flow.ts b/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/ocr2/setup.dev.flow.ts index 20f5f58e1..229af3eca 100644 --- a/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/ocr2/setup.dev.flow.ts +++ b/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/ocr2/setup.dev.flow.ts @@ -7,13 +7,10 @@ import Initialize from './initialize' import InitializeAC from '../accessController/initialize' import InitializeStore from '../store/initialize' import DeployToken from '../token/deploy' -import SetValidatorConfig from '../store/setValidatorConfig' -import AddAccess from '../accessController/addAccess' import SetBilling from './setBilling' import CreateFeed from '../store/createFeed' import SetWriter from '../store/setWriter' import CreateProposal from './proposal/createProposal' -import ProposeOffchainConfig from './proposeOffchainConfig' import ProposeConfig from './proposeConfig' import FinalizeProposal from './proposal/finalizeProposal' import AcceptProposal from './proposal/acceptProposal' @@ -208,17 +205,6 @@ export default class SetupFlow extends FlowCommand { input: { oracles: configInput.oracles, f: configInput.f, - proposalId: this.getReportStepDataById(FlowCommand.ID.data(this.stepIds.PROPOSAL, 'proposal')), - }, - proposalId: FlowCommand.ID.data(this.stepIds.PROPOSAL, 'proposal'), - }, - args: [FlowCommand.ID.contract(this.stepIds.OCR_2)], - }, - { - name: 'Propose Offchain Config', - command: ProposeOffchainConfig, - flags: { - input: { offchainConfig: offchainConfigInput, proposalId: this.getReportStepDataById(FlowCommand.ID.data(this.stepIds.PROPOSAL, 'proposal')), }, diff --git a/gauntlet/packages/gauntlet-solana-contracts/src/lib/encoding.ts b/gauntlet/packages/gauntlet-solana-contracts/src/lib/encoding.ts index ab23720d5..5acfa2556 100644 --- a/gauntlet/packages/gauntlet-solana-contracts/src/lib/encoding.ts +++ b/gauntlet/packages/gauntlet-solana-contracts/src/lib/encoding.ts @@ -1,6 +1,6 @@ import { Proto, sharedSecretEncryptions } from '@chainlink/gauntlet-core/dist/crypto' import { join } from 'path' -import { OffchainConfig } from '../commands/contracts/ocr2/proposeOffchainConfig' +import { OffchainConfig } from '../commands/contracts/ocr2/proposeConfig' import { descriptor as OCR2Descriptor } from './ocr2Proto' export const deserializeConfig = (buffer: Buffer): any => {