From 63bca128a529dec7705e68d8c5a46f07cecef479 Mon Sep 17 00:00:00 2001 From: Makram Date: Thu, 5 Dec 2024 18:41:48 +0200 Subject: [PATCH] deployment/ccip/changeset: conform to ChangeSet iface (#15456) * deployment/ccip/changeset: conform to ChangeSet iface * port over some more changesets * compose more changesets * remove skips * Revert "remove skips" This reverts commit f97cf7f34b466f2bc8787f0775b6a3c297ca70e7. * address comments * fix --- .../ccip/changeset/cs_active_candidate.go | 138 ++++++++++----- deployment/ccip/changeset/cs_add_chain.go | 159 ++++++++++++++---- .../ccip/changeset/cs_add_chain_test.go | 77 ++++++--- 3 files changed, 277 insertions(+), 97 deletions(-) diff --git a/deployment/ccip/changeset/cs_active_candidate.go b/deployment/ccip/changeset/cs_active_candidate.go index 29516b36736..b2aad3889ec 100644 --- a/deployment/ccip/changeset/cs_active_candidate.go +++ b/deployment/ccip/changeset/cs_active_candidate.go @@ -17,18 +17,76 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" ) +var ( + _ deployment.ChangeSet[PromoteAllCandidatesChangesetConfig] = PromoteAllCandidatesChangeset + _ deployment.ChangeSet[AddDonAndSetCandidateChangesetConfig] = SetCandidatePluginChangeset +) + +type PromoteAllCandidatesChangesetConfig struct { + HomeChainSelector uint64 + NewChainSelector uint64 + NodeIDs []string +} + +func (p PromoteAllCandidatesChangesetConfig) Validate(e deployment.Environment, state CCIPOnChainState) (deployment.Nodes, error) { + if p.HomeChainSelector == 0 { + return nil, fmt.Errorf("HomeChainSelector must be set") + } + if p.NewChainSelector == 0 { + return nil, fmt.Errorf("NewChainSelector must be set") + } + if len(p.NodeIDs) == 0 { + return nil, fmt.Errorf("NodeIDs must be set") + } + + nodes, err := deployment.NodeInfo(p.NodeIDs, e.Offchain) + if err != nil { + return nil, fmt.Errorf("fetch node info: %w", err) + } + + donID, err := internal.DonIDForChain( + state.Chains[p.HomeChainSelector].CapabilityRegistry, + state.Chains[p.HomeChainSelector].CCIPHome, + p.NewChainSelector, + ) + if err != nil { + return nil, fmt.Errorf("fetch don id for chain: %w", err) + } + + // check if the DON ID has a candidate digest set that we can promote + for _, pluginType := range []cctypes.PluginType{cctypes.PluginTypeCCIPCommit, cctypes.PluginTypeCCIPExec} { + candidateDigest, err := state.Chains[p.HomeChainSelector].CCIPHome.GetCandidateDigest(nil, donID, uint8(pluginType)) + if err != nil { + return nil, fmt.Errorf("error fetching candidate digest for pluginType(%s): %w", pluginType.String(), err) + } + if candidateDigest == [32]byte{} { + return nil, fmt.Errorf("candidate digest is zero, must be non-zero to promote") + } + } + + return nodes, nil +} + // PromoteAllCandidatesChangeset generates a proposal to call promoteCandidate on the CCIPHome through CapReg. // This needs to be called after SetCandidateProposal is executed. -// TODO: make it conform to the ChangeSet interface. func PromoteAllCandidatesChangeset( - state CCIPOnChainState, - homeChainSel, newChainSel uint64, - nodes deployment.Nodes, + e deployment.Environment, + cfg PromoteAllCandidatesChangesetConfig, ) (deployment.ChangesetOutput, error) { + state, err := LoadOnchainState(e) + if err != nil { + return deployment.ChangesetOutput{}, err + } + + nodes, err := cfg.Validate(e, state) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("%w: %w", deployment.ErrInvalidConfig, err) + } + promoteCandidateOps, err := promoteAllCandidatesForChainOps( - state.Chains[homeChainSel].CapabilityRegistry, - state.Chains[homeChainSel].CCIPHome, - newChainSel, + state.Chains[cfg.HomeChainSelector].CapabilityRegistry, + state.Chains[cfg.HomeChainSelector].CCIPHome, + cfg.NewChainSelector, nodes.NonBootstraps(), ) if err != nil { @@ -37,17 +95,17 @@ func PromoteAllCandidatesChangeset( var ( timelocksPerChain = map[uint64]common.Address{ - homeChainSel: state.Chains[homeChainSel].Timelock.Address(), + cfg.HomeChainSelector: state.Chains[cfg.HomeChainSelector].Timelock.Address(), } proposerMCMSes = map[uint64]*gethwrappers.ManyChainMultiSig{ - homeChainSel: state.Chains[homeChainSel].ProposerMcm, + cfg.HomeChainSelector: state.Chains[cfg.HomeChainSelector].ProposerMcm, } ) prop, err := proposalutils.BuildProposalFromBatches( timelocksPerChain, proposerMCMSes, []timelock.BatchChainOperation{{ - ChainIdentifier: mcms.ChainIdentifier(homeChainSel), + ChainIdentifier: mcms.ChainIdentifier(cfg.HomeChainSelector), Batch: promoteCandidateOps, }}, "promoteCandidate for commit and execution", @@ -63,46 +121,45 @@ func PromoteAllCandidatesChangeset( }, nil } -// SetCandidateExecPluginProposal calls setCandidate on the CCIPHome for setting up OCR3 exec Plugin config for the new chain. -// TODO: make it conform to the ChangeSet interface. +// SetCandidatePluginChangeset calls setCandidate on the CCIPHome for setting up OCR3 exec Plugin config for the new chain. func SetCandidatePluginChangeset( - state CCIPOnChainState, e deployment.Environment, - nodes deployment.Nodes, - ocrSecrets deployment.OCRSecrets, - homeChainSel, feedChainSel, newChainSel uint64, - tokenConfig TokenConfig, - pluginType cctypes.PluginType, + cfg AddDonAndSetCandidateChangesetConfig, ) (deployment.ChangesetOutput, error) { - ccipOCRParams := DefaultOCRParams( - feedChainSel, - tokenConfig.GetTokenInfo(e.Logger, state.Chains[newChainSel].LinkToken, state.Chains[newChainSel].Weth9), - nil, - ) + state, err := LoadOnchainState(e) + if err != nil { + return deployment.ChangesetOutput{}, err + } + + nodes, err := cfg.Validate(e, state) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("%w: %w", deployment.ErrInvalidConfig, err) + } + newDONArgs, err := internal.BuildOCR3ConfigForCCIPHome( - ocrSecrets, - state.Chains[newChainSel].OffRamp, - e.Chains[newChainSel], + cfg.OCRSecrets, + state.Chains[cfg.NewChainSelector].OffRamp, + e.Chains[cfg.NewChainSelector], nodes.NonBootstraps(), - state.Chains[homeChainSel].RMNHome.Address(), - ccipOCRParams.OCRParameters, - ccipOCRParams.CommitOffChainConfig, - ccipOCRParams.ExecuteOffChainConfig, + state.Chains[cfg.HomeChainSelector].RMNHome.Address(), + cfg.CCIPOCRParams.OCRParameters, + cfg.CCIPOCRParams.CommitOffChainConfig, + cfg.CCIPOCRParams.ExecuteOffChainConfig, ) if err != nil { return deployment.ChangesetOutput{}, err } - execConfig, ok := newDONArgs[pluginType] + config, ok := newDONArgs[cfg.PluginType] if !ok { - return deployment.ChangesetOutput{}, fmt.Errorf("missing exec plugin in ocr3Configs") + return deployment.ChangesetOutput{}, fmt.Errorf("missing %s plugin in ocr3Configs", cfg.PluginType.String()) } setCandidateMCMSOps, err := setCandidateOnExistingDon( - execConfig, - state.Chains[homeChainSel].CapabilityRegistry, - state.Chains[homeChainSel].CCIPHome, - newChainSel, + config, + state.Chains[cfg.HomeChainSelector].CapabilityRegistry, + state.Chains[cfg.HomeChainSelector].CCIPHome, + cfg.NewChainSelector, nodes.NonBootstraps(), ) if err != nil { @@ -111,20 +168,20 @@ func SetCandidatePluginChangeset( var ( timelocksPerChain = map[uint64]common.Address{ - homeChainSel: state.Chains[homeChainSel].Timelock.Address(), + cfg.HomeChainSelector: state.Chains[cfg.HomeChainSelector].Timelock.Address(), } proposerMCMSes = map[uint64]*gethwrappers.ManyChainMultiSig{ - homeChainSel: state.Chains[homeChainSel].ProposerMcm, + cfg.HomeChainSelector: state.Chains[cfg.HomeChainSelector].ProposerMcm, } ) prop, err := proposalutils.BuildProposalFromBatches( timelocksPerChain, proposerMCMSes, []timelock.BatchChainOperation{{ - ChainIdentifier: mcms.ChainIdentifier(homeChainSel), + ChainIdentifier: mcms.ChainIdentifier(cfg.HomeChainSelector), Batch: setCandidateMCMSOps, }}, - "SetCandidate for execution", + fmt.Sprintf("SetCandidate for %s plugin", cfg.PluginType.String()), 0, // minDelay ) if err != nil { @@ -135,7 +192,6 @@ func SetCandidatePluginChangeset( *prop, }, }, nil - } // setCandidateOnExistingDon calls setCandidate on CCIPHome contract through the UpdateDON call on CapReg contract diff --git a/deployment/ccip/changeset/cs_add_chain.go b/deployment/ccip/changeset/cs_add_chain.go index 262d2e85e7e..9c19517260f 100644 --- a/deployment/ccip/changeset/cs_add_chain.go +++ b/deployment/ccip/changeset/cs_add_chain.go @@ -23,22 +23,47 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/onramp" ) +var _ deployment.ChangeSet[ChainInboundChangesetConfig] = NewChainInboundChangeset + +type ChainInboundChangesetConfig struct { + HomeChainSelector uint64 + NewChainSelector uint64 + SourceChainSelectors []uint64 +} + +func (c ChainInboundChangesetConfig) Validate() error { + if c.HomeChainSelector == 0 { + return fmt.Errorf("HomeChainSelector must be set") + } + if c.NewChainSelector == 0 { + return fmt.Errorf("NewChainSelector must be set") + } + if len(c.SourceChainSelectors) == 0 { + return fmt.Errorf("SourceChainSelectors must be set") + } + return nil +} + // NewChainInboundChangeset generates a proposal // to connect the new chain to the existing chains. -// TODO: doesn't implement the ChangeSet interface. func NewChainInboundChangeset( e deployment.Environment, - state CCIPOnChainState, - homeChainSel uint64, - newChainSel uint64, - sources []uint64, + cfg ChainInboundChangesetConfig, ) (deployment.ChangesetOutput, error) { + if err := cfg.Validate(); err != nil { + return deployment.ChangesetOutput{}, err + } + + state, err := LoadOnchainState(e) + if err != nil { + return deployment.ChangesetOutput{}, err + } // Generate proposal which enables new destination (from test router) on all source chains. var batches []timelock.BatchChainOperation - for _, source := range sources { + for _, source := range cfg.SourceChainSelectors { enableOnRampDest, err := state.Chains[source].OnRamp.ApplyDestChainConfigUpdates(deployment.SimTransactOpts(), []onramp.OnRampDestChainConfigArgs{ { - DestChainSelector: newChainSel, + DestChainSelector: cfg.NewChainSelector, Router: state.Chains[source].TestRouter.Address(), }, }) @@ -49,7 +74,7 @@ func NewChainInboundChangeset( deployment.SimTransactOpts(), []fee_quoter.FeeQuoterDestChainConfigArgs{ { - DestChainSelector: newChainSel, + DestChainSelector: cfg.NewChainSelector, DestChainConfig: DefaultFeeQuoterDestChainConfig(), }, }) @@ -74,13 +99,13 @@ func NewChainInboundChangeset( }) } - addChainOp, err := applyChainConfigUpdatesOp(e, state, homeChainSel, []uint64{newChainSel}) + addChainOp, err := applyChainConfigUpdatesOp(e, state, cfg.HomeChainSelector, []uint64{cfg.NewChainSelector}) if err != nil { return deployment.ChangesetOutput{}, err } batches = append(batches, timelock.BatchChainOperation{ - ChainIdentifier: mcms.ChainIdentifier(homeChainSel), + ChainIdentifier: mcms.ChainIdentifier(cfg.HomeChainSelector), Batch: []mcms.Operation{ addChainOp, }, @@ -90,7 +115,7 @@ func NewChainInboundChangeset( timelocksPerChain = make(map[uint64]common.Address) proposerMCMSes = make(map[uint64]*gethwrappers.ManyChainMultiSig) ) - for _, chain := range append(sources, homeChainSel) { + for _, chain := range append(cfg.SourceChainSelectors, cfg.HomeChainSelector) { timelocksPerChain[chain] = state.Chains[chain].Timelock.Address() proposerMCMSes[chain] = state.Chains[chain].ProposerMcm } @@ -110,48 +135,110 @@ func NewChainInboundChangeset( }, nil } +type AddDonAndSetCandidateChangesetConfig struct { + HomeChainSelector uint64 + FeedChainSelector uint64 + NewChainSelector uint64 + PluginType types.PluginType + NodeIDs []string + CCIPOCRParams CCIPOCRParams + OCRSecrets deployment.OCRSecrets +} + +func (a AddDonAndSetCandidateChangesetConfig) Validate(e deployment.Environment, state CCIPOnChainState) (deployment.Nodes, error) { + if a.HomeChainSelector == 0 { + return nil, fmt.Errorf("HomeChainSelector must be set") + } + if a.FeedChainSelector == 0 { + return nil, fmt.Errorf("FeedChainSelector must be set") + } + if a.NewChainSelector == 0 { + return nil, fmt.Errorf("ocr config chain selector must be set") + } + if a.PluginType != types.PluginTypeCCIPCommit && + a.PluginType != types.PluginTypeCCIPExec { + return nil, fmt.Errorf("PluginType must be set to either CCIPCommit or CCIPExec") + } + // TODO: validate token config + if len(a.NodeIDs) == 0 { + return nil, fmt.Errorf("nodeIDs must be set") + } + nodes, err := deployment.NodeInfo(a.NodeIDs, e.Offchain) + if err != nil { + return nil, fmt.Errorf("get node info: %w", err) + } + + // check that chain config is set up for the new chain + // TODO: feels like we should just have a getter for a particular chain, this pagination + // logic seems a bit out of place here. + allConfigs, err := state.Chains[a.HomeChainSelector].CCIPHome.GetAllChainConfigs(nil, big.NewInt(0), big.NewInt(100)) + if err != nil { + return nil, fmt.Errorf("get all chain configs: %w", err) + } + var found bool + for _, chainConfig := range allConfigs { + if chainConfig.ChainSelector == a.NewChainSelector { + found = true + break + } + } + if !found { + return nil, fmt.Errorf("chain config not set for chain %d", a.NewChainSelector) + } + + err = a.CCIPOCRParams.Validate() + if err != nil { + return nil, fmt.Errorf("invalid ccip ocr params: %w", err) + } + + if a.OCRSecrets.IsEmpty() { + return nil, fmt.Errorf("OCR secrets must be set") + } + + return nodes, nil +} + // AddDonAndSetCandidateChangeset adds new DON for destination to home chain // and sets the commit plugin config as candidateConfig for the don. func AddDonAndSetCandidateChangeset( - state CCIPOnChainState, e deployment.Environment, - nodes deployment.Nodes, - ocrSecrets deployment.OCRSecrets, - homeChainSel, feedChainSel, newChainSel uint64, - tokenConfig TokenConfig, - pluginType types.PluginType, + cfg AddDonAndSetCandidateChangesetConfig, ) (deployment.ChangesetOutput, error) { - ccipOCRParams := DefaultOCRParams( - feedChainSel, - tokenConfig.GetTokenInfo(e.Logger, state.Chains[newChainSel].LinkToken, state.Chains[newChainSel].Weth9), - // TODO: Need USDC support. - nil, - ) + state, err := LoadOnchainState(e) + if err != nil { + return deployment.ChangesetOutput{}, err + } + + nodes, err := cfg.Validate(e, state) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("%w: %w", deployment.ErrInvalidConfig, err) + } + newDONArgs, err := internal.BuildOCR3ConfigForCCIPHome( - ocrSecrets, - state.Chains[newChainSel].OffRamp, - e.Chains[newChainSel], + cfg.OCRSecrets, + state.Chains[cfg.NewChainSelector].OffRamp, + e.Chains[cfg.NewChainSelector], nodes.NonBootstraps(), - state.Chains[homeChainSel].RMNHome.Address(), - ccipOCRParams.OCRParameters, - ccipOCRParams.CommitOffChainConfig, - ccipOCRParams.ExecuteOffChainConfig, + state.Chains[cfg.HomeChainSelector].RMNHome.Address(), + cfg.CCIPOCRParams.OCRParameters, + cfg.CCIPOCRParams.CommitOffChainConfig, + cfg.CCIPOCRParams.ExecuteOffChainConfig, ) if err != nil { return deployment.ChangesetOutput{}, err } - latestDon, err := internal.LatestCCIPDON(state.Chains[homeChainSel].CapabilityRegistry) + latestDon, err := internal.LatestCCIPDON(state.Chains[cfg.HomeChainSelector].CapabilityRegistry) if err != nil { return deployment.ChangesetOutput{}, err } - commitConfig, ok := newDONArgs[pluginType] + commitConfig, ok := newDONArgs[cfg.PluginType] if !ok { return deployment.ChangesetOutput{}, fmt.Errorf("missing commit plugin in ocr3Configs") } donID := latestDon.Id + 1 addDonOp, err := newDonWithCandidateOp( donID, commitConfig, - state.Chains[homeChainSel].CapabilityRegistry, + state.Chains[cfg.HomeChainSelector].CapabilityRegistry, nodes.NonBootstraps(), ) if err != nil { @@ -160,17 +247,17 @@ func AddDonAndSetCandidateChangeset( var ( timelocksPerChain = map[uint64]common.Address{ - homeChainSel: state.Chains[homeChainSel].Timelock.Address(), + cfg.HomeChainSelector: state.Chains[cfg.HomeChainSelector].Timelock.Address(), } proposerMCMSes = map[uint64]*gethwrappers.ManyChainMultiSig{ - homeChainSel: state.Chains[homeChainSel].ProposerMcm, + cfg.HomeChainSelector: state.Chains[cfg.HomeChainSelector].ProposerMcm, } ) prop, err := proposalutils.BuildProposalFromBatches( timelocksPerChain, proposerMCMSes, []timelock.BatchChainOperation{{ - ChainIdentifier: mcms.ChainIdentifier(homeChainSel), + ChainIdentifier: mcms.ChainIdentifier(cfg.HomeChainSelector), Batch: []mcms.Operation{addDonOp}, }}, "setCandidate for commit and AddDon on new Chain", diff --git a/deployment/ccip/changeset/cs_add_chain_test.go b/deployment/ccip/changeset/cs_add_chain_test.go index b8a845ac27c..c83872c0dd7 100644 --- a/deployment/ccip/changeset/cs_add_chain_test.go +++ b/deployment/ccip/changeset/cs_add_chain_test.go @@ -151,11 +151,18 @@ func TestAddChainInbound(t *testing.T) { initialDeploy[1]: state.Chains[initialDeploy[1]].Timelock, initialDeploy[2]: state.Chains[initialDeploy[2]].Timelock, }, []commonchangeset.ChangesetApplication{ - // note this doesn't have proposals. { Changeset: commonchangeset.WrapChangeSet(commonchangeset.TransferToMCMSWithTimelock), Config: genTestTransferOwnershipConfig(e, initialDeploy, state), }, + { + Changeset: commonchangeset.WrapChangeSet(NewChainInboundChangeset), + Config: ChainInboundChangesetConfig{ + HomeChainSelector: e.HomeChainSel, + NewChainSelector: newChain, + SourceChainSelectors: initialDeploy, + }, + }, }) require.NoError(t, err) @@ -164,29 +171,59 @@ func TestAddChainInbound(t *testing.T) { nodes, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain) require.NoError(t, err) - // Generate and sign inbound proposal to new 4th chain. - chainInboundChangeset, err := NewChainInboundChangeset(e.Env, state, e.HomeChainSel, newChain, initialDeploy) - require.NoError(t, err) - ProcessChangeset(t, e.Env, chainInboundChangeset) - // TODO This currently is not working - Able to send the request here but request gets stuck in execution // Send a new message and expect that this is delivered once the chain is completely set up as inbound //TestSendRequest(t, e.Env, state, initialDeploy[0], newChain, true) + var nodeIDs []string + for _, node := range nodes { + nodeIDs = append(nodeIDs, node.NodeID) + } - t.Logf("Executing add don and set candidate proposal for commit plugin on chain %d", newChain) - addDonChangeset, err := AddDonAndSetCandidateChangeset(state, e.Env, nodes, deployment.XXXGenerateTestOCRSecrets(), e.HomeChainSel, e.FeedChainSel, newChain, tokenConfig, types.PluginTypeCCIPCommit) - require.NoError(t, err) - ProcessChangeset(t, e.Env, addDonChangeset) - - t.Logf("Executing promote candidate proposal for exec plugin on chain %d", newChain) - setCandidateForExecChangeset, err := SetCandidatePluginChangeset(state, e.Env, nodes, deployment.XXXGenerateTestOCRSecrets(), e.HomeChainSel, e.FeedChainSel, newChain, tokenConfig, types.PluginTypeCCIPExec) - require.NoError(t, err) - ProcessChangeset(t, e.Env, setCandidateForExecChangeset) - - t.Logf("Executing promote candidate proposal for both commit and exec plugins on chain %d", newChain) - donPromoteChangeset, err := PromoteAllCandidatesChangeset(state, e.HomeChainSel, newChain, nodes) - require.NoError(t, err) - ProcessChangeset(t, e.Env, donPromoteChangeset) + _, err = commonchangeset.ApplyChangesets(t, e.Env, map[uint64]*gethwrappers.RBACTimelock{ + e.HomeChainSel: state.Chains[e.HomeChainSel].Timelock, + newChain: state.Chains[newChain].Timelock, + }, []commonchangeset.ChangesetApplication{ + { + Changeset: commonchangeset.WrapChangeSet(AddDonAndSetCandidateChangeset), + Config: AddDonAndSetCandidateChangesetConfig{ + HomeChainSelector: e.HomeChainSel, + FeedChainSelector: e.FeedChainSel, + NewChainSelector: newChain, + PluginType: types.PluginTypeCCIPCommit, + NodeIDs: nodeIDs, + OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), + CCIPOCRParams: DefaultOCRParams( + e.FeedChainSel, + tokenConfig.GetTokenInfo(logger.TestLogger(t), state.Chains[newChain].LinkToken, state.Chains[newChain].Weth9), + nil, + ), + }, + }, + { + Changeset: commonchangeset.WrapChangeSet(SetCandidatePluginChangeset), + Config: AddDonAndSetCandidateChangesetConfig{ + HomeChainSelector: e.HomeChainSel, + FeedChainSelector: e.FeedChainSel, + NewChainSelector: newChain, + PluginType: types.PluginTypeCCIPExec, + NodeIDs: nodeIDs, + OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), + CCIPOCRParams: DefaultOCRParams( + e.FeedChainSel, + tokenConfig.GetTokenInfo(logger.TestLogger(t), state.Chains[newChain].LinkToken, state.Chains[newChain].Weth9), + nil, + ), + }, + }, + { + Changeset: commonchangeset.WrapChangeSet(PromoteAllCandidatesChangeset), + Config: PromoteAllCandidatesChangesetConfig{ + HomeChainSelector: e.HomeChainSel, + NewChainSelector: newChain, + NodeIDs: nodeIDs, + }, + }, + }) // verify if the configs are updated require.NoError(t, ValidateCCIPHomeConfigSetUp(