Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

deployment/ccip/changeset: optional MCMS for promote candidate #15641

Merged
merged 3 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 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 @@ -238,8 +238,11 @@ func TestAddChainInbound(t *testing.T) {
Changeset: commonchangeset.WrapChangeSet(PromoteAllCandidatesChangeset),
Config: PromoteAllCandidatesChangesetConfig{
HomeChainSelector: e.HomeChainSel,
NewChainSelector: newChain,
DONChainSelector: newChain,
NodeIDs: nodeIDs,
MCMS: &MCMSConfig{
MinDelay: 0,
},
},
},
})
Expand Down
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 @@ -24,46 +26,70 @@ var (

type PromoteAllCandidatesChangesetConfig struct {
HomeChainSelector uint64
NewChainSelector uint64
NodeIDs []string
// DONChainSelector is the chain selector of the DON that we want to promote the candidate config of.
// Note that each (chain, ccip capability version) pair has a unique DON ID.
DONChainSelector uint64
NodeIDs []string
MCMS *MCMSConfig
}

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 err := deployment.IsValidChainSelector(p.HomeChainSelector); err != nil {
return nil, fmt.Errorf("home chain selector invalid: %w", err)
}
if p.NewChainSelector == 0 {
return nil, fmt.Errorf("NewChainSelector must be set")
if err := deployment.IsValidChainSelector(p.DONChainSelector); err != nil {
return nil, fmt.Errorf("don chain selector invalid: %w", err)
}
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 {
return nil, fmt.Errorf("fetch node info: %w", err)
}

donID, exists, err := internal.DonIDForChain(
donID, err := internal.DonIDForChain(
state.Chains[p.HomeChainSelector].CapabilityRegistry,
state.Chains[p.HomeChainSelector].CCIPHome,
p.NewChainSelector,
p.DONChainSelector,
)
if err != nil {
return nil, fmt.Errorf("fetch don id for chain: %w", err)
}
if !exists {
return nil, fmt.Errorf("don id for chain(%d) does not exist", p.NewChainSelector)
if donID == 0 {
return nil, fmt.Errorf("don doesn't exist in CR for chain %d", p.DONChainSelector)
}
// 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 +111,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,
cfg.DONChainSelector,
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 @@ -206,13 +243,14 @@ func setCandidateOnExistingDon(
nodes deployment.Nodes,
) ([]mcms.Operation, error) {
// fetch DON ID for the chain
donID, exists, err := internal.DonIDForChain(capReg, ccipHome, chainSelector)
donID, err := internal.DonIDForChain(capReg, ccipHome, chainSelector)
if err != nil {
return nil, fmt.Errorf("fetch don id for chain: %w", err)
}
if !exists {
return nil, fmt.Errorf("don id for chain(%d) does not exist", chainSelector)
if donID == 0 {
return nil, fmt.Errorf("don doesn't exist in CR for chain %d", chainSelector)
}

fmt.Printf("donID: %d", donID)
encodedSetCandidateCall, err := internal.CCIPHomeABI.Pack(
"setCandidate",
Expand Down Expand Up @@ -251,19 +289,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 +316,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 +331,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,28 +347,31 @@ 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)
donID, err := internal.DonIDForChain(capReg, ccipHome, chainSelector)
if err != nil {
return nil, fmt.Errorf("fetch don id for chain: %w", err)
}
if !exists {
return nil, fmt.Errorf("don id for chain(%d) does not exist", chainSelector)
if donID == 0 {
return nil, fmt.Errorf("don doesn't exist in CR for chain %d", chainSelector)
}

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
Loading
Loading