From a2a67f8a29651aba555ad2058838c23038730c46 Mon Sep 17 00:00:00 2001 From: Anindita Ghosh <88458927+AnieeG@users.noreply.github.com> Date: Fri, 22 Nov 2024 11:25:33 -0800 Subject: [PATCH] CCIP-4320 : Part 1 - Parameterize ocr inputs (#15377) * verify if token prices are getting updated without lanes * parameterize ocr inputs * fix test * revert * comments * add rmn signature timeout * addressed review comments * fix tests --- deployment/ccip/changeset/active_candidate.go | 11 +- .../ccip/changeset/active_candidate_test.go | 11 +- deployment/ccip/changeset/add_chain.go | 11 +- deployment/ccip/changeset/add_chain_test.go | 7 +- deployment/ccip/changeset/add_lane.go | 115 ++++++++++++++++-- deployment/ccip/changeset/add_lane_test.go | 46 ++++++- deployment/ccip/changeset/deploy.go | 29 ++--- deployment/ccip/changeset/deploy_chain.go | 3 + .../ccip/changeset/deploy_home_chain.go | 12 +- .../ccip/changeset/initial_add_chain.go | 100 +++++++++++++++ .../changeset/internal/deploy_home_chain.go | 71 ++++++----- deployment/ccip/changeset/test_helpers.go | 7 +- deployment/common/types/types.go | 57 +++++++++ .../smoke/ccip/ccip_batching_test.go | 5 +- .../smoke/ccip/ccip_messaging_test.go | 2 +- .../smoke/ccip/fee_boosting_test.go | 2 +- integration-tests/testsetups/test_helpers.go | 8 +- 17 files changed, 408 insertions(+), 89 deletions(-) diff --git a/deployment/ccip/changeset/active_candidate.go b/deployment/ccip/changeset/active_candidate.go index a336cf69536..ee0c4d10ebf 100644 --- a/deployment/ccip/changeset/active_candidate.go +++ b/deployment/ccip/changeset/active_candidate.go @@ -52,15 +52,20 @@ func SetCandidatePluginChangeset( tokenConfig TokenConfig, pluginType cctypes.PluginType, ) (deployment.ChangesetOutput, error) { + ccipOCRParams := DefaultOCRParams( + feedChainSel, + tokenConfig.GetTokenInfo(e.Logger, state.Chains[newChainSel].LinkToken, state.Chains[newChainSel].Weth9), + nil, + ) newDONArgs, err := internal.BuildOCR3ConfigForCCIPHome( ocrSecrets, state.Chains[newChainSel].OffRamp, e.Chains[newChainSel], - feedChainSel, - tokenConfig.GetTokenInfo(e.Logger, state.Chains[newChainSel].LinkToken, state.Chains[newChainSel].Weth9), nodes.NonBootstraps(), state.Chains[homeChainSel].RMNHome.Address(), - nil, + ccipOCRParams.OCRParameters, + ccipOCRParams.CommitOffChainConfig, + ccipOCRParams.ExecuteOffChainConfig, ) if err != nil { return deployment.ChangesetOutput{}, err diff --git a/deployment/ccip/changeset/active_candidate_test.go b/deployment/ccip/changeset/active_candidate_test.go index e53d3d177ad..40c0240f3db 100644 --- a/deployment/ccip/changeset/active_candidate_test.go +++ b/deployment/ccip/changeset/active_candidate_test.go @@ -123,15 +123,20 @@ func TestActiveCandidate(t *testing.T) { // commit and exec plugin we will be using rmnHomeAddress := state.Chains[tenv.HomeChainSel].RMNHome.Address() tokenConfig := NewTestTokenConfig(state.Chains[tenv.FeedChainSel].USDFeeds) + ccipOCRParams := DefaultOCRParams( + tenv.FeedChainSel, + tokenConfig.GetTokenInfo(e.Logger, state.Chains[tenv.FeedChainSel].LinkToken, state.Chains[tenv.FeedChainSel].Weth9), + nil, + ) ocr3ConfigMap, err := internal.BuildOCR3ConfigForCCIPHome( deployment.XXXGenerateTestOCRSecrets(), state.Chains[tenv.FeedChainSel].OffRamp, e.Chains[tenv.FeedChainSel], - tenv.FeedChainSel, - tokenConfig.GetTokenInfo(e.Logger, state.Chains[tenv.FeedChainSel].LinkToken, state.Chains[tenv.FeedChainSel].Weth9), nodes.NonBootstraps(), rmnHomeAddress, - nil, + ccipOCRParams.OCRParameters, + ccipOCRParams.CommitOffChainConfig, + ccipOCRParams.ExecuteOffChainConfig, ) require.NoError(t, err) diff --git a/deployment/ccip/changeset/add_chain.go b/deployment/ccip/changeset/add_chain.go index cb7d0701973..bc4d8d6b97e 100644 --- a/deployment/ccip/changeset/add_chain.go +++ b/deployment/ccip/changeset/add_chain.go @@ -98,15 +98,20 @@ func AddDonAndSetCandidateChangeset( tokenConfig TokenConfig, pluginType types.PluginType, ) (deployment.ChangesetOutput, error) { + ccipOCRParams := DefaultOCRParams( + feedChainSel, + tokenConfig.GetTokenInfo(e.Logger, state.Chains[newChainSel].LinkToken, state.Chains[newChainSel].Weth9), + nil, + ) newDONArgs, err := internal.BuildOCR3ConfigForCCIPHome( ocrSecrets, state.Chains[newChainSel].OffRamp, e.Chains[newChainSel], - feedChainSel, - tokenConfig.GetTokenInfo(e.Logger, state.Chains[newChainSel].LinkToken, state.Chains[newChainSel].Weth9), nodes.NonBootstraps(), state.Chains[homeChainSel].RMNHome.Address(), - nil, + ccipOCRParams.OCRParameters, + ccipOCRParams.CommitOffChainConfig, + ccipOCRParams.ExecuteOffChainConfig, ) if err != nil { return deployment.ChangesetOutput{}, err diff --git a/deployment/ccip/changeset/add_chain_test.go b/deployment/ccip/changeset/add_chain_test.go index 81f9d2412ec..7b5ae9c43d7 100644 --- a/deployment/ccip/changeset/add_chain_test.go +++ b/deployment/ccip/changeset/add_chain_test.go @@ -59,12 +59,17 @@ func TestAddChainInbound(t *testing.T) { require.NoError(t, e.Env.ExistingAddresses.Merge(out.AddressBook)) newAddresses = deployment.NewMemoryAddressBook() tokenConfig := NewTestTokenConfig(state.Chains[e.FeedChainSel].USDFeeds) + ocrParams := make(map[uint64]CCIPOCRParams) + for _, chain := range initialDeploy { + ocrParams[chain] = DefaultOCRParams(e.FeedChainSel, nil, nil) + } err = deployCCIPContracts(e.Env, newAddresses, NewChainsConfig{ HomeChainSel: e.HomeChainSel, FeedChainSel: e.FeedChainSel, ChainsToDeploy: initialDeploy, TokenConfig: tokenConfig, OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), + OCRParams: ocrParams, }) require.NoError(t, err) @@ -75,7 +80,7 @@ func TestAddChainInbound(t *testing.T) { for _, source := range initialDeploy { for _, dest := range initialDeploy { if source != dest { - require.NoError(t, AddLaneWithDefaultPrices(e.Env, state, source, dest)) + require.NoError(t, AddLaneWithDefaultPricesAndFeeQuoterConfig(e.Env, state, source, dest, false)) } } } diff --git a/deployment/ccip/changeset/add_lane.go b/deployment/ccip/changeset/add_lane.go index 82cf60b77f6..0b16611021f 100644 --- a/deployment/ccip/changeset/add_lane.go +++ b/deployment/ccip/changeset/add_lane.go @@ -2,9 +2,11 @@ package changeset import ( "encoding/hex" + "fmt" "math/big" "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/ccipevm" @@ -15,25 +17,122 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" ) +var _ deployment.ChangeSet[AddLanesConfig] = AddLanesWithTestRouter + type InitialPrices struct { LinkPrice *big.Int // USD to the power of 18 (e18) per LINK WethPrice *big.Int // USD to the power of 18 (e18) per WETH GasPrice *big.Int // uint224 packed gas price in USD (112 for exec // 112 for da) } +func (p InitialPrices) Validate() error { + if p.LinkPrice == nil { + return fmt.Errorf("missing link price") + } + if p.WethPrice == nil { + return fmt.Errorf("missing weth price") + } + if p.GasPrice == nil { + return fmt.Errorf("missing gas price") + } + return nil +} + +type LaneConfig struct { + SourceSelector uint64 + DestSelector uint64 + InitialPricesBySource InitialPrices + FeeQuoterDestChain fee_quoter.FeeQuoterDestChainConfig +} + +type AddLanesConfig struct { + LaneConfigs []LaneConfig +} + +func (c AddLanesConfig) Validate() error { + for _, pair := range c.LaneConfigs { + if pair.SourceSelector == pair.DestSelector { + return fmt.Errorf("cannot add lane to the same chain") + } + if err := pair.InitialPricesBySource.Validate(); err != nil { + return fmt.Errorf("error in validating initial prices for chain %d : %w", pair.SourceSelector, err) + } + // TODO: add more FeeQuoterDestChainConfigArgs validation + if pair.FeeQuoterDestChain == (fee_quoter.FeeQuoterDestChainConfig{}) { + return fmt.Errorf("missing fee quoter dest chain config") + } + } + return nil +} + +// AddLanesWithTestRouter adds lanes between chains using the test router. +// AddLanesWithTestRouter is run while the contracts are still owned by the deployer. +// This is useful to test the initial deployment to enable lanes between chains. +// Once the testrouter is enabled, the lanes can be used to send messages between chains with testrouter. +// On successful verification with testrouter, the lanes can be enabled with the main router with different AddLane ChangeSet. +func AddLanesWithTestRouter(e deployment.Environment, cfg AddLanesConfig) (deployment.ChangesetOutput, error) { + if err := cfg.Validate(); err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("invalid AddLanesConfig: %w", err) + } + newAddresses := deployment.NewMemoryAddressBook() + err := addLanes(e, cfg) + if err != nil { + e.Logger.Errorw("Failed to add lanes", "err", err) + return deployment.ChangesetOutput{}, err + } + return deployment.ChangesetOutput{ + Proposals: []timelock.MCMSWithTimelockProposal{}, + AddressBook: newAddresses, + JobSpecs: nil, + }, nil +} + var DefaultInitialPrices = InitialPrices{ LinkPrice: deployment.E18Mult(20), WethPrice: deployment.E18Mult(4000), GasPrice: ToPackedFee(big.NewInt(8e14), big.NewInt(0)), } -func AddLaneWithDefaultPrices(e deployment.Environment, state CCIPOnChainState, from, to uint64) error { - return AddLane(e, state, from, to, DefaultInitialPrices) +func addLanes(e deployment.Environment, cfg AddLanesConfig) error { + state, err := LoadOnchainState(e) + if err != nil { + return fmt.Errorf("failed to load onchain state: %w", err) + } + for _, laneCfg := range cfg.LaneConfigs { + e.Logger.Infow("Enabling lane with test router", "from", laneCfg.SourceSelector, "to", laneCfg.DestSelector) + if err := AddLane(e, state, laneCfg, true); err != nil { + return err + } + } + return nil } -func AddLane(e deployment.Environment, state CCIPOnChainState, from, to uint64, initialPrices InitialPrices) error { +func AddLaneWithDefaultPricesAndFeeQuoterConfig(e deployment.Environment, state CCIPOnChainState, from, to uint64, isTestRouter bool) error { + cfg := LaneConfig{ + SourceSelector: from, + DestSelector: to, + InitialPricesBySource: DefaultInitialPrices, + FeeQuoterDestChain: DefaultFeeQuoterDestChainConfig(), + } + return AddLane(e, state, cfg, isTestRouter) +} + +func AddLane(e deployment.Environment, state CCIPOnChainState, config LaneConfig, isTestRouter bool) error { // TODO: Batch - tx, err := state.Chains[from].Router.ApplyRampUpdates(e.Chains[from].DeployerKey, []router.RouterOnRamp{ + var fromRouter *router.Router + var toRouter *router.Router + from := config.SourceSelector + to := config.DestSelector + feeQuoterDestChainConfig := config.FeeQuoterDestChain + initialPrices := config.InitialPricesBySource + if isTestRouter { + fromRouter = state.Chains[from].TestRouter + toRouter = state.Chains[to].TestRouter + } else { + fromRouter = state.Chains[from].Router + toRouter = state.Chains[to].Router + } + tx, err := fromRouter.ApplyRampUpdates(e.Chains[from].DeployerKey, []router.RouterOnRamp{ { DestChainSelector: to, OnRamp: state.Chains[from].OnRamp.Address(), @@ -46,7 +145,7 @@ func AddLane(e deployment.Environment, state CCIPOnChainState, from, to uint64, []onramp.OnRampDestChainConfigArgs{ { DestChainSelector: to, - Router: state.Chains[from].Router.Address(), + Router: fromRouter.Address(), }, }) if _, err := deployment.ConfirmIfNoError(e.Chains[from], tx, err); err != nil { @@ -80,7 +179,7 @@ func AddLane(e deployment.Environment, state CCIPOnChainState, from, to uint64, []fee_quoter.FeeQuoterDestChainConfigArgs{ { DestChainSelector: to, - DestChainConfig: DefaultFeeQuoterDestChainConfig(), + DestChainConfig: feeQuoterDestChainConfig, }, }) if _, err := deployment.ConfirmIfNoError(e.Chains[from], tx, err); err != nil { @@ -90,7 +189,7 @@ func AddLane(e deployment.Environment, state CCIPOnChainState, from, to uint64, tx, err = state.Chains[to].OffRamp.ApplySourceChainConfigUpdates(e.Chains[to].DeployerKey, []offramp.OffRampSourceChainConfigArgs{ { - Router: state.Chains[to].Router.Address(), + Router: toRouter.Address(), SourceChainSelector: from, IsEnabled: true, OnRamp: common.LeftPadBytes(state.Chains[from].OnRamp.Address().Bytes(), 32), @@ -99,7 +198,7 @@ func AddLane(e deployment.Environment, state CCIPOnChainState, from, to uint64, if _, err := deployment.ConfirmIfNoError(e.Chains[to], tx, err); err != nil { return err } - tx, err = state.Chains[to].Router.ApplyRampUpdates(e.Chains[to].DeployerKey, []router.RouterOnRamp{}, []router.RouterOffRamp{}, []router.RouterOffRamp{ + tx, err = toRouter.ApplyRampUpdates(e.Chains[to].DeployerKey, []router.RouterOnRamp{}, []router.RouterOffRamp{}, []router.RouterOffRamp{ { SourceChainSelector: from, OffRamp: state.Chains[to].OffRamp.Address(), diff --git a/deployment/ccip/changeset/add_lane_test.go b/deployment/ccip/changeset/add_lane_test.go index 1939d0da10a..194777c07bc 100644 --- a/deployment/ccip/changeset/add_lane_test.go +++ b/deployment/ccip/changeset/add_lane_test.go @@ -16,6 +16,48 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/logger" ) +func TestAddLanesWithTestRouter(t *testing.T) { + e := NewMemoryEnvironmentWithJobsAndContracts(t, logger.TestLogger(t), 2, 4, nil) + // Here we have CR + nodes set up, but no CCIP contracts deployed. + state, err := LoadOnchainState(e.Env) + require.NoError(t, err) + + selectors := e.Env.AllChainSelectors() + chain1, chain2 := selectors[0], selectors[1] + + _, err = AddLanesWithTestRouter(e.Env, AddLanesConfig{ + LaneConfigs: []LaneConfig{ + { + SourceSelector: chain1, + DestSelector: chain2, + InitialPricesBySource: DefaultInitialPrices, + FeeQuoterDestChain: DefaultFeeQuoterDestChainConfig(), + }, + }, + }) + require.NoError(t, err) + // Need to keep track of the block number for each chain so that event subscription can be done from that block. + startBlocks := make(map[uint64]*uint64) + // Send a message from each chain to every other chain. + expectedSeqNumExec := make(map[SourceDestPair][]uint64) + latesthdr, err := e.Env.Chains[chain2].Client.HeaderByNumber(testcontext.Get(t), nil) + require.NoError(t, err) + block := latesthdr.Number.Uint64() + startBlocks[chain2] = &block + msgSentEvent := TestSendRequest(t, e.Env, state, chain1, chain2, true, router.ClientEVM2AnyMessage{ + Receiver: common.LeftPadBytes(state.Chains[chain2].Receiver.Address().Bytes(), 32), + Data: []byte("hello"), + TokenAmounts: nil, + FeeToken: common.HexToAddress("0x0"), + ExtraArgs: nil, + }) + expectedSeqNumExec[SourceDestPair{ + SourceChainSelector: chain1, + DestChainSelector: chain2, + }] = []uint64{msgSentEvent.SequenceNumber} + ConfirmExecWithSeqNrsForAll(t, e.Env, state, expectedSeqNumExec, startBlocks) +} + // TestAddLane covers the workflow of adding a lane between two chains and enabling it. // It also covers the case where the onRamp is disabled on the OffRamp contract initially and then enabled. func TestAddLane(t *testing.T) { @@ -41,7 +83,7 @@ func TestAddLane(t *testing.T) { require.NoError(t, err) // Add one lane from chain1 to chain 2 and send traffic. - require.NoError(t, AddLaneWithDefaultPrices(e.Env, state, chain1, chain2)) + require.NoError(t, AddLaneWithDefaultPricesAndFeeQuoterConfig(e.Env, state, chain1, chain2, false)) ReplayLogs(t, e.Env.Offchain, replayBlocks) time.Sleep(30 * time.Second) @@ -87,7 +129,7 @@ func TestAddLane(t *testing.T) { require.Equal(t, uint64(1), msgSentEvent1.SequenceNumber) // Add another lane - require.NoError(t, AddLaneWithDefaultPrices(e.Env, state, chain2, chain1)) + require.NoError(t, AddLaneWithDefaultPricesAndFeeQuoterConfig(e.Env, state, chain2, chain1, false)) // Send traffic on the second lane and it should succeed latesthdr, err = e.Env.Chains[chain1].Client.HeaderByNumber(testcontext.Get(t), nil) diff --git a/deployment/ccip/changeset/deploy.go b/deployment/ccip/changeset/deploy.go index a3736075c48..a53dd7f3f2c 100644 --- a/deployment/ccip/changeset/deploy.go +++ b/deployment/ccip/changeset/deploy.go @@ -7,10 +7,9 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" "golang.org/x/sync/errgroup" - "github.com/smartcontractkit/chainlink-ccip/pluginconfig" - "github.com/smartcontractkit/chainlink/deployment/ccip/changeset/internal" "github.com/smartcontractkit/chainlink/deployment" @@ -378,10 +377,15 @@ func configureChain( if !ok { return fmt.Errorf("chain state not found for chain %d", chain.Selector) } + ocrParams, ok := c.OCRParams[chain.Selector] + if !ok { + return fmt.Errorf("OCR params not found for chain %d", chain.Selector) + } if chainState.OffRamp == nil { return fmt.Errorf("off ramp not found for chain %d", chain.Selector) } - tokenInfo := c.TokenConfig.GetTokenInfo(e.Logger, existingState.Chains[chainSel].LinkToken, existingState.Chains[chainSel].Weth9) + // TODO : better handling - need to scale this for more tokens + ocrParams.CommitOffChainConfig.TokenInfo = c.TokenConfig.GetTokenInfo(e.Logger, existingState.Chains[chainSel].LinkToken, existingState.Chains[chainSel].Weth9) _, err = AddChainConfig( e.Logger, e.Chains[c.HomeChainSel], @@ -391,33 +395,22 @@ func configureChain( if err != nil { return err } - var tokenDataObserversConf []pluginconfig.TokenDataObserverConfig if enabled, ok := c.USDCConfig.EnabledChainMap()[chainSel]; ok && enabled { - tokenDataObserversConf = []pluginconfig.TokenDataObserverConfig{{ - Type: pluginconfig.USDCCCTPHandlerType, - Version: "1.0", - USDCCCTPObserverConfig: &pluginconfig.USDCCCTPObserverConfig{ - Tokens: c.USDCConfig.CCTPTokenConfig, - AttestationAPI: c.USDCConfig.API, - AttestationAPITimeout: c.USDCConfig.APITimeout, - AttestationAPIInterval: c.USDCConfig.APIInterval, - }, - }} + ocrParams.ExecuteOffChainConfig.TokenDataObservers = append(ocrParams.ExecuteOffChainConfig.TokenDataObservers, c.USDCConfig.ToTokenDataObserverConfig()...) } + ocrParams.CommitOffChainConfig.PriceFeedChainSelector = cciptypes.ChainSelector(c.FeedChainSel) // For each chain, we create a DON on the home chain (2 OCR instances) - if err := AddDON( + if err := addDON( e.Logger, c.OCRSecrets, capReg, ccipHome, rmnHome.Address(), chainState.OffRamp, - c.FeedChainSel, - tokenInfo, chain, e.Chains[c.HomeChainSel], nodes.NonBootstraps(), - tokenDataObserversConf, + ocrParams, ); err != nil { e.Logger.Errorw("Failed to add DON", "err", err) return err diff --git a/deployment/ccip/changeset/deploy_chain.go b/deployment/ccip/changeset/deploy_chain.go index 4c37603c64d..d0f44724070 100644 --- a/deployment/ccip/changeset/deploy_chain.go +++ b/deployment/ccip/changeset/deploy_chain.go @@ -16,6 +16,9 @@ var _ deployment.ChangeSet[DeployChainContractsConfig] = DeployChainContracts // changeset again with the same input to retry the failed deployment. // Caller should update the environment's address book with the returned addresses. func DeployChainContracts(env deployment.Environment, c DeployChainContractsConfig) (deployment.ChangesetOutput, error) { + if err := c.Validate(); err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("invalid DeployChainContractsConfig: %w", err) + } newAddresses := deployment.NewMemoryAddressBook() err := deployChainContractsForChains(env, newAddresses, c.HomeChainSelector, c.ChainSelectors) if err != nil { diff --git a/deployment/ccip/changeset/deploy_home_chain.go b/deployment/ccip/changeset/deploy_home_chain.go index 446328c0530..881f7c386c3 100644 --- a/deployment/ccip/changeset/deploy_home_chain.go +++ b/deployment/ccip/changeset/deploy_home_chain.go @@ -16,11 +16,10 @@ import ( "github.com/smartcontractkit/chainlink-ccip/chainconfig" "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" - "github.com/smartcontractkit/chainlink-ccip/pluginconfig" "github.com/smartcontractkit/chainlink-common/pkg/logger" - "github.com/smartcontractkit/chainlink/deployment/ccip/changeset/internal" "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/ccip/changeset/internal" cctypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/ccip_home" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/offramp" @@ -437,23 +436,20 @@ func ValidateCCIPHomeConfigSetUp( return nil } -func AddDON( +func addDON( lggr logger.Logger, ocrSecrets deployment.OCRSecrets, capReg *capabilities_registry.CapabilitiesRegistry, ccipHome *ccip_home.CCIPHome, rmnHomeAddress common.Address, offRamp *offramp.OffRamp, - feedChainSel uint64, - // Token address on Dest chain to aggregate address on feed chain - tokenInfo map[ccipocr3.UnknownEncodedAddress]pluginconfig.TokenInfo, dest deployment.Chain, home deployment.Chain, nodes deployment.Nodes, - tokenConfigs []pluginconfig.TokenDataObserverConfig, + ocrParams CCIPOCRParams, ) error { ocrConfigs, err := internal.BuildOCR3ConfigForCCIPHome( - ocrSecrets, offRamp, dest, feedChainSel, tokenInfo, nodes, rmnHomeAddress, tokenConfigs) + ocrSecrets, offRamp, dest, nodes, rmnHomeAddress, ocrParams.OCRParameters, ocrParams.CommitOffChainConfig, ocrParams.ExecuteOffChainConfig) if err != nil { return err } diff --git a/deployment/ccip/changeset/initial_add_chain.go b/deployment/ccip/changeset/initial_add_chain.go index 0e8da566626..338e3ea76e8 100644 --- a/deployment/ccip/changeset/initial_add_chain.go +++ b/deployment/ccip/changeset/initial_add_chain.go @@ -2,13 +2,20 @@ package changeset import ( "fmt" + "os" + "slices" + "sort" + "time" "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" "github.com/smartcontractkit/chainlink-ccip/pluginconfig" "github.com/smartcontractkit/chainlink-common/pkg/config" + "github.com/smartcontractkit/chainlink-common/pkg/merklemulti" "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/ccip/changeset/internal" + "github.com/smartcontractkit/chainlink/deployment/common/types" ) var _ deployment.ChangeSet[NewChainsConfig] = ConfigureNewChains @@ -48,12 +55,44 @@ func (cfg USDCConfig) EnabledChainMap() map[uint64]bool { return m } +func (cfg USDCConfig) ToTokenDataObserverConfig() []pluginconfig.TokenDataObserverConfig { + return []pluginconfig.TokenDataObserverConfig{{ + Type: pluginconfig.USDCCCTPHandlerType, + Version: "1.0", + USDCCCTPObserverConfig: &pluginconfig.USDCCCTPObserverConfig{ + Tokens: cfg.CCTPTokenConfig, + AttestationAPI: cfg.API, + AttestationAPITimeout: cfg.APITimeout, + AttestationAPIInterval: cfg.APIInterval, + }, + }} +} + type USDCAttestationConfig struct { API string APITimeout *config.Duration APIInterval *config.Duration } +type CCIPOCRParams struct { + OCRParameters types.OCRParameters + CommitOffChainConfig pluginconfig.CommitOffchainConfig + ExecuteOffChainConfig pluginconfig.ExecuteOffchainConfig +} + +func (p CCIPOCRParams) Validate() error { + if err := p.OCRParameters.Validate(); err != nil { + return fmt.Errorf("invalid OCR parameters: %w", err) + } + if err := p.CommitOffChainConfig.Validate(); err != nil { + return fmt.Errorf("invalid commit off-chain config: %w", err) + } + if err := p.ExecuteOffChainConfig.Validate(); err != nil { + return fmt.Errorf("invalid execute off-chain config: %w", err) + } + return nil +} + type NewChainsConfig struct { HomeChainSel uint64 FeedChainSel uint64 @@ -62,6 +101,7 @@ type NewChainsConfig struct { USDCConfig USDCConfig // For setting OCR configuration OCRSecrets deployment.OCRSecrets + OCRParams map[uint64]CCIPOCRParams } func (c NewChainsConfig) Validate() error { @@ -106,5 +146,65 @@ func (c NewChainsConfig) Validate() error { return fmt.Errorf("invalid chain selector: %d - %w", chain, err) } } + // Validate OCR params + var ocrChains []uint64 + for chain, ocrParams := range c.OCRParams { + ocrChains = append(ocrChains, chain) + if _, exists := mapChainsToDeploy[chain]; !exists { + return fmt.Errorf("chain %d is not in chains to deploy", chain) + } + if err := ocrParams.Validate(); err != nil { + return fmt.Errorf("invalid OCR params for chain %d: %w", chain, err) + } + } + sort.Slice(ocrChains, func(i, j int) bool { return ocrChains[i] < ocrChains[j] }) + sort.Slice(c.ChainsToDeploy, func(i, j int) bool { return c.ChainsToDeploy[i] < c.ChainsToDeploy[j] }) + if !slices.Equal(ocrChains, c.ChainsToDeploy) { + return fmt.Errorf("mismatch in given OCR params and chains to deploy") + } return nil } + +func DefaultOCRParams( + feedChainSel uint64, + tokenInfo map[ccipocr3.UnknownEncodedAddress]pluginconfig.TokenInfo, + dataObserverConfig []pluginconfig.TokenDataObserverConfig, +) CCIPOCRParams { + return CCIPOCRParams{ + OCRParameters: types.OCRParameters{ + DeltaProgress: internal.DeltaProgress, + DeltaResend: internal.DeltaResend, + DeltaInitial: internal.DeltaInitial, + DeltaRound: internal.DeltaRound, + DeltaGrace: internal.DeltaGrace, + DeltaCertifiedCommitRequest: internal.DeltaCertifiedCommitRequest, + DeltaStage: internal.DeltaStage, + Rmax: internal.Rmax, + MaxDurationQuery: internal.MaxDurationQuery, + MaxDurationObservation: internal.MaxDurationObservation, + MaxDurationShouldAcceptAttestedReport: internal.MaxDurationShouldAcceptAttestedReport, + MaxDurationShouldTransmitAcceptedReport: internal.MaxDurationShouldTransmitAcceptedReport, + }, + ExecuteOffChainConfig: pluginconfig.ExecuteOffchainConfig{ + BatchGasLimit: internal.BatchGasLimit, + RelativeBoostPerWaitHour: internal.RelativeBoostPerWaitHour, + InflightCacheExpiry: *config.MustNewDuration(internal.InflightCacheExpiry), + RootSnoozeTime: *config.MustNewDuration(internal.RootSnoozeTime), + MessageVisibilityInterval: *config.MustNewDuration(internal.FirstBlockAge), + BatchingStrategyID: internal.BatchingStrategyID, + TokenDataObservers: dataObserverConfig, + }, + CommitOffChainConfig: pluginconfig.CommitOffchainConfig{ + RemoteGasPriceBatchWriteFrequency: *config.MustNewDuration(internal.RemoteGasPriceBatchWriteFrequency), + TokenPriceBatchWriteFrequency: *config.MustNewDuration(internal.TokenPriceBatchWriteFrequency), + TokenInfo: tokenInfo, + PriceFeedChainSelector: ccipocr3.ChainSelector(feedChainSel), + NewMsgScanBatchSize: merklemulti.MaxNumberTreeLeaves, + MaxReportTransmissionCheckAttempts: 5, + RMNEnabled: os.Getenv("ENABLE_RMN") == "true", // only enabled in manual test + RMNSignaturesTimeout: 30 * time.Minute, + MaxMerkleTreeSize: merklemulti.MaxNumberTreeLeaves, + SignObservationPrefix: "chainlink ccip 1.6 rmn observation", + }, + } +} diff --git a/deployment/ccip/changeset/internal/deploy_home_chain.go b/deployment/ccip/changeset/internal/deploy_home_chain.go index 7b4e6e9a27b..4fc0a9d1d60 100644 --- a/deployment/ccip/changeset/internal/deploy_home_chain.go +++ b/deployment/ccip/changeset/internal/deploy_home_chain.go @@ -3,7 +3,6 @@ package internal import ( "context" "fmt" - "os" "time" "github.com/ethereum/go-ethereum/accounts/abi" @@ -12,11 +11,10 @@ import ( "github.com/smartcontractkit/libocr/offchainreporting2plus/confighelper" "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3confighelper" - "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" "github.com/smartcontractkit/chainlink-ccip/pluginconfig" - "github.com/smartcontractkit/chainlink-common/pkg/config" - "github.com/smartcontractkit/chainlink-common/pkg/merklemulti" + "github.com/smartcontractkit/chainlink/deployment" + types2 "github.com/smartcontractkit/chainlink/deployment/common/types" "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/ccip_home" @@ -30,7 +28,7 @@ const ( FirstBlockAge = 8 * time.Hour RemoteGasPriceBatchWriteFrequency = 30 * time.Minute - TokenPriceBatchWriteFrequency = 30 * time.Minute + TokenPriceBatchWriteFrequency = 3 * time.Second BatchGasLimit = 6_500_000 RelativeBoostPerWaitHour = 1.5 InflightCacheExpiry = 10 * time.Minute @@ -412,11 +410,11 @@ func BuildOCR3ConfigForCCIPHome( ocrSecrets deployment.OCRSecrets, offRamp *offramp.OffRamp, dest deployment.Chain, - feedChainSel uint64, - tokenInfo map[ccipocr3.UnknownEncodedAddress]pluginconfig.TokenInfo, nodes deployment.Nodes, rmnHomeAddress common.Address, - configs []pluginconfig.TokenDataObserverConfig, + ocrParams types2.OCRParameters, + commitOffchainCfg pluginconfig.CommitOffchainConfig, + execOffchainCfg pluginconfig.ExecuteOffchainConfig, ) (map[types.PluginType]ccip_home.CCIPHomeOCR3Config, error) { p2pIDs := nodes.PeerIDs() // Get OCR3 Config from helper @@ -445,25 +443,26 @@ func BuildOCR3ConfigForCCIPHome( var err2 error if pluginType == types.PluginTypeCCIPCommit { encodedOffchainConfig, err2 = pluginconfig.EncodeCommitOffchainConfig(pluginconfig.CommitOffchainConfig{ - RemoteGasPriceBatchWriteFrequency: *config.MustNewDuration(RemoteGasPriceBatchWriteFrequency), - TokenPriceBatchWriteFrequency: *config.MustNewDuration(TokenPriceBatchWriteFrequency), - PriceFeedChainSelector: ccipocr3.ChainSelector(feedChainSel), - TokenInfo: tokenInfo, - NewMsgScanBatchSize: merklemulti.MaxNumberTreeLeaves, - MaxReportTransmissionCheckAttempts: 5, - MaxMerkleTreeSize: merklemulti.MaxNumberTreeLeaves, - SignObservationPrefix: "chainlink ccip 1.6 rmn observation", - RMNEnabled: os.Getenv("ENABLE_RMN") == "true", // only enabled in manual test + RemoteGasPriceBatchWriteFrequency: commitOffchainCfg.RemoteGasPriceBatchWriteFrequency, + TokenPriceBatchWriteFrequency: commitOffchainCfg.TokenPriceBatchWriteFrequency, + PriceFeedChainSelector: commitOffchainCfg.PriceFeedChainSelector, + TokenInfo: commitOffchainCfg.TokenInfo, + NewMsgScanBatchSize: commitOffchainCfg.NewMsgScanBatchSize, + MaxReportTransmissionCheckAttempts: commitOffchainCfg.MaxReportTransmissionCheckAttempts, + MaxMerkleTreeSize: commitOffchainCfg.MaxMerkleTreeSize, + SignObservationPrefix: commitOffchainCfg.SignObservationPrefix, + RMNEnabled: commitOffchainCfg.RMNEnabled, + RMNSignaturesTimeout: commitOffchainCfg.RMNSignaturesTimeout, }) } else { encodedOffchainConfig, err2 = pluginconfig.EncodeExecuteOffchainConfig(pluginconfig.ExecuteOffchainConfig{ - BatchGasLimit: BatchGasLimit, - RelativeBoostPerWaitHour: RelativeBoostPerWaitHour, - MessageVisibilityInterval: *config.MustNewDuration(FirstBlockAge), - InflightCacheExpiry: *config.MustNewDuration(InflightCacheExpiry), - RootSnoozeTime: *config.MustNewDuration(RootSnoozeTime), - BatchingStrategyID: BatchingStrategyID, - TokenDataObservers: configs, + BatchGasLimit: execOffchainCfg.BatchGasLimit, + RelativeBoostPerWaitHour: execOffchainCfg.RelativeBoostPerWaitHour, + MessageVisibilityInterval: execOffchainCfg.MessageVisibilityInterval, + InflightCacheExpiry: execOffchainCfg.InflightCacheExpiry, + RootSnoozeTime: execOffchainCfg.RootSnoozeTime, + BatchingStrategyID: execOffchainCfg.BatchingStrategyID, + TokenDataObservers: execOffchainCfg.TokenDataObservers, }) } if err2 != nil { @@ -472,22 +471,22 @@ func BuildOCR3ConfigForCCIPHome( signers, transmitters, configF, _, offchainConfigVersion, offchainConfig, err2 := ocr3confighelper.ContractSetConfigArgsDeterministic( ocrSecrets.EphemeralSk, ocrSecrets.SharedSecret, - DeltaProgress, - DeltaResend, - DeltaInitial, - DeltaRound, - DeltaGrace, - DeltaCertifiedCommitRequest, - DeltaStage, - Rmax, + ocrParams.DeltaProgress, + ocrParams.DeltaResend, + ocrParams.DeltaInitial, + ocrParams.DeltaRound, + ocrParams.DeltaGrace, + ocrParams.DeltaCertifiedCommitRequest, + ocrParams.DeltaStage, + ocrParams.Rmax, schedule, oracles, encodedOffchainConfig, nil, // maxDurationInitialization - MaxDurationQuery, - MaxDurationObservation, - MaxDurationShouldAcceptAttestedReport, - MaxDurationShouldTransmitAcceptedReport, + ocrParams.MaxDurationQuery, + ocrParams.MaxDurationObservation, + ocrParams.MaxDurationShouldAcceptAttestedReport, + ocrParams.MaxDurationShouldTransmitAcceptedReport, int(nodes.DefaultF()), []byte{}, // empty OnChainConfig ) diff --git a/deployment/ccip/changeset/test_helpers.go b/deployment/ccip/changeset/test_helpers.go index b1e9a328c9d..5e8705d4757 100644 --- a/deployment/ccip/changeset/test_helpers.go +++ b/deployment/ccip/changeset/test_helpers.go @@ -295,6 +295,7 @@ func NewMemoryEnvironmentWithJobsAndContracts(t *testing.T, lggr logger.Logger, state, err := LoadOnchainState(e.Env) require.NoError(t, err) tokenConfig := NewTestTokenConfig(state.Chains[e.FeedChainSel].USDFeeds) + ocrParams := make(map[uint64]CCIPOCRParams) usdcCCTPConfig := make(map[cciptypes.ChainSelector]pluginconfig.USDCCCTPTokenConfig) timelocksPerChain := make(map[uint64]*gethwrappers.RBACTimelock) for _, chain := range usdcChains { @@ -305,7 +306,10 @@ func NewMemoryEnvironmentWithJobsAndContracts(t *testing.T, lggr logger.Logger, SourcePoolAddress: state.Chains[chain].USDCTokenPool.Address().String(), SourceMessageTransmitterAddr: state.Chains[chain].MockUSDCTransmitter.Address().String(), } + } + for _, chain := range allChains { timelocksPerChain[chain] = state.Chains[chain].Timelock + ocrParams[chain] = DefaultOCRParams(e.FeedChainSel, nil, nil) } var usdcCfg USDCAttestationConfig if len(usdcChains) > 0 { @@ -343,6 +347,7 @@ func NewMemoryEnvironmentWithJobsAndContracts(t *testing.T, lggr logger.Logger, USDCAttestationConfig: usdcCfg, CCTPTokenConfig: usdcCCTPConfig, }, + OCRParams: ocrParams, }, }, { @@ -500,7 +505,7 @@ func AddLanesForAll(e deployment.Environment, state CCIPOnChainState) error { for source := range e.Chains { for dest := range e.Chains { if source != dest { - err := AddLaneWithDefaultPrices(e, state, source, dest) + err := AddLaneWithDefaultPricesAndFeeQuoterConfig(e, state, source, dest, false) if err != nil { return err } diff --git a/deployment/common/types/types.go b/deployment/common/types/types.go index 0efb226d73b..7fb602c3704 100644 --- a/deployment/common/types/types.go +++ b/deployment/common/types/types.go @@ -1,7 +1,9 @@ package types import ( + "fmt" "math/big" + "time" "github.com/ethereum/go-ethereum/common" "github.com/smartcontractkit/ccip-owner-contracts/pkg/config" @@ -23,3 +25,58 @@ type MCMSWithTimelockConfig struct { TimelockExecutors []common.Address TimelockMinDelay *big.Int } + +type OCRParameters struct { + DeltaProgress time.Duration + DeltaResend time.Duration + DeltaInitial time.Duration + DeltaRound time.Duration + DeltaGrace time.Duration + DeltaCertifiedCommitRequest time.Duration + DeltaStage time.Duration + Rmax uint64 + MaxDurationQuery time.Duration + MaxDurationObservation time.Duration + MaxDurationShouldAcceptAttestedReport time.Duration + MaxDurationShouldTransmitAcceptedReport time.Duration +} + +func (params OCRParameters) Validate() error { + if params.DeltaProgress <= 0 { + return fmt.Errorf("deltaProgress must be positive") + } + if params.DeltaResend <= 0 { + return fmt.Errorf("deltaResend must be positive") + } + if params.DeltaInitial <= 0 { + return fmt.Errorf("deltaInitial must be positive") + } + if params.DeltaRound <= 0 { + return fmt.Errorf("deltaRound must be positive") + } + if params.DeltaGrace <= 0 { + return fmt.Errorf("deltaGrace must be positive") + } + if params.DeltaCertifiedCommitRequest <= 0 { + return fmt.Errorf("deltaCertifiedCommitRequest must be positive") + } + if params.DeltaStage <= 0 { + return fmt.Errorf("deltaStage must be positive") + } + if params.Rmax <= 0 { + return fmt.Errorf("rmax must be positive") + } + if params.MaxDurationQuery <= 0 { + return fmt.Errorf("maxDurationQuery must be positive") + } + if params.MaxDurationObservation <= 0 { + return fmt.Errorf("maxDurationObservation must be positive") + } + if params.MaxDurationShouldAcceptAttestedReport <= 0 { + return fmt.Errorf("maxDurationShouldAcceptAttestedReport must be positive") + } + if params.MaxDurationShouldTransmitAcceptedReport <= 0 { + return fmt.Errorf("maxDurationShouldTransmitAcceptedReport must be positive") + } + return nil +} diff --git a/integration-tests/smoke/ccip/ccip_batching_test.go b/integration-tests/smoke/ccip/ccip_batching_test.go index d85924c1739..0667d1367cc 100644 --- a/integration-tests/smoke/ccip/ccip_batching_test.go +++ b/integration-tests/smoke/ccip/ccip_batching_test.go @@ -13,6 +13,7 @@ import ( "golang.org/x/exp/maps" "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" + "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" "github.com/smartcontractkit/chainlink/integration-tests/testsetups" @@ -51,8 +52,8 @@ func Test_CCIPBatching(t *testing.T) { ) // connect sourceChain1 and sourceChain2 to destChain - require.NoError(t, changeset.AddLaneWithDefaultPrices(e.Env, state, sourceChain1, destChain)) - require.NoError(t, changeset.AddLaneWithDefaultPrices(e.Env, state, sourceChain2, destChain)) + require.NoError(t, changeset.AddLaneWithDefaultPricesAndFeeQuoterConfig(e.Env, state, sourceChain1, destChain, false)) + require.NoError(t, changeset.AddLaneWithDefaultPricesAndFeeQuoterConfig(e.Env, state, sourceChain2, destChain, false)) const ( numMessages = 5 diff --git a/integration-tests/smoke/ccip/ccip_messaging_test.go b/integration-tests/smoke/ccip/ccip_messaging_test.go index 1eb16838c92..35066ecbaf7 100644 --- a/integration-tests/smoke/ccip/ccip_messaging_test.go +++ b/integration-tests/smoke/ccip/ccip_messaging_test.go @@ -65,7 +65,7 @@ func Test_CCIPMessaging(t *testing.T) { ", dest chain selector:", destChain, ) // connect a single lane, source to dest - require.NoError(t, changeset.AddLaneWithDefaultPrices(e.Env, state, sourceChain, destChain)) + require.NoError(t, changeset.AddLaneWithDefaultPricesAndFeeQuoterConfig(e.Env, state, sourceChain, destChain, false)) var ( replayed bool diff --git a/integration-tests/smoke/ccip/fee_boosting_test.go b/integration-tests/smoke/ccip/fee_boosting_test.go index 1f9b1f9083a..4d331c20b7d 100644 --- a/integration-tests/smoke/ccip/fee_boosting_test.go +++ b/integration-tests/smoke/ccip/fee_boosting_test.go @@ -88,7 +88,7 @@ func Test_CCIPFeeBoosting(t *testing.T) { } func runFeeboostTestCase(tc feeboostTestCase) { - require.NoError(tc.t, changeset.AddLane(tc.deployedEnv.Env, tc.onchainState, tc.sourceChain, tc.destChain, tc.initialPrices)) + require.NoError(tc.t, changeset.AddLaneWithDefaultPricesAndFeeQuoterConfig(tc.deployedEnv.Env, tc.onchainState, tc.sourceChain, tc.destChain, false)) startBlocks := make(map[uint64]*uint64) expectedSeqNum := make(map[changeset.SourceDestPair]uint64) diff --git a/integration-tests/testsetups/test_helpers.go b/integration-tests/testsetups/test_helpers.go index f11b343f155..f33dacb3851 100644 --- a/integration-tests/testsetups/test_helpers.go +++ b/integration-tests/testsetups/test_helpers.go @@ -195,6 +195,7 @@ func NewLocalDevEnvironment( tokenConfig := changeset.NewTestTokenConfig(state.Chains[feedSel].USDFeeds) usdcCCTPConfig := make(map[cciptypes.ChainSelector]pluginconfig.USDCCCTPTokenConfig) timelocksPerChain := make(map[uint64]*gethwrappers.RBACTimelock) + ocrParams := make(map[uint64]changeset.CCIPOCRParams) for _, chain := range usdcChains { require.NotNil(t, state.Chains[chain].MockUSDCTokenMessenger) require.NotNil(t, state.Chains[chain].MockUSDCTransmitter) @@ -203,7 +204,6 @@ func NewLocalDevEnvironment( SourcePoolAddress: state.Chains[chain].USDCTokenPool.Address().String(), SourceMessageTransmitterAddr: state.Chains[chain].MockUSDCTransmitter.Address().String(), } - timelocksPerChain[chain] = state.Chains[chain].Timelock } var usdcAttestationCfg changeset.USDCAttestationConfig if len(usdcChains) > 0 { @@ -217,7 +217,10 @@ func NewLocalDevEnvironment( APIInterval: commonconfig.MustNewDuration(500 * time.Millisecond), } } - + for _, chain := range allChains { + timelocksPerChain[chain] = state.Chains[chain].Timelock + ocrParams[chain] = changeset.DefaultOCRParams(feedSel, nil, nil) + } // Deploy second set of changesets to deploy and configure the CCIP contracts. env, err = commonchangeset.ApplyChangesets(t, env, timelocksPerChain, []commonchangeset.ChangesetApplication{ { @@ -228,6 +231,7 @@ func NewLocalDevEnvironment( ChainsToDeploy: allChains, TokenConfig: tokenConfig, OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), + OCRParams: ocrParams, USDCConfig: changeset.USDCConfig{ EnabledChains: usdcChains, USDCAttestationConfig: usdcAttestationCfg,