From 3fcaec99903295125081270d5638b37c681985e3 Mon Sep 17 00:00:00 2001 From: Makram Date: Tue, 6 Aug 2024 18:02:27 +0300 Subject: [PATCH] core/capabilities/ccip: use OCR offchain config (#1264) We want to define and use the appropriate OCR offchain config for each plugin. Requires https://github.com/smartcontractkit/chainlink-ccip/pull/36/ --- .../ccip/ccip_integration_tests/helpers.go | 136 ++++++++++++------ .../ccip_integration_tests/home_chain_test.go | 23 ++- .../ccip/configs/evm/chain_writer.go | 22 ++- .../ccip/ocrimpls/config_tracker.go | 29 ++-- .../ccip/oraclecreator/inprocess.go | 32 ++++- 5 files changed, 177 insertions(+), 65 deletions(-) diff --git a/core/capabilities/ccip/ccip_integration_tests/helpers.go b/core/capabilities/ccip/ccip_integration_tests/helpers.go index 057e48b3e7b..7606c8bbebc 100644 --- a/core/capabilities/ccip/ccip_integration_tests/helpers.go +++ b/core/capabilities/ccip/ccip_integration_tests/helpers.go @@ -5,10 +5,13 @@ import ( "encoding/hex" "math/big" "sort" - "strconv" "testing" "time" + "github.com/smartcontractkit/chainlink-ccip/chainconfig" + "github.com/smartcontractkit/chainlink-ccip/pluginconfig" + commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" + "github.com/smartcontractkit/chainlink-common/pkg/types/ccipocr3" "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/ccip_integration_tests/integrationhelpers" cctypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types" @@ -53,9 +56,27 @@ const ( CapabilityLabelledName = "ccip" CapabilityVersion = "v1.0.0" NodeOperatorID = 1 - // TODO: this is 8 hours now to match what is hardcoded in the exec plugin. - // Eventually this should drive what is set in the exec plugin. - FirstBlockAge = 8 * time.Hour + + // These constants drive what is set in the plugin offchain configs. + FirstBlockAge = 8 * time.Hour + RemoteGasPriceBatchWriteFrequency = 30 * time.Minute + BatchGasLimit = 6_500_000 + RelativeBoostPerWaitHour = 1.5 + InflightCacheExpiry = 10 * time.Minute + RootSnoozeTime = 30 * time.Minute + BatchingStrategyID = 0 + DeltaProgress = 30 * time.Second + DeltaResend = 10 * time.Second + DeltaInitial = 20 * time.Second + DeltaRound = 2 * time.Second + DeltaGrace = 2 * time.Second + DeltaCertifiedCommitRequest = 10 * time.Second + DeltaStage = 10 * time.Second + Rmax = 3 + MaxDurationQuery = 50 * time.Millisecond + MaxDurationObservation = 5 * time.Second + MaxDurationShouldAcceptAttestedReport = 10 * time.Second + MaxDurationShouldTransmitAcceptedReport = 10 * time.Second ) func e18Mult(amount uint64) *big.Int { @@ -412,11 +433,18 @@ func AddChainConfig( // Need to sort, otherwise _checkIsValidUniqueSubset onChain will fail sortP2PIDS(p2pIDs) // First Add ChainConfig that includes all p2pIDs as readers - chainConfig := integrationhelpers.SetupConfigInfo(chainSelector, p2pIDs, f, []byte(strconv.FormatUint(chainSelector, 10))) + encodedExtraChainConfig, err := chainconfig.EncodeChainConfig(chainconfig.ChainConfig{ + GasPriceDeviationPPB: ccipocr3.NewBigIntFromInt64(1000), + DAGasPriceDeviationPPB: ccipocr3.NewBigIntFromInt64(0), + FinalityDepth: 10, + OptimisticConfirmations: 1, + }) + require.NoError(t, err) + chainConfig := integrationhelpers.SetupConfigInfo(chainSelector, p2pIDs, f, encodedExtraChainConfig) inputConfig := []ccip_config.CCIPConfigTypesChainConfigInfo{ chainConfig, } - _, err := h.ccipConfig.ApplyChainConfigUpdates(h.owner, nil, inputConfig) + _, err = h.ccipConfig.ApplyChainConfigUpdates(h.owner, nil, inputConfig) require.NoError(t, err) h.backend.Commit() return chainConfig @@ -437,49 +465,71 @@ func (h *homeChain) AddDON( for range oracles { schedule = append(schedule, 1) } - signers, transmitters, f, _, offchainConfigVersion, offchainConfig, err := ocr3confighelper.ContractSetConfigArgsForTests( - 30*time.Second, // deltaProgress - 10*time.Second, // deltaResend - 20*time.Second, // deltaInitial - 2*time.Second, // deltaRound - 2*time.Second, // deltaGrace - 10*time.Second, // deltaCertifiedCommitRequest TODO: whats a good value for this? - 10*time.Second, // deltaStage - 3, // rmax - schedule, - oracles, - []byte{}, // empty offchain config - 50*time.Millisecond, // maxDurationQuery - 5*time.Second, // maxDurationObservation - 10*time.Second, // maxDurationShouldAcceptAttestedReport - 10*time.Second, // maxDurationShouldTransmitAcceptedReport - int(f), - []byte{}) // empty OnChainConfig - require.NoError(t, err, "failed to create contract config") tabi, err := ocr3_config_encoder.IOCR3ConfigEncoderMetaData.GetAbi() require.NoError(t, err) - signersBytes := make([][]byte, len(signers)) - for i, signer := range signers { - signersBytes[i] = signer - } - - transmittersBytes := make([][]byte, len(transmitters)) - for i, transmitter := range transmitters { - // anotherErr because linting doesn't want to shadow err - parsed, anotherErr := common.ParseHexOrString(string(transmitter)) - require.NoError(t, anotherErr) - transmittersBytes[i] = parsed - } - // Add DON on capability registry contract var ocr3Configs []ocr3_config_encoder.CCIPConfigTypesOCR3Config for _, pluginType := range []cctypes.PluginType{cctypes.PluginTypeCCIPCommit, cctypes.PluginTypeCCIPExec} { + var encodedOffchainConfig []byte + var err2 error + if pluginType == cctypes.PluginTypeCCIPCommit { + encodedOffchainConfig, err2 = pluginconfig.EncodeCommitOffchainConfig(pluginconfig.CommitOffchainConfig{ + RemoteGasPriceBatchWriteFrequency: *commonconfig.MustNewDuration(RemoteGasPriceBatchWriteFrequency), + // TODO: implement token price writes + // TokenPriceBatchWriteFrequency: *commonconfig.MustNewDuration(tokenPriceBatchWriteFrequency), + }) + require.NoError(t, err2) + } else { + encodedOffchainConfig, err2 = pluginconfig.EncodeExecuteOffchainConfig(pluginconfig.ExecuteOffchainConfig{ + BatchGasLimit: BatchGasLimit, + RelativeBoostPerWaitHour: RelativeBoostPerWaitHour, + MessageVisibilityInterval: *commonconfig.MustNewDuration(FirstBlockAge), + InflightCacheExpiry: *commonconfig.MustNewDuration(InflightCacheExpiry), + RootSnoozeTime: *commonconfig.MustNewDuration(RootSnoozeTime), + BatchingStrategyID: BatchingStrategyID, + }) + require.NoError(t, err2) + } + signers, transmitters, configF, _, offchainConfigVersion, offchainConfig, err2 := ocr3confighelper.ContractSetConfigArgsForTests( + DeltaProgress, + DeltaResend, + DeltaInitial, + DeltaRound, + DeltaGrace, + DeltaCertifiedCommitRequest, + DeltaStage, + Rmax, + schedule, + oracles, + encodedOffchainConfig, + MaxDurationQuery, + MaxDurationObservation, + MaxDurationShouldAcceptAttestedReport, + MaxDurationShouldTransmitAcceptedReport, + int(f), + []byte{}, // empty OnChainConfig + ) + require.NoError(t, err2, "failed to create contract config") + + signersBytes := make([][]byte, len(signers)) + for i, signer := range signers { + signersBytes[i] = signer + } + + transmittersBytes := make([][]byte, len(transmitters)) + for i, transmitter := range transmitters { + // anotherErr because linting doesn't want to shadow err + parsed, anotherErr := common.ParseHexOrString(string(transmitter)) + require.NoError(t, anotherErr) + transmittersBytes[i] = parsed + } + ocr3Configs = append(ocr3Configs, ocr3_config_encoder.CCIPConfigTypesOCR3Config{ PluginType: uint8(pluginType), ChainSelector: chainSelector, - F: f, + F: configF, OffchainConfigVersion: offchainConfigVersion, OfframpAddress: uni.offramp.Address().Bytes(), BootstrapP2PIds: [][32]byte{bootstrapP2PID}, @@ -522,13 +572,13 @@ func (h *homeChain) AddDON( require.NotZero(t, donID, "failed to get donID from config set event") var signerAddresses []common.Address - for _, signer := range signers { - signerAddresses = append(signerAddresses, common.BytesToAddress(signer)) + for _, oracle := range oracles { + signerAddresses = append(signerAddresses, common.BytesToAddress(oracle.OnchainPublicKey)) } var transmitterAddresses []common.Address - for _, transmitter := range transmitters { - transmitterAddresses = append(transmitterAddresses, common.HexToAddress(string(transmitter))) + for _, oracle := range oracles { + transmitterAddresses = append(transmitterAddresses, common.HexToAddress(string(oracle.TransmitAccount))) } // get the config digest from the ccip config contract and set config on the offramp. diff --git a/core/capabilities/ccip/ccip_integration_tests/home_chain_test.go b/core/capabilities/ccip/ccip_integration_tests/home_chain_test.go index 2188e9ea75d..c78fd37b809 100644 --- a/core/capabilities/ccip/ccip_integration_tests/home_chain_test.go +++ b/core/capabilities/ccip/ccip_integration_tests/home_chain_test.go @@ -11,6 +11,7 @@ import ( libocrtypes "github.com/smartcontractkit/libocr/ragep2p/types" + "github.com/smartcontractkit/chainlink-ccip/chainconfig" ccipreader "github.com/smartcontractkit/chainlink-ccip/pkg/reader" cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccipocr3" @@ -35,15 +36,22 @@ func TestHomeChainReader(t *testing.T) { p2pIDs := integrationhelpers.P2pIDsFromInts(arr) uni.AddCapability(p2pIDs) //==============================Apply configs to Capability Contract================================= - chainAConf := integrationhelpers.SetupConfigInfo(integrationhelpers.ChainA, p2pIDs, integrationhelpers.FChainA, []byte("ChainA")) - chainBConf := integrationhelpers.SetupConfigInfo(integrationhelpers.ChainB, p2pIDs[1:], integrationhelpers.FChainB, []byte("ChainB")) - chainCConf := integrationhelpers.SetupConfigInfo(integrationhelpers.ChainC, p2pIDs[2:], integrationhelpers.FChainC, []byte("ChainC")) + encodedChainConfig, err := chainconfig.EncodeChainConfig(chainconfig.ChainConfig{ + GasPriceDeviationPPB: cciptypes.NewBigIntFromInt64(1000), + DAGasPriceDeviationPPB: cciptypes.NewBigIntFromInt64(1_000_000), + FinalityDepth: -1, + OptimisticConfirmations: 1, + }) + require.NoError(t, err) + chainAConf := integrationhelpers.SetupConfigInfo(integrationhelpers.ChainA, p2pIDs, integrationhelpers.FChainA, encodedChainConfig) + chainBConf := integrationhelpers.SetupConfigInfo(integrationhelpers.ChainB, p2pIDs[1:], integrationhelpers.FChainB, encodedChainConfig) + chainCConf := integrationhelpers.SetupConfigInfo(integrationhelpers.ChainC, p2pIDs[2:], integrationhelpers.FChainC, encodedChainConfig) inputConfig := []capcfg.CCIPConfigTypesChainConfigInfo{ chainAConf, chainBConf, chainCConf, } - _, err := uni.CcipCfg.ApplyChainConfigUpdates(uni.Transactor, nil, inputConfig) + _, err = uni.CcipCfg.ApplyChainConfigUpdates(uni.Transactor, nil, inputConfig) require.NoError(t, err) uni.Backend.Commit() //================================Setup HomeChainReader=============================== @@ -63,6 +71,7 @@ func TestHomeChainReader(t *testing.T) { expectedChainConfigs[cciptypes.ChainSelector(c.ChainSelector)] = ccipreader.ChainConfig{ FChain: int(c.ChainConfig.FChain), SupportedNodes: toPeerIDs(c.ChainConfig.Readers), + Config: mustDecodeChainConfig(t, c.ChainConfig.Config), } } configs, err := homeChain.GetAllChainConfigs() @@ -86,3 +95,9 @@ func toPeerIDs(readers [][32]byte) mapset.Set[libocrtypes.PeerID] { } return peerIDs } + +func mustDecodeChainConfig(t *testing.T, encodedChainConfig []byte) chainconfig.ChainConfig { + chainConfig, err := chainconfig.DecodeChainConfig(encodedChainConfig) + require.NoError(t, err) + return chainConfig +} diff --git a/core/capabilities/ccip/configs/evm/chain_writer.go b/core/capabilities/ccip/configs/evm/chain_writer.go index b112681c645..6d3b73c6f5c 100644 --- a/core/capabilities/ccip/configs/evm/chain_writer.go +++ b/core/capabilities/ccip/configs/evm/chain_writer.go @@ -20,8 +20,13 @@ var ( offrampABI = evmtypes.MustGetABI(evm_2_evm_multi_offramp.EVM2EVMMultiOffRampABI) ) -func MustChainWriterConfig(fromAddress common.Address, maxGasPrice *assets.Wei) []byte { - rawConfig := ChainWriterConfigRaw(fromAddress, maxGasPrice) +func MustChainWriterConfig( + fromAddress common.Address, + maxGasPrice *assets.Wei, + commitGasLimit, + execBatchGasLimit uint64, +) []byte { + rawConfig := ChainWriterConfigRaw(fromAddress, maxGasPrice, commitGasLimit, execBatchGasLimit) encoded, err := json.Marshal(rawConfig) if err != nil { panic(fmt.Errorf("failed to marshal ChainWriterConfig: %w", err)) @@ -31,7 +36,12 @@ func MustChainWriterConfig(fromAddress common.Address, maxGasPrice *assets.Wei) } // ChainWriterConfigRaw returns a ChainWriterConfig that can be used to transmit commit and execute reports. -func ChainWriterConfigRaw(fromAddress common.Address, maxGasPrice *assets.Wei) evmrelaytypes.ChainWriterConfig { +func ChainWriterConfigRaw( + fromAddress common.Address, + maxGasPrice *assets.Wei, + commitGasLimit, + execBatchGasLimit uint64, +) evmrelaytypes.ChainWriterConfig { return evmrelaytypes.ChainWriterConfig{ Contracts: map[string]*evmrelaytypes.ContractConfig{ consts.ContractNameOffRamp: { @@ -40,14 +50,12 @@ func ChainWriterConfigRaw(fromAddress common.Address, maxGasPrice *assets.Wei) e consts.MethodCommit: { ChainSpecificName: mustGetMethodName("commit", offrampABI), FromAddress: fromAddress, - // TODO: inject this into the method, should be fetched from the OCR config. - GasLimit: 500_000, + GasLimit: commitGasLimit, }, consts.MethodExecute: { ChainSpecificName: mustGetMethodName("execute", offrampABI), FromAddress: fromAddress, - // TODO: inject this into the method, should be fetched from the OCR config. - GasLimit: 6_500_000, + GasLimit: execBatchGasLimit, }, }, }, diff --git a/core/capabilities/ccip/ocrimpls/config_tracker.go b/core/capabilities/ccip/ocrimpls/config_tracker.go index f32c796a7a4..3a6a27fa40c 100644 --- a/core/capabilities/ccip/ocrimpls/config_tracker.go +++ b/core/capabilities/ccip/ocrimpls/config_tracker.go @@ -6,6 +6,7 @@ import ( cctypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types" gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3confighelper" "github.com/smartcontractkit/libocr/offchainreporting2plus/types" ) @@ -24,6 +25,20 @@ func (c *configTracker) LatestBlockHeight(ctx context.Context) (blockHeight uint // LatestConfig implements types.ContractConfigTracker. func (c *configTracker) LatestConfig(ctx context.Context, changedInBlock uint64) (types.ContractConfig, error) { + return c.contractConfig(), nil +} + +// LatestConfigDetails implements types.ContractConfigTracker. +func (c *configTracker) LatestConfigDetails(ctx context.Context) (changedInBlock uint64, configDigest types.ConfigDigest, err error) { + return 0, c.cfg.ConfigDigest, nil +} + +// Notify implements types.ContractConfigTracker. +func (c *configTracker) Notify() <-chan struct{} { + return nil +} + +func (c *configTracker) contractConfig() types.ContractConfig { return types.ContractConfig{ ConfigDigest: c.cfg.ConfigDigest, ConfigCount: c.cfg.ConfigCount, @@ -33,17 +48,13 @@ func (c *configTracker) LatestConfig(ctx context.Context, changedInBlock uint64) OnchainConfig: []byte{}, OffchainConfigVersion: c.cfg.Config.OffchainConfigVersion, OffchainConfig: c.cfg.Config.OffchainConfig, - }, nil -} - -// LatestConfigDetails implements types.ContractConfigTracker. -func (c *configTracker) LatestConfigDetails(ctx context.Context) (changedInBlock uint64, configDigest types.ConfigDigest, err error) { - return 0, c.cfg.ConfigDigest, nil + } } -// Notify implements types.ContractConfigTracker. -func (c *configTracker) Notify() <-chan struct{} { - return nil +// PublicConfig returns the OCR configuration as a PublicConfig so that we can +// access ReportingPluginConfig and other fields prior to launching the plugins. +func (c *configTracker) PublicConfig() (ocr3confighelper.PublicConfig, error) { + return ocr3confighelper.PublicConfigFromContractConfig(false, c.contractConfig()) } func toOnchainPublicKeys(signers [][]byte) []types.OnchainPublicKey { diff --git a/core/capabilities/ccip/oraclecreator/inprocess.go b/core/capabilities/ccip/oraclecreator/inprocess.go index 1d178678461..6616d356756 100644 --- a/core/capabilities/ccip/oraclecreator/inprocess.go +++ b/core/capabilities/ccip/oraclecreator/inprocess.go @@ -18,6 +18,7 @@ import ( chainsel "github.com/smartcontractkit/chain-selectors" "github.com/smartcontractkit/chainlink-ccip/pkg/consts" + "github.com/smartcontractkit/chainlink-ccip/pluginconfig" "github.com/smartcontractkit/libocr/commontypes" libocr3 "github.com/smartcontractkit/libocr/offchainreporting2plus" @@ -46,6 +47,10 @@ import ( var _ cctypes.OracleCreator = &inprocessOracleCreator{} +const ( + defaultCommitGasLimit = 500_000 +) + // inprocessOracleCreator creates oracles that reference plugins running // in the same process as the chainlink node, i.e not LOOPPs. type inprocessOracleCreator struct { @@ -148,6 +153,24 @@ func (i *inprocessOracleCreator) CreatePluginOracle(pluginType cctypes.PluginTyp destChainFamily := relay.NetworkEVM destRelayID := types.NewRelayID(destChainFamily, fmt.Sprintf("%d", destChainID)) + configTracker := ocrimpls.NewConfigTracker(config) + publicConfig, err := configTracker.PublicConfig() + if err != nil { + return nil, fmt.Errorf("failed to get public config from OCR config: %w", err) + } + var execBatchGasLimit uint64 + if pluginType == cctypes.PluginTypeCCIPExec { + execOffchainConfig, err2 := pluginconfig.DecodeExecuteOffchainConfig(publicConfig.ReportingPluginConfig) + if err2 != nil { + return nil, fmt.Errorf("failed to decode execute offchain config: %w, raw: %s", + err2, string(publicConfig.ReportingPluginConfig)) + } + if execOffchainConfig.BatchGasLimit == 0 && destChainFamily == relay.NetworkEVM { + return nil, fmt.Errorf("BatchGasLimit not set in execute offchain config, must be > 0") + } + execBatchGasLimit = execOffchainConfig.BatchGasLimit + } + // this is so that we can use the msg hasher and report encoder from that dest chain relayer's provider. contractReaders := make(map[cciptypes.ChainSelector]types.ContractReader) chainWriters := make(map[cciptypes.ChainSelector]types.ChainWriter) @@ -208,7 +231,12 @@ func (i *inprocessOracleCreator) CreatePluginOracle(pluginType cctypes.PluginTyp chain.Client(), chain.TxManager(), chain.GasEstimator(), - evmconfig.ChainWriterConfigRaw(fromAddress, chain.Config().EVM().GasEstimator().PriceMaxKey(fromAddress)), + evmconfig.ChainWriterConfigRaw( + fromAddress, + chain.Config().EVM().GasEstimator().PriceMaxKey(fromAddress), + defaultCommitGasLimit, + execBatchGasLimit, + ), ) if err2 != nil { return nil, fmt.Errorf("failed to create chain writer for chain %s: %w", chain.ID(), err2) @@ -297,7 +325,7 @@ func (i *inprocessOracleCreator) CreatePluginOracle(pluginType cctypes.PluginTyp BinaryNetworkEndpointFactory: i.peerWrapper.Peer2, Database: i.db, V2Bootstrappers: i.bootstrapperLocators, - ContractConfigTracker: ocrimpls.NewConfigTracker(config), + ContractConfigTracker: configTracker, ContractTransmitter: transmitter, LocalConfig: defaultLocalConfig(), Logger: ocrcommon.NewOCRWrapper(