Skip to content

Commit

Permalink
deployment/ccip/changeset: optional MCMS for promote candidate
Browse files Browse the repository at this point in the history
Add an optional MCMS config that, when nil, will simply execute the
transactions to promote the candidate config using the deployer key.
When non-nil, it only generates the MCMS proposals.

Updated some of the validation as well and removed previous validation
that was incorrect.
  • Loading branch information
makramkd committed Dec 11, 2024
1 parent 3e74cb9 commit 5649294
Show file tree
Hide file tree
Showing 5 changed files with 237 additions and 37 deletions.
109 changes: 78 additions & 31 deletions deployment/ccip/changeset/cs_active_candidate.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package changeset

import (
"context"
"fmt"
"math/big"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers"
"github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms"
Expand All @@ -26,6 +28,7 @@ type PromoteAllCandidatesChangesetConfig struct {
HomeChainSelector uint64
NewChainSelector uint64
NodeIDs []string
MCMS *MCMSConfig
}

func (p PromoteAllCandidatesChangesetConfig) Validate(e deployment.Environment, state CCIPOnChainState) (deployment.Nodes, error) {
Expand All @@ -38,6 +41,12 @@ func (p PromoteAllCandidatesChangesetConfig) Validate(e deployment.Environment,
if len(p.NodeIDs) == 0 {
return nil, fmt.Errorf("NodeIDs must be set")
}
if state.Chains[p.HomeChainSelector].CCIPHome == nil {
return nil, fmt.Errorf("CCIPHome contract does not exist")
}
if state.Chains[p.HomeChainSelector].CapabilityRegistry == nil {
return nil, fmt.Errorf("CapabilityRegistry contract does not exist")
}

nodes, err := deployment.NodeInfo(p.NodeIDs, e.Offchain)
if err != nil {
Expand All @@ -55,15 +64,30 @@ func (p PromoteAllCandidatesChangesetConfig) Validate(e deployment.Environment,
if !exists {
return nil, fmt.Errorf("don id for chain(%d) does not exist", p.NewChainSelector)
}
// 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")
}

// Check that candidate digest and active digest are not both zero - this is enforced onchain.
commitConfigs, err := state.Chains[p.HomeChainSelector].CCIPHome.GetAllConfigs(&bind.CallOpts{
Context: context.Background(),
}, donID, uint8(cctypes.PluginTypeCCIPCommit))
if err != nil {
return nil, fmt.Errorf("fetching commit configs from cciphome: %w", err)
}

execConfigs, err := state.Chains[p.HomeChainSelector].CCIPHome.GetAllConfigs(&bind.CallOpts{
Context: context.Background(),
}, donID, uint8(cctypes.PluginTypeCCIPExec))
if err != nil {
return nil, fmt.Errorf("fetching exec configs from cciphome: %w", err)
}

if commitConfigs.ActiveConfig.ConfigDigest == [32]byte{} &&
commitConfigs.CandidateConfig.ConfigDigest == [32]byte{} {
return nil, fmt.Errorf("commit active and candidate config digests are both zero")
}

if execConfigs.ActiveConfig.ConfigDigest == [32]byte{} &&
execConfigs.CandidateConfig.ConfigDigest == [32]byte{} {
return nil, fmt.Errorf("exec active and candidate config digests are both zero")
}

return nodes, nil
Expand All @@ -85,33 +109,44 @@ func PromoteAllCandidatesChangeset(
return deployment.ChangesetOutput{}, fmt.Errorf("%w: %w", deployment.ErrInvalidConfig, err)
}

txOpts := e.Chains[cfg.HomeChainSelector].DeployerKey
if cfg.MCMS != nil {
txOpts = deployment.SimTransactOpts()
}

homeChain := e.Chains[cfg.HomeChainSelector]

promoteCandidateOps, err := promoteAllCandidatesForChainOps(
homeChain,
txOpts,
state.Chains[cfg.HomeChainSelector].CapabilityRegistry,
state.Chains[cfg.HomeChainSelector].CCIPHome,
cfg.NewChainSelector,
nodes.NonBootstraps(),
cfg.MCMS != nil,
)
if err != nil {
return deployment.ChangesetOutput{}, err
return deployment.ChangesetOutput{}, fmt.Errorf("generating promote candidate ops: %w", err)
}

var (
timelocksPerChain = map[uint64]common.Address{
// Disabled MCMS means that we already executed the txes, so just return early w/out the proposals.
if cfg.MCMS == nil {
return deployment.ChangesetOutput{}, nil
}

prop, err := proposalutils.BuildProposalFromBatches(
map[uint64]common.Address{
cfg.HomeChainSelector: state.Chains[cfg.HomeChainSelector].Timelock.Address(),
}
proposerMCMSes = map[uint64]*gethwrappers.ManyChainMultiSig{
},
map[uint64]*gethwrappers.ManyChainMultiSig{
cfg.HomeChainSelector: state.Chains[cfg.HomeChainSelector].ProposerMcm,
}
)
prop, err := proposalutils.BuildProposalFromBatches(
timelocksPerChain,
proposerMCMSes,
},
[]timelock.BatchChainOperation{{
ChainIdentifier: mcms.ChainIdentifier(cfg.HomeChainSelector),
Batch: promoteCandidateOps,
}},
"promoteCandidate for commit and execution",
0, // minDelay
cfg.MCMS.MinDelay,
)
if err != nil {
return deployment.ChangesetOutput{}, err
Expand Down Expand Up @@ -251,19 +286,21 @@ func setCandidateOnExistingDon(
}

// promoteCandidateOp will create the MCMS Operation for `promoteCandidateAndRevokeActive` directed towards the capabilityRegistry
func promoteCandidateOp(donID uint32, pluginType uint8, capReg *capabilities_registry.CapabilitiesRegistry,
ccipHome *ccip_home.CCIPHome, nodes deployment.Nodes) (mcms.Operation, error) {

func promoteCandidateOp(
homeChain deployment.Chain,
txOpts *bind.TransactOpts,
donID uint32,
pluginType uint8,
capReg *capabilities_registry.CapabilitiesRegistry,
ccipHome *ccip_home.CCIPHome,
nodes deployment.Nodes,
mcmsEnabled bool,
) (mcms.Operation, error) {
allConfigs, err := ccipHome.GetAllConfigs(nil, donID, pluginType)
if err != nil {
return mcms.Operation{}, err
}

if allConfigs.CandidateConfig.ConfigDigest == [32]byte{} {
return mcms.Operation{}, fmt.Errorf("candidate digest is empty, expected nonempty")
}
fmt.Printf("commit candidate digest after setCandidate: %x\n", allConfigs.CandidateConfig.ConfigDigest)

encodedPromotionCall, err := internal.CCIPHomeABI.Pack(
"promoteCandidateAndRevokeActive",
donID,
Expand All @@ -276,7 +313,7 @@ func promoteCandidateOp(donID uint32, pluginType uint8, capReg *capabilities_reg
}

updateDonTx, err := capReg.UpdateDON(
deployment.SimTransactOpts(),
txOpts,
donID,
nodes.PeerIDs(),
[]capabilities_registry.CapabilitiesRegistryCapabilityConfiguration{
Expand All @@ -291,6 +328,13 @@ func promoteCandidateOp(donID uint32, pluginType uint8, capReg *capabilities_reg
if err != nil {
return mcms.Operation{}, fmt.Errorf("error creating updateDon op for donID(%d) and plugin type (%d): %w", donID, pluginType, err)
}
if !mcmsEnabled {
_, err = deployment.ConfirmIfNoError(homeChain, updateDonTx, err)
if err != nil {
return mcms.Operation{}, fmt.Errorf("error confirming updateDon call for donID(%d) and plugin type (%d): %w", donID, pluginType, err)
}
}

return mcms.Operation{
To: capReg.Address(),
Data: updateDonTx.Data(),
Expand All @@ -300,10 +344,13 @@ func promoteCandidateOp(donID uint32, pluginType uint8, capReg *capabilities_reg

// promoteAllCandidatesForChainOps promotes the candidate commit and exec configs to active by calling promoteCandidateAndRevokeActive on CCIPHome through the UpdateDON call on CapReg contract
func promoteAllCandidatesForChainOps(
homeChain deployment.Chain,
txOpts *bind.TransactOpts,
capReg *capabilities_registry.CapabilitiesRegistry,
ccipHome *ccip_home.CCIPHome,
chainSelector uint64,
nodes deployment.Nodes,
mcmsEnabled bool,
) ([]mcms.Operation, error) {
// fetch DON ID for the chain
donID, exists, err := internal.DonIDForChain(capReg, ccipHome, chainSelector)
Expand All @@ -315,13 +362,13 @@ func promoteAllCandidatesForChainOps(
}

var mcmsOps []mcms.Operation
updateCommitOp, err := promoteCandidateOp(donID, uint8(cctypes.PluginTypeCCIPCommit), capReg, ccipHome, nodes)
updateCommitOp, err := promoteCandidateOp(homeChain, txOpts, donID, uint8(cctypes.PluginTypeCCIPCommit), capReg, ccipHome, nodes, mcmsEnabled)
if err != nil {
return nil, fmt.Errorf("promote candidate op: %w", err)
}
mcmsOps = append(mcmsOps, updateCommitOp)

updateExecOp, err := promoteCandidateOp(donID, uint8(cctypes.PluginTypeCCIPExec), capReg, ccipHome, nodes)
updateExecOp, err := promoteCandidateOp(homeChain, txOpts, donID, uint8(cctypes.PluginTypeCCIPExec), capReg, ccipHome, nodes, mcmsEnabled)
if err != nil {
return nil, fmt.Errorf("promote candidate op: %w", err)
}
Expand Down
9 changes: 8 additions & 1 deletion deployment/ccip/changeset/cs_active_candidate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,14 @@ func TestActiveCandidate(t *testing.T) {
oldCandidateDigest, err := state.Chains[tenv.HomeChainSel].CCIPHome.GetCandidateDigest(nil, donID, uint8(cctypes.PluginTypeCCIPExec))
require.NoError(t, err)

promoteOps, err := promoteAllCandidatesForChainOps(state.Chains[tenv.HomeChainSel].CapabilityRegistry, state.Chains[tenv.HomeChainSel].CCIPHome, tenv.FeedChainSel, nodes.NonBootstraps())
promoteOps, err := promoteAllCandidatesForChainOps(
tenv.Env.Chains[tenv.HomeChainSel],
deployment.SimTransactOpts(),
state.Chains[tenv.HomeChainSel].CapabilityRegistry,
state.Chains[tenv.HomeChainSel].CCIPHome,
tenv.FeedChainSel,
nodes.NonBootstraps(),
true)
require.NoError(t, err)
promoteProposal, err := proposalutils.BuildProposalFromBatches(timelocksPerChain, proposerMCMSes, []timelock.BatchChainOperation{{
ChainIdentifier: mcms.ChainIdentifier(tenv.HomeChainSel),
Expand Down
13 changes: 8 additions & 5 deletions deployment/ccip/changeset/cs_add_chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,15 +153,15 @@ func TestAddChainInbound(t *testing.T) {

// transfer ownership to timelock
_, err = commonchangeset.ApplyChangesets(t, e.Env, map[uint64]*commonchangeset.TimelockExecutionContracts{
initialDeploy[0]: &commonchangeset.TimelockExecutionContracts{
initialDeploy[0]: {
Timelock: state.Chains[initialDeploy[0]].Timelock,
CallProxy: state.Chains[initialDeploy[0]].CallProxy,
},
initialDeploy[1]: &commonchangeset.TimelockExecutionContracts{
initialDeploy[1]: {
Timelock: state.Chains[initialDeploy[1]].Timelock,
CallProxy: state.Chains[initialDeploy[1]].CallProxy,
},
initialDeploy[2]: &commonchangeset.TimelockExecutionContracts{
initialDeploy[2]: {
Timelock: state.Chains[initialDeploy[2]].Timelock,
CallProxy: state.Chains[initialDeploy[2]].CallProxy,
},
Expand Down Expand Up @@ -195,11 +195,11 @@ func TestAddChainInbound(t *testing.T) {
}

_, err = commonchangeset.ApplyChangesets(t, e.Env, map[uint64]*commonchangeset.TimelockExecutionContracts{
e.HomeChainSel: &commonchangeset.TimelockExecutionContracts{
e.HomeChainSel: {
Timelock: state.Chains[e.HomeChainSel].Timelock,
CallProxy: state.Chains[e.HomeChainSel].CallProxy,
},
newChain: &commonchangeset.TimelockExecutionContracts{
newChain: {
Timelock: state.Chains[newChain].Timelock,
CallProxy: state.Chains[newChain].CallProxy,
},
Expand Down Expand Up @@ -240,6 +240,9 @@ func TestAddChainInbound(t *testing.T) {
HomeChainSelector: e.HomeChainSel,
NewChainSelector: newChain,
NodeIDs: nodeIDs,
MCMS: &MCMSConfig{
MinDelay: 0,
},
},
},
})
Expand Down
Loading

0 comments on commit 5649294

Please sign in to comment.