From 230dcc85fd3757b35ae5bcb0fb19fea1a2e54128 Mon Sep 17 00:00:00 2001 From: "Simon B.Robert" Date: Thu, 5 Dec 2024 10:13:50 -0500 Subject: [PATCH 1/9] Initial commit for RMN Home config changeset --- .../changeset/cs_update_rmn_home_config.go | 121 ++++++++++++++++++ .../cs_update_rmn_home_config_test.go | 121 ++++++++++++++++++ 2 files changed, 242 insertions(+) create mode 100644 deployment/ccip/changeset/cs_update_rmn_home_config.go create mode 100644 deployment/ccip/changeset/cs_update_rmn_home_config_test.go diff --git a/deployment/ccip/changeset/cs_update_rmn_home_config.go b/deployment/ccip/changeset/cs_update_rmn_home_config.go new file mode 100644 index 00000000000..88830922dfc --- /dev/null +++ b/deployment/ccip/changeset/cs_update_rmn_home_config.go @@ -0,0 +1,121 @@ +package changeset + +import ( + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_home" +) + +type SetRMNHomeCandidateConfig struct { + HomeChainSelector uint64 + RMNStaticConfig rmn_home.RMNHomeStaticConfig + RMNDynamicConfig rmn_home.RMNHomeDynamicConfig + DigestToOverride [32]byte +} + +type PromoteRMNHomeCandidateConfig struct { + HomeChainSelector uint64 +} + +func NewSetRMNHomeCandidateConfigChangeset(e deployment.Environment, config SetRMNHomeCandidateConfig) (deployment.ChangesetOutput, error) { + state, err := LoadOnchainState(e) + lggr := e.Logger + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to load onchain state: %w", err) + } + rmnHome := state.Chains[config.HomeChainSelector].RMNHome + + setCandidateTx, err := rmnHome.SetCandidate(deployment.SimTransactOpts(), config.RMNStaticConfig, config.RMNDynamicConfig, config.DigestToOverride) + if err != nil { + lggr.Errorw("Failed to build call data to set RMNHome candidate digest", "err", err) + return deployment.ChangesetOutput{}, err + } + + op := mcms.Operation{ + To: rmnHome.Address(), + Data: setCandidateTx.Data(), + Value: big.NewInt(0), + } + + prop, err := buildProposal(op, state, config.HomeChainSelector) + + return deployment.ChangesetOutput{ + Proposals: []timelock.MCMSWithTimelockProposal{*prop}, + }, nil +} + +func NewPromoteCandidateConfigChangeset(e deployment.Environment, config PromoteRMNHomeCandidateConfig) (deployment.ChangesetOutput, error) { + state, err := LoadOnchainState(e) + lggr := e.Logger + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to load onchain state: %w", err) + } + rmnHome := state.Chains[config.HomeChainSelector].RMNHome + + currentCandidateDigest, err := rmnHome.GetCandidateDigest(nil) + if err != nil { + lggr.Errorw("Failed to get RMNHome candidate digest", "err", err) + return deployment.ChangesetOutput{}, err + } + + currentActiveDigest, err := rmnHome.GetActiveDigest(nil) + if err != nil { + lggr.Errorw("Failed to get RMNHome active digest", "err", err) + return deployment.ChangesetOutput{}, err + } + + promoteCandidateTx, err := rmnHome.PromoteCandidateAndRevokeActive(deployment.SimTransactOpts(), currentCandidateDigest, currentActiveDigest) + if err != nil { + lggr.Errorw("Failed to get call data to promote RMNHome candidate digest", "err", err) + return deployment.ChangesetOutput{}, err + } + + op := mcms.Operation{ + To: rmnHome.Address(), + Data: promoteCandidateTx.Data(), + Value: big.NewInt(0), + } + + prop, err := buildProposal(op, state, config.HomeChainSelector) + + if err != nil { + lggr.Errorw("Failed to build proposal", "err", err) + return deployment.ChangesetOutput{}, err + } + + return deployment.ChangesetOutput{ + Proposals: []timelock.MCMSWithTimelockProposal{*prop}, + }, nil +} + +func buildProposal(op mcms.Operation, state CCIPOnChainState, homeChainSelector uint64) (*timelock.MCMSWithTimelockProposal, error) { + batches := []timelock.BatchChainOperation{ + { + ChainIdentifier: mcms.ChainIdentifier(homeChainSelector), + Batch: []mcms.Operation{op}, + }, + } + + timelocksPerChain := map[uint64]common.Address{ + homeChainSelector: state.Chains[homeChainSelector].Timelock.Address(), + } + + proposerMCMSes := map[uint64]*gethwrappers.ManyChainMultiSig{ + homeChainSelector: state.Chains[homeChainSelector].ProposerMcm, + } + + return proposalutils.BuildProposalFromBatches( + timelocksPerChain, + proposerMCMSes, + batches, + "proposal to promote candidate config", + 0, + ) +} diff --git a/deployment/ccip/changeset/cs_update_rmn_home_config_test.go b/deployment/ccip/changeset/cs_update_rmn_home_config_test.go new file mode 100644 index 00000000000..157a113160a --- /dev/null +++ b/deployment/ccip/changeset/cs_update_rmn_home_config_test.go @@ -0,0 +1,121 @@ +package changeset + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + + mcms "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" + commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" + "github.com/smartcontractkit/chainlink/deployment/environment/memory" + "github.com/smartcontractkit/chainlink/v2/core/logger" +) + +func TestUpdateRMNHomeConfig(t *testing.T) { + t.Parallel() + e := NewMemoryEnvironmentWithJobsAndContracts(t, logger.TestLogger(t), memory.MemoryEnvironmentConfig{ + Chains: 2, + Nodes: 4, + Bootstraps: 1, + }, nil) + + state, err := LoadOnchainState(e.Env) + require.NoError(t, err) + + // This is required because RMNHome is initially owner by the deployer + err = transferOwnershipToHomeChainTimelock(t, e, state) + require.NoError(t, err) + + rmnHome := state.Chains[e.HomeChainSel].RMNHome + + previousCandidateDigest, err := rmnHome.GetCandidateDigest(nil) + require.NoError(t, err) + previousActiveDigest, err := rmnHome.GetActiveDigest(nil) + require.NoError(t, err) + + configInput := SetRMNHomeCandidateConfig{ + HomeChainSelector: e.HomeChainSel, + RMNStaticConfig: NewTestRMNStaticConfig(), + RMNDynamicConfig: NewTestRMNDynamicConfig(), + } + + timelocksPerChain := buildTimelockPerChain(e, state) + commonchangeset.ApplyChangesets(t, e.Env, timelocksPerChain, []commonchangeset.ChangesetApplication{ + { + Changeset: commonchangeset.WrapChangeSet(NewSetRMNHomeCandidateConfigChangeset), + Config: configInput, + }, + }) + + state, err = LoadOnchainState(e.Env) + require.NoError(t, err) + + currentCandidateDigest, err := rmnHome.GetCandidateDigest(nil) + require.NoError(t, err) + currentActiveDigest, err := rmnHome.GetActiveDigest(nil) + require.NoError(t, err) + + require.NotEqual(t, previousCandidateDigest, currentCandidateDigest) + require.Equal(t, previousActiveDigest, currentActiveDigest) + + promoteConfigInput := PromoteRMNHomeCandidateConfig{ + HomeChainSelector: e.HomeChainSel, + } + + commonchangeset.ApplyChangesets(t, e.Env, timelocksPerChain, []commonchangeset.ChangesetApplication{ + { + Changeset: commonchangeset.WrapChangeSet(NewPromoteCandidateConfigChangeset), + Config: promoteConfigInput, + }, + }) + + require.NoError(t, err) + currentActiveDigest, err = rmnHome.GetActiveDigest(nil) + + require.NoError(t, err) + require.NotEqual(t, previousActiveDigest, currentActiveDigest) +} + +func buildTimelockPerChain(e DeployedEnv, state CCIPOnChainState) map[uint64]*mcms.RBACTimelock { + timelocksPerChain := make(map[uint64]*mcms.RBACTimelock) + for _, chain := range e.Env.Chains { + timelocksPerChain[chain.Selector] = state.Chains[chain.Selector].Timelock + } + return timelocksPerChain +} + +func transferOwnershipToHomeChainTimelock(t *testing.T, e DeployedEnv, state CCIPOnChainState) error { + rmnHome := state.Chains[e.HomeChainSel].RMNHome + timelockAddress := state.Chains[e.HomeChainSel].Timelock.Address() + proposerMcm := state.Chains[e.HomeChainSel].ProposerMcm + _, err := commonchangeset.NewTransferOwnershipChangeset(e.Env, commonchangeset.TransferOwnershipConfig{ + Contracts: map[uint64][]commonchangeset.OwnershipTransferrer{ + e.HomeChainSel: {rmnHome}, + }, + OwnersPerChain: map[uint64]common.Address{ + e.HomeChainSel: timelockAddress, + }, + }) + + timelocksPerChain := buildTimelockPerChain(e, state) + + _, err = commonchangeset.ApplyChangesets(t, e.Env, timelocksPerChain, []commonchangeset.ChangesetApplication{ + { + Changeset: commonchangeset.WrapChangeSet(commonchangeset.NewAcceptOwnershipChangeset), + Config: commonchangeset.AcceptOwnershipConfig{ + Contracts: map[uint64][]commonchangeset.OwnershipAcceptor{ + e.HomeChainSel: {rmnHome}, + }, + OwnersPerChain: map[uint64]common.Address{ + e.HomeChainSel: timelockAddress, + }, + ProposerMCMSes: map[uint64]*mcms.ManyChainMultiSig{ + e.HomeChainSel: proposerMcm, + }, + }, + }, + }) + + return err +} From c7eee407cea4b69be939ee83ddc2f604a6262f3f Mon Sep 17 00:00:00 2001 From: "Simon B.Robert" Date: Fri, 6 Dec 2024 10:26:35 -0500 Subject: [PATCH 2/9] Add RMNRemote changeset --- .../ccip/changeset/cs_update_rmn_config.go | 256 ++++++++++++++++++ ...g_test.go => cs_update_rmn_config_test.go} | 88 +++++- .../changeset/cs_update_rmn_home_config.go | 121 --------- 3 files changed, 331 insertions(+), 134 deletions(-) create mode 100644 deployment/ccip/changeset/cs_update_rmn_config.go rename deployment/ccip/changeset/{cs_update_rmn_home_config_test.go => cs_update_rmn_config_test.go} (54%) delete mode 100644 deployment/ccip/changeset/cs_update_rmn_home_config.go diff --git a/deployment/ccip/changeset/cs_update_rmn_config.go b/deployment/ccip/changeset/cs_update_rmn_config.go new file mode 100644 index 00000000000..77534aa3e61 --- /dev/null +++ b/deployment/ccip/changeset/cs_update_rmn_config.go @@ -0,0 +1,256 @@ +package changeset + +import ( + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" + mcmsWrappers "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_home" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_remote" +) + +type SetRMNHomeCandidateConfig struct { + HomeChainSelector uint64 + RMNStaticConfig rmn_home.RMNHomeStaticConfig + RMNDynamicConfig rmn_home.RMNHomeDynamicConfig + DigestToOverride [32]byte +} + +func (c SetRMNHomeCandidateConfig) Validate() error { + if c.HomeChainSelector == 0 { + return fmt.Errorf("HomeChainSelector must be set") + } + if len(c.RMNDynamicConfig.OffchainConfig) != 0 { + return fmt.Errorf("RMNDynamicConfig.OffchainConfig must be empty") + } + if len(c.RMNStaticConfig.OffchainConfig) != 0 { + return fmt.Errorf("RMNStaticConfig.OffchainConfig must be empty") + } + + return nil +} + +type PromoteRMNHomeCandidateConfig struct { + HomeChainSelector uint64 +} + +func (c PromoteRMNHomeCandidateConfig) Validate() error { + if c.HomeChainSelector == 0 { + return fmt.Errorf("HomeChainSelector must be set") + } + + return nil +} + +func NewSetRMNHomeCandidateConfigChangeset(e deployment.Environment, config SetRMNHomeCandidateConfig) (deployment.ChangesetOutput, error) { + state, err := LoadOnchainState(e) + lggr := e.Logger + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to load onchain state: %w", err) + } + rmnHome := state.Chains[config.HomeChainSelector].RMNHome + + setCandidateTx, err := rmnHome.SetCandidate(deployment.SimTransactOpts(), config.RMNStaticConfig, config.RMNDynamicConfig, config.DigestToOverride) + if err != nil { + lggr.Errorw("Failed to build call data to set RMNHome candidate digest", "err", err) + return deployment.ChangesetOutput{}, err + } + + op := mcms.Operation{ + To: rmnHome.Address(), + Data: setCandidateTx.Data(), + Value: big.NewInt(0), + } + + prop, err := buildProposal(e, op, state, config.HomeChainSelector) + + return deployment.ChangesetOutput{ + Proposals: []timelock.MCMSWithTimelockProposal{*prop}, + }, nil +} + +func NewPromoteCandidateConfigChangeset(e deployment.Environment, config PromoteRMNHomeCandidateConfig) (deployment.ChangesetOutput, error) { + state, err := LoadOnchainState(e) + lggr := e.Logger + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to load onchain state: %w", err) + } + rmnHome := state.Chains[config.HomeChainSelector].RMNHome + + currentCandidateDigest, err := rmnHome.GetCandidateDigest(nil) + if err != nil { + lggr.Errorw("Failed to get RMNHome candidate digest", "err", err) + return deployment.ChangesetOutput{}, err + } + + currentActiveDigest, err := rmnHome.GetActiveDigest(nil) + if err != nil { + lggr.Errorw("Failed to get RMNHome active digest", "err", err) + return deployment.ChangesetOutput{}, err + } + + promoteCandidateTx, err := rmnHome.PromoteCandidateAndRevokeActive(deployment.SimTransactOpts(), currentCandidateDigest, currentActiveDigest) + if err != nil { + lggr.Errorw("Failed to get call data to promote RMNHome candidate digest", "err", err) + return deployment.ChangesetOutput{}, err + } + + op := mcms.Operation{ + To: rmnHome.Address(), + Data: promoteCandidateTx.Data(), + Value: big.NewInt(0), + } + + prop, err := buildProposal(e, op, state, config.HomeChainSelector) + + if err != nil { + lggr.Errorw("Failed to build proposal", "err", err) + return deployment.ChangesetOutput{}, err + } + + return deployment.ChangesetOutput{ + Proposals: []timelock.MCMSWithTimelockProposal{*prop}, + }, nil +} + +func buildTimelockPerChain(e deployment.Environment, state CCIPOnChainState) map[uint64]*mcmsWrappers.RBACTimelock { + timelocksPerChain := make(map[uint64]*mcmsWrappers.RBACTimelock) + for _, chain := range e.Chains { + timelocksPerChain[chain.Selector] = state.Chains[chain.Selector].Timelock + } + return timelocksPerChain +} + +func buildTimelockAddressPerChain(e deployment.Environment, state CCIPOnChainState) map[uint64]common.Address { + timelocksPerChain := buildTimelockPerChain(e, state) + timelockAddressPerChain := make(map[uint64]common.Address) + for chain, timelock := range timelocksPerChain { + timelockAddressPerChain[chain] = timelock.Address() + } + return timelockAddressPerChain +} + +func buildProposerPerChain(e deployment.Environment, state CCIPOnChainState) map[uint64]*gethwrappers.ManyChainMultiSig { + proposerPerChain := make(map[uint64]*gethwrappers.ManyChainMultiSig) + for _, chain := range e.Chains { + proposerPerChain[chain.Selector] = state.Chains[chain.Selector].ProposerMcm + } + return proposerPerChain +} + +func buildRemoteRemotePerChain(e deployment.Environment, state CCIPOnChainState) map[uint64]*rmn_remote.RMNRemote { + timelocksPerChain := make(map[uint64]*rmn_remote.RMNRemote) + for _, chain := range e.Chains { + timelocksPerChain[chain.Selector] = state.Chains[chain.Selector].RMNRemote + } + return timelocksPerChain +} + +func buildProposal(e deployment.Environment, op mcms.Operation, state CCIPOnChainState, homeChainSelector uint64) (*timelock.MCMSWithTimelockProposal, error) { + batches := []timelock.BatchChainOperation{ + { + ChainIdentifier: mcms.ChainIdentifier(homeChainSelector), + Batch: []mcms.Operation{op}, + }, + } + + timelocksPerChain := buildTimelockAddressPerChain(e, state) + + proposerMCMSes := buildProposerPerChain(e, state) + + return proposalutils.BuildProposalFromBatches( + timelocksPerChain, + proposerMCMSes, + batches, + "proposal to promote candidate config", + 0, + ) +} + +type SetRMNRemoteConfig struct { + HomeChainSelector uint64 + Signers []rmn_remote.RMNRemoteSigner + F uint64 +} + +func (c SetRMNRemoteConfig) Validate() error { + if c.HomeChainSelector == 0 { + return fmt.Errorf("HomeChainSelector must be set") + } + + return nil +} + +func NewSetRMNRemoteConfigChangeset(e deployment.Environment, config SetRMNRemoteConfig) (deployment.ChangesetOutput, error) { + state, err := LoadOnchainState(e) + lggr := e.Logger + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to load onchain state: %w", err) + } + rmnHome := state.Chains[config.HomeChainSelector].RMNHome + + activeConfig, err := rmnHome.GetActiveDigest(nil) + if err != nil { + lggr.Errorw("Failed to get RMNHome active digest", "err", err) + return deployment.ChangesetOutput{}, err + } + + rmnRemotePerChain := buildRemoteRemotePerChain(e, state) + batches := make([]timelock.BatchChainOperation, 0) + for chain, remote := range rmnRemotePerChain { + if remote == nil { + continue + } + + tx, err := remote.SetConfig(deployment.SimTransactOpts(), rmn_remote.RMNRemoteConfig{ + RmnHomeContractConfigDigest: activeConfig, + Signers: config.Signers, + F: config.F, + }) + + if err != nil { + lggr.Errorw("Failed to build call data to set RMNRemote config", "err", err) + return deployment.ChangesetOutput{}, err + } + + op := mcms.Operation{ + To: remote.Address(), + Data: tx.Data(), + Value: big.NewInt(0), + } + + batch := timelock.BatchChainOperation{ + ChainIdentifier: mcms.ChainIdentifier(chain), + Batch: []mcms.Operation{op}, + } + + batches = append(batches, batch) + } + + timelocksPerChain := buildTimelockAddressPerChain(e, state) + + proposerMCMSes := buildProposerPerChain(e, state) + + prop, err := proposalutils.BuildProposalFromBatches( + timelocksPerChain, + proposerMCMSes, + batches, + "proposal to promote candidate config", + 0, + ) + + if err != nil { + lggr.Errorw("Failed to build proposal", "err", err) + return deployment.ChangesetOutput{}, err + } + + return deployment.ChangesetOutput{ + Proposals: []timelock.MCMSWithTimelockProposal{*prop}, + }, nil +} diff --git a/deployment/ccip/changeset/cs_update_rmn_home_config_test.go b/deployment/ccip/changeset/cs_update_rmn_config_test.go similarity index 54% rename from deployment/ccip/changeset/cs_update_rmn_home_config_test.go rename to deployment/ccip/changeset/cs_update_rmn_config_test.go index 157a113160a..463dd8a52d4 100644 --- a/deployment/ccip/changeset/cs_update_rmn_home_config_test.go +++ b/deployment/ccip/changeset/cs_update_rmn_config_test.go @@ -9,6 +9,7 @@ import ( mcms "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" "github.com/smartcontractkit/chainlink/deployment/environment/memory" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_remote" "github.com/smartcontractkit/chainlink/v2/core/logger" ) @@ -26,6 +27,8 @@ func TestUpdateRMNHomeConfig(t *testing.T) { // This is required because RMNHome is initially owner by the deployer err = transferOwnershipToHomeChainTimelock(t, e, state) require.NoError(t, err) + err = transferOwnershipForRMNRemote(t, e, state) + require.NoError(t, err) rmnHome := state.Chains[e.HomeChainSel].RMNHome @@ -34,17 +37,17 @@ func TestUpdateRMNHomeConfig(t *testing.T) { previousActiveDigest, err := rmnHome.GetActiveDigest(nil) require.NoError(t, err) - configInput := SetRMNHomeCandidateConfig{ + setRMNHomeCandidateConfig := SetRMNHomeCandidateConfig{ HomeChainSelector: e.HomeChainSel, RMNStaticConfig: NewTestRMNStaticConfig(), RMNDynamicConfig: NewTestRMNDynamicConfig(), } - timelocksPerChain := buildTimelockPerChain(e, state) + timelocksPerChain := buildTimelockPerChain(e.Env, state) commonchangeset.ApplyChangesets(t, e.Env, timelocksPerChain, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(NewSetRMNHomeCandidateConfigChangeset), - Config: configInput, + Config: setRMNHomeCandidateConfig, }, }) @@ -59,14 +62,14 @@ func TestUpdateRMNHomeConfig(t *testing.T) { require.NotEqual(t, previousCandidateDigest, currentCandidateDigest) require.Equal(t, previousActiveDigest, currentActiveDigest) - promoteConfigInput := PromoteRMNHomeCandidateConfig{ + promoteConfig := PromoteRMNHomeCandidateConfig{ HomeChainSelector: e.HomeChainSel, } - commonchangeset.ApplyChangesets(t, e.Env, timelocksPerChain, []commonchangeset.ChangesetApplication{ + _, err = commonchangeset.ApplyChangesets(t, e.Env, timelocksPerChain, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(NewPromoteCandidateConfigChangeset), - Config: promoteConfigInput, + Config: promoteConfig, }, }) @@ -75,14 +78,37 @@ func TestUpdateRMNHomeConfig(t *testing.T) { require.NoError(t, err) require.NotEqual(t, previousActiveDigest, currentActiveDigest) -} -func buildTimelockPerChain(e DeployedEnv, state CCIPOnChainState) map[uint64]*mcms.RBACTimelock { - timelocksPerChain := make(map[uint64]*mcms.RBACTimelock) - for _, chain := range e.Env.Chains { - timelocksPerChain[chain.Selector] = state.Chains[chain.Selector].Timelock + setRemoteConfig := SetRMNRemoteConfig{ + HomeChainSelector: e.HomeChainSel, + Signers: []rmn_remote.RMNRemoteSigner{ + { + OnchainPublicKey: common.Address{}, + NodeIndex: 0, + }, + }, + F: 0, + } + + _, err = commonchangeset.ApplyChangesets(t, e.Env, timelocksPerChain, []commonchangeset.ChangesetApplication{ + { + Changeset: commonchangeset.WrapChangeSet(NewSetRMNRemoteConfigChangeset), + Config: setRemoteConfig, + }, + }) + + require.NoError(t, err) + rmnRemotePerChain := buildRemoteRemotePerChain(e.Env, state) + for _, rmnRemote := range rmnRemotePerChain { + remoteConfigSetEvents, err := rmnRemote.FilterConfigSet(nil, nil) + require.NoError(t, err) + var lastEvent *rmn_remote.RMNRemoteConfigSet + for remoteConfigSetEvents.Next() { + lastEvent = remoteConfigSetEvents.Event + } + require.NotNil(t, lastEvent) + require.Equal(t, lastEvent.Config.RmnHomeContractConfigDigest, currentActiveDigest) } - return timelocksPerChain } func transferOwnershipToHomeChainTimelock(t *testing.T, e DeployedEnv, state CCIPOnChainState) error { @@ -98,7 +124,7 @@ func transferOwnershipToHomeChainTimelock(t *testing.T, e DeployedEnv, state CCI }, }) - timelocksPerChain := buildTimelockPerChain(e, state) + timelocksPerChain := buildTimelockPerChain(e.Env, state) _, err = commonchangeset.ApplyChangesets(t, e.Env, timelocksPerChain, []commonchangeset.ChangesetApplication{ { @@ -119,3 +145,39 @@ func transferOwnershipToHomeChainTimelock(t *testing.T, e DeployedEnv, state CCI return err } + +func transferOwnershipForRMNRemote(t *testing.T, e DeployedEnv, state CCIPOnChainState) error { + rmnRemotePerChain := buildRemoteRemotePerChain(e.Env, state) + timelockAddressPerChain := buildTimelockAddressPerChain(e.Env, state) + timelocksPerChain := buildTimelockPerChain(e.Env, state) + proposers := buildProposerPerChain(e.Env, state) + for chain, rmnRemote := range rmnRemotePerChain { + _, err := commonchangeset.NewTransferOwnershipChangeset(e.Env, commonchangeset.TransferOwnershipConfig{ + Contracts: map[uint64][]commonchangeset.OwnershipTransferrer{ + chain: {rmnRemote}, + }, + OwnersPerChain: timelockAddressPerChain, + }) + if err != nil { + return err + } + + _, err = commonchangeset.ApplyChangesets(t, e.Env, timelocksPerChain, []commonchangeset.ChangesetApplication{ + { + Changeset: commonchangeset.WrapChangeSet(commonchangeset.NewAcceptOwnershipChangeset), + Config: commonchangeset.AcceptOwnershipConfig{ + Contracts: map[uint64][]commonchangeset.OwnershipAcceptor{ + chain: {rmnRemote}, + }, + OwnersPerChain: timelockAddressPerChain, + ProposerMCMSes: proposers, + }, + }, + }) + + if err != nil { + return err + } + } + return nil +} diff --git a/deployment/ccip/changeset/cs_update_rmn_home_config.go b/deployment/ccip/changeset/cs_update_rmn_home_config.go deleted file mode 100644 index 88830922dfc..00000000000 --- a/deployment/ccip/changeset/cs_update_rmn_home_config.go +++ /dev/null @@ -1,121 +0,0 @@ -package changeset - -import ( - "fmt" - "math/big" - - "github.com/ethereum/go-ethereum/common" - "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" - "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms" - "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" - "github.com/smartcontractkit/chainlink/deployment" - "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_home" -) - -type SetRMNHomeCandidateConfig struct { - HomeChainSelector uint64 - RMNStaticConfig rmn_home.RMNHomeStaticConfig - RMNDynamicConfig rmn_home.RMNHomeDynamicConfig - DigestToOverride [32]byte -} - -type PromoteRMNHomeCandidateConfig struct { - HomeChainSelector uint64 -} - -func NewSetRMNHomeCandidateConfigChangeset(e deployment.Environment, config SetRMNHomeCandidateConfig) (deployment.ChangesetOutput, error) { - state, err := LoadOnchainState(e) - lggr := e.Logger - if err != nil { - return deployment.ChangesetOutput{}, fmt.Errorf("failed to load onchain state: %w", err) - } - rmnHome := state.Chains[config.HomeChainSelector].RMNHome - - setCandidateTx, err := rmnHome.SetCandidate(deployment.SimTransactOpts(), config.RMNStaticConfig, config.RMNDynamicConfig, config.DigestToOverride) - if err != nil { - lggr.Errorw("Failed to build call data to set RMNHome candidate digest", "err", err) - return deployment.ChangesetOutput{}, err - } - - op := mcms.Operation{ - To: rmnHome.Address(), - Data: setCandidateTx.Data(), - Value: big.NewInt(0), - } - - prop, err := buildProposal(op, state, config.HomeChainSelector) - - return deployment.ChangesetOutput{ - Proposals: []timelock.MCMSWithTimelockProposal{*prop}, - }, nil -} - -func NewPromoteCandidateConfigChangeset(e deployment.Environment, config PromoteRMNHomeCandidateConfig) (deployment.ChangesetOutput, error) { - state, err := LoadOnchainState(e) - lggr := e.Logger - if err != nil { - return deployment.ChangesetOutput{}, fmt.Errorf("failed to load onchain state: %w", err) - } - rmnHome := state.Chains[config.HomeChainSelector].RMNHome - - currentCandidateDigest, err := rmnHome.GetCandidateDigest(nil) - if err != nil { - lggr.Errorw("Failed to get RMNHome candidate digest", "err", err) - return deployment.ChangesetOutput{}, err - } - - currentActiveDigest, err := rmnHome.GetActiveDigest(nil) - if err != nil { - lggr.Errorw("Failed to get RMNHome active digest", "err", err) - return deployment.ChangesetOutput{}, err - } - - promoteCandidateTx, err := rmnHome.PromoteCandidateAndRevokeActive(deployment.SimTransactOpts(), currentCandidateDigest, currentActiveDigest) - if err != nil { - lggr.Errorw("Failed to get call data to promote RMNHome candidate digest", "err", err) - return deployment.ChangesetOutput{}, err - } - - op := mcms.Operation{ - To: rmnHome.Address(), - Data: promoteCandidateTx.Data(), - Value: big.NewInt(0), - } - - prop, err := buildProposal(op, state, config.HomeChainSelector) - - if err != nil { - lggr.Errorw("Failed to build proposal", "err", err) - return deployment.ChangesetOutput{}, err - } - - return deployment.ChangesetOutput{ - Proposals: []timelock.MCMSWithTimelockProposal{*prop}, - }, nil -} - -func buildProposal(op mcms.Operation, state CCIPOnChainState, homeChainSelector uint64) (*timelock.MCMSWithTimelockProposal, error) { - batches := []timelock.BatchChainOperation{ - { - ChainIdentifier: mcms.ChainIdentifier(homeChainSelector), - Batch: []mcms.Operation{op}, - }, - } - - timelocksPerChain := map[uint64]common.Address{ - homeChainSelector: state.Chains[homeChainSelector].Timelock.Address(), - } - - proposerMCMSes := map[uint64]*gethwrappers.ManyChainMultiSig{ - homeChainSelector: state.Chains[homeChainSelector].ProposerMcm, - } - - return proposalutils.BuildProposalFromBatches( - timelocksPerChain, - proposerMCMSes, - batches, - "proposal to promote candidate config", - 0, - ) -} From f884d443054fff7537029709f8b241279fb8ff35 Mon Sep 17 00:00:00 2001 From: "Simon B.Robert" Date: Fri, 6 Dec 2024 15:00:06 -0500 Subject: [PATCH 3/9] Integrate with develop --- .../ccip/changeset/cs_update_rmn_config.go | 141 +++++++++++++++--- .../changeset/cs_update_rmn_config_test.go | 113 ++++---------- 2 files changed, 153 insertions(+), 101 deletions(-) diff --git a/deployment/ccip/changeset/cs_update_rmn_config.go b/deployment/ccip/changeset/cs_update_rmn_config.go index 77534aa3e61..7bf82fbd19b 100644 --- a/deployment/ccip/changeset/cs_update_rmn_config.go +++ b/deployment/ccip/changeset/cs_update_rmn_config.go @@ -3,6 +3,7 @@ package changeset import ( "fmt" "math/big" + "reflect" "github.com/ethereum/go-ethereum/common" "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" @@ -23,9 +24,11 @@ type SetRMNHomeCandidateConfig struct { } func (c SetRMNHomeCandidateConfig) Validate() error { - if c.HomeChainSelector == 0 { - return fmt.Errorf("HomeChainSelector must be set") + err := deployment.IsValidChainSelector(c.HomeChainSelector) + if err != nil { + return err } + if len(c.RMNDynamicConfig.OffchainConfig) != 0 { return fmt.Errorf("RMNDynamicConfig.OffchainConfig must be empty") } @@ -33,16 +36,38 @@ func (c SetRMNHomeCandidateConfig) Validate() error { return fmt.Errorf("RMNStaticConfig.OffchainConfig must be empty") } + if len(c.RMNStaticConfig.Nodes) > 256 { + return fmt.Errorf("RMNStaticConfig.Nodes must be less than 256") + } + + peerIds := make(map[[32]byte]struct{}) + for _, node := range c.RMNStaticConfig.Nodes { + if _, exists := peerIds[node.PeerId]; exists { + return fmt.Errorf("peerId %x is duplicated", node.PeerId) + } + peerIds[node.PeerId] = struct{}{} + } + + offchainPublicKeys := make(map[[32]byte]struct{}) + for _, node := range c.RMNStaticConfig.Nodes { + if _, exists := offchainPublicKeys[node.OffchainPublicKey]; exists { + return fmt.Errorf("offchainPublicKey %x is duplicated", node.OffchainPublicKey) + } + offchainPublicKeys[node.OffchainPublicKey] = struct{}{} + } + return nil } type PromoteRMNHomeCandidateConfig struct { HomeChainSelector uint64 + DigestToPromote [32]byte } func (c PromoteRMNHomeCandidateConfig) Validate() error { - if c.HomeChainSelector == 0 { - return fmt.Errorf("HomeChainSelector must be set") + err := deployment.IsValidChainSelector(c.HomeChainSelector) + if err != nil { + return err } return nil @@ -50,15 +75,27 @@ func (c PromoteRMNHomeCandidateConfig) Validate() error { func NewSetRMNHomeCandidateConfigChangeset(e deployment.Environment, config SetRMNHomeCandidateConfig) (deployment.ChangesetOutput, error) { state, err := LoadOnchainState(e) - lggr := e.Logger if err != nil { return deployment.ChangesetOutput{}, fmt.Errorf("failed to load onchain state: %w", err) } + + lggr := e.Logger + + err = config.Validate() + if err != nil { + return deployment.ChangesetOutput{}, err + } + + homeChain := e.Chains[config.HomeChainSelector] + rmnHome := state.Chains[config.HomeChainSelector].RMNHome + if rmnHome == nil { + return deployment.ChangesetOutput{}, fmt.Errorf("RMNHome not found for chain %s", homeChain.String()) + } setCandidateTx, err := rmnHome.SetCandidate(deployment.SimTransactOpts(), config.RMNStaticConfig, config.RMNDynamicConfig, config.DigestToOverride) if err != nil { - lggr.Errorw("Failed to build call data to set RMNHome candidate digest", "err", err) + lggr.Errorw("Failed to build call data to set RMNHome candidate digest", "err", err, "chain", homeChain.String()) return deployment.ChangesetOutput{}, err } @@ -77,27 +114,43 @@ func NewSetRMNHomeCandidateConfigChangeset(e deployment.Environment, config SetR func NewPromoteCandidateConfigChangeset(e deployment.Environment, config PromoteRMNHomeCandidateConfig) (deployment.ChangesetOutput, error) { state, err := LoadOnchainState(e) - lggr := e.Logger + if err != nil { return deployment.ChangesetOutput{}, fmt.Errorf("failed to load onchain state: %w", err) } + + lggr := e.Logger + + err = config.Validate() + if err != nil { + return deployment.ChangesetOutput{}, err + } + + homeChain := e.Chains[config.HomeChainSelector] rmnHome := state.Chains[config.HomeChainSelector].RMNHome + if rmnHome == nil { + return deployment.ChangesetOutput{}, fmt.Errorf("RMNHome not found for chain %s", homeChain.String()) + } currentCandidateDigest, err := rmnHome.GetCandidateDigest(nil) if err != nil { - lggr.Errorw("Failed to get RMNHome candidate digest", "err", err) + lggr.Errorw("Failed to get RMNHome candidate digest", "err", err, "chain", homeChain.String()) return deployment.ChangesetOutput{}, err } currentActiveDigest, err := rmnHome.GetActiveDigest(nil) if err != nil { - lggr.Errorw("Failed to get RMNHome active digest", "err", err) + lggr.Errorw("Failed to get RMNHome active digest", "err", err, "chain", homeChain.String()) return deployment.ChangesetOutput{}, err } + if currentCandidateDigest != config.DigestToPromote { + return deployment.ChangesetOutput{}, fmt.Errorf("candidate digest does not match digest to promote for chain %s", homeChain.String()) + } + promoteCandidateTx, err := rmnHome.PromoteCandidateAndRevokeActive(deployment.SimTransactOpts(), currentCandidateDigest, currentActiveDigest) if err != nil { - lggr.Errorw("Failed to get call data to promote RMNHome candidate digest", "err", err) + lggr.Errorw("Failed to get call data to promote RMNHome candidate digest", "err", err, "chain", homeChain.String()) return deployment.ChangesetOutput{}, err } @@ -110,7 +163,7 @@ func NewPromoteCandidateConfigChangeset(e deployment.Environment, config Promote prop, err := buildProposal(e, op, state, config.HomeChainSelector) if err != nil { - lggr.Errorw("Failed to build proposal", "err", err) + lggr.Errorw("Failed to build proposal", "err", err, "chain", homeChain.String()) return deployment.ChangesetOutput{}, err } @@ -144,7 +197,7 @@ func buildProposerPerChain(e deployment.Environment, state CCIPOnChainState) map return proposerPerChain } -func buildRemoteRemotePerChain(e deployment.Environment, state CCIPOnChainState) map[uint64]*rmn_remote.RMNRemote { +func buildRMNRemotePerChain(e deployment.Environment, state CCIPOnChainState) map[uint64]*rmn_remote.RMNRemote { timelocksPerChain := make(map[uint64]*rmn_remote.RMNRemote) for _, chain := range e.Chains { timelocksPerChain[chain.Selector] = state.Chains[chain.Selector].RMNRemote @@ -152,6 +205,18 @@ func buildRemoteRemotePerChain(e deployment.Environment, state CCIPOnChainState) return timelocksPerChain } +func buildRMNRemoteAddressPerChain(e deployment.Environment, state CCIPOnChainState) map[uint64]common.Address { + rmnRemotePerChain := buildRMNRemotePerChain(e, state) + rmnRemoteAddressPerChain := make(map[uint64]common.Address) + for chain, remote := range rmnRemotePerChain { + if remote == nil { + continue + } + rmnRemoteAddressPerChain[chain] = remote.Address() + } + return rmnRemoteAddressPerChain +} + func buildProposal(e deployment.Environment, op mcms.Operation, state CCIPOnChainState, homeChainSelector uint64) (*timelock.MCMSWithTimelockProposal, error) { batches := []timelock.BatchChainOperation{ { @@ -180,8 +245,19 @@ type SetRMNRemoteConfig struct { } func (c SetRMNRemoteConfig) Validate() error { - if c.HomeChainSelector == 0 { - return fmt.Errorf("HomeChainSelector must be set") + err := deployment.IsValidChainSelector(c.HomeChainSelector) + if err != nil { + return err + } + + for i := 0; i < len(c.Signers)-1; i++ { + if c.Signers[i].NodeIndex >= c.Signers[i+1].NodeIndex { + return fmt.Errorf("signers must be in ascending order of nodeIndex") + } + } + + if len(c.Signers) < 2*int(c.F)+1 { + return fmt.Errorf("signers count must great or equal to %d", 2*c.F+1) } return nil @@ -189,33 +265,58 @@ func (c SetRMNRemoteConfig) Validate() error { func NewSetRMNRemoteConfigChangeset(e deployment.Environment, config SetRMNRemoteConfig) (deployment.ChangesetOutput, error) { state, err := LoadOnchainState(e) - lggr := e.Logger + if err != nil { return deployment.ChangesetOutput{}, fmt.Errorf("failed to load onchain state: %w", err) } + + lggr := e.Logger + + err = config.Validate() + if err != nil { + return deployment.ChangesetOutput{}, err + } + + homeChain := e.Chains[config.HomeChainSelector] rmnHome := state.Chains[config.HomeChainSelector].RMNHome + if rmnHome == nil { + return deployment.ChangesetOutput{}, fmt.Errorf("RMNHome not found for chain %s", homeChain.String()) + } activeConfig, err := rmnHome.GetActiveDigest(nil) if err != nil { - lggr.Errorw("Failed to get RMNHome active digest", "err", err) + lggr.Errorw("Failed to get RMNHome active digest", "err", err, "chain", homeChain.String()) return deployment.ChangesetOutput{}, err } - rmnRemotePerChain := buildRemoteRemotePerChain(e, state) + rmnRemotePerChain := buildRMNRemotePerChain(e, state) batches := make([]timelock.BatchChainOperation, 0) for chain, remote := range rmnRemotePerChain { if remote == nil { continue } - tx, err := remote.SetConfig(deployment.SimTransactOpts(), rmn_remote.RMNRemoteConfig{ + currentVersionConfig, err := remote.GetVersionedConfig(nil) + if err != nil { + lggr.Errorw("Failed to get RMNRemote config", "err", err, "chain", e.Chains[chain].String()) + return deployment.ChangesetOutput{}, err + } + + newConfig := rmn_remote.RMNRemoteConfig{ RmnHomeContractConfigDigest: activeConfig, Signers: config.Signers, F: config.F, - }) + } + + if reflect.DeepEqual(currentVersionConfig.Config, newConfig) { + lggr.Infow("RMNRemote config already up to date", "chain", e.Chains[chain].String()) + continue + } + + tx, err := remote.SetConfig(deployment.SimTransactOpts(), newConfig) if err != nil { - lggr.Errorw("Failed to build call data to set RMNRemote config", "err", err) + lggr.Errorw("Failed to build call data to set RMNRemote config", "err", err, "chain", e.Chains[chain].String()) return deployment.ChangesetOutput{}, err } diff --git a/deployment/ccip/changeset/cs_update_rmn_config_test.go b/deployment/ccip/changeset/cs_update_rmn_config_test.go index 463dd8a52d4..41fa67c2b92 100644 --- a/deployment/ccip/changeset/cs_update_rmn_config_test.go +++ b/deployment/ccip/changeset/cs_update_rmn_config_test.go @@ -6,9 +6,9 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" - mcms "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" "github.com/smartcontractkit/chainlink/deployment/environment/memory" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_home" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_remote" "github.com/smartcontractkit/chainlink/v2/core/logger" ) @@ -24,11 +24,25 @@ func TestUpdateRMNHomeConfig(t *testing.T) { state, err := LoadOnchainState(e.Env) require.NoError(t, err) + contractsByChain := make(map[uint64][]common.Address) + rmnRemoteAddressesByChain := buildRMNRemoteAddressPerChain(e.Env, state) + for chainSelector, rmnRemoteAddress := range rmnRemoteAddressesByChain { + contractsByChain[chainSelector] = []common.Address{rmnRemoteAddress} + } + + contractsByChain[e.HomeChainSel] = append(contractsByChain[e.HomeChainSel], state.Chains[e.HomeChainSel].RMNHome.Address()) + // This is required because RMNHome is initially owner by the deployer - err = transferOwnershipToHomeChainTimelock(t, e, state) - require.NoError(t, err) - err = transferOwnershipForRMNRemote(t, e, state) - require.NoError(t, err) + timelocksPerChain := buildTimelockPerChain(e.Env, state) + _, err = commonchangeset.ApplyChangesets(t, e.Env, timelocksPerChain, []commonchangeset.ChangesetApplication{ + { + Changeset: commonchangeset.WrapChangeSet(commonchangeset.TransferToMCMSWithTimelock), + Config: commonchangeset.TransferToMCMSWithTimelockConfig{ + ContractsByChain: contractsByChain, + MinDelay: 0, + }, + }, + }) rmnHome := state.Chains[e.HomeChainSel].RMNHome @@ -39,18 +53,25 @@ func TestUpdateRMNHomeConfig(t *testing.T) { setRMNHomeCandidateConfig := SetRMNHomeCandidateConfig{ HomeChainSelector: e.HomeChainSel, - RMNStaticConfig: NewTestRMNStaticConfig(), - RMNDynamicConfig: NewTestRMNDynamicConfig(), + RMNStaticConfig: rmn_home.RMNHomeStaticConfig{ + Nodes: []rmn_home.RMNHomeNode{}, + OffchainConfig: []byte(""), + }, + RMNDynamicConfig: rmn_home.RMNHomeDynamicConfig{ + SourceChains: []rmn_home.RMNHomeSourceChain{}, + OffchainConfig: []byte(""), + }, } - timelocksPerChain := buildTimelockPerChain(e.Env, state) - commonchangeset.ApplyChangesets(t, e.Env, timelocksPerChain, []commonchangeset.ChangesetApplication{ + _, err = commonchangeset.ApplyChangesets(t, e.Env, timelocksPerChain, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(NewSetRMNHomeCandidateConfigChangeset), Config: setRMNHomeCandidateConfig, }, }) + require.NoError(t, err) + state, err = LoadOnchainState(e.Env) require.NoError(t, err) @@ -64,6 +85,7 @@ func TestUpdateRMNHomeConfig(t *testing.T) { promoteConfig := PromoteRMNHomeCandidateConfig{ HomeChainSelector: e.HomeChainSel, + DigestToPromote: currentCandidateDigest, } _, err = commonchangeset.ApplyChangesets(t, e.Env, timelocksPerChain, []commonchangeset.ChangesetApplication{ @@ -98,7 +120,7 @@ func TestUpdateRMNHomeConfig(t *testing.T) { }) require.NoError(t, err) - rmnRemotePerChain := buildRemoteRemotePerChain(e.Env, state) + rmnRemotePerChain := buildRMNRemotePerChain(e.Env, state) for _, rmnRemote := range rmnRemotePerChain { remoteConfigSetEvents, err := rmnRemote.FilterConfigSet(nil, nil) require.NoError(t, err) @@ -110,74 +132,3 @@ func TestUpdateRMNHomeConfig(t *testing.T) { require.Equal(t, lastEvent.Config.RmnHomeContractConfigDigest, currentActiveDigest) } } - -func transferOwnershipToHomeChainTimelock(t *testing.T, e DeployedEnv, state CCIPOnChainState) error { - rmnHome := state.Chains[e.HomeChainSel].RMNHome - timelockAddress := state.Chains[e.HomeChainSel].Timelock.Address() - proposerMcm := state.Chains[e.HomeChainSel].ProposerMcm - _, err := commonchangeset.NewTransferOwnershipChangeset(e.Env, commonchangeset.TransferOwnershipConfig{ - Contracts: map[uint64][]commonchangeset.OwnershipTransferrer{ - e.HomeChainSel: {rmnHome}, - }, - OwnersPerChain: map[uint64]common.Address{ - e.HomeChainSel: timelockAddress, - }, - }) - - timelocksPerChain := buildTimelockPerChain(e.Env, state) - - _, err = commonchangeset.ApplyChangesets(t, e.Env, timelocksPerChain, []commonchangeset.ChangesetApplication{ - { - Changeset: commonchangeset.WrapChangeSet(commonchangeset.NewAcceptOwnershipChangeset), - Config: commonchangeset.AcceptOwnershipConfig{ - Contracts: map[uint64][]commonchangeset.OwnershipAcceptor{ - e.HomeChainSel: {rmnHome}, - }, - OwnersPerChain: map[uint64]common.Address{ - e.HomeChainSel: timelockAddress, - }, - ProposerMCMSes: map[uint64]*mcms.ManyChainMultiSig{ - e.HomeChainSel: proposerMcm, - }, - }, - }, - }) - - return err -} - -func transferOwnershipForRMNRemote(t *testing.T, e DeployedEnv, state CCIPOnChainState) error { - rmnRemotePerChain := buildRemoteRemotePerChain(e.Env, state) - timelockAddressPerChain := buildTimelockAddressPerChain(e.Env, state) - timelocksPerChain := buildTimelockPerChain(e.Env, state) - proposers := buildProposerPerChain(e.Env, state) - for chain, rmnRemote := range rmnRemotePerChain { - _, err := commonchangeset.NewTransferOwnershipChangeset(e.Env, commonchangeset.TransferOwnershipConfig{ - Contracts: map[uint64][]commonchangeset.OwnershipTransferrer{ - chain: {rmnRemote}, - }, - OwnersPerChain: timelockAddressPerChain, - }) - if err != nil { - return err - } - - _, err = commonchangeset.ApplyChangesets(t, e.Env, timelocksPerChain, []commonchangeset.ChangesetApplication{ - { - Changeset: commonchangeset.WrapChangeSet(commonchangeset.NewAcceptOwnershipChangeset), - Config: commonchangeset.AcceptOwnershipConfig{ - Contracts: map[uint64][]commonchangeset.OwnershipAcceptor{ - chain: {rmnRemote}, - }, - OwnersPerChain: timelockAddressPerChain, - ProposerMCMSes: proposers, - }, - }, - }) - - if err != nil { - return err - } - } - return nil -} From 1ca99e6d0fcbca79acfb98a260329663f13df809 Mon Sep 17 00:00:00 2001 From: "Simon B.Robert" Date: Mon, 9 Dec 2024 08:58:43 -0500 Subject: [PATCH 4/9] Update deployment/ccip/changeset/cs_update_rmn_config_test.go Co-authored-by: Makram --- deployment/ccip/changeset/cs_update_rmn_config_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/ccip/changeset/cs_update_rmn_config_test.go b/deployment/ccip/changeset/cs_update_rmn_config_test.go index 41fa67c2b92..3cd3a59e4e5 100644 --- a/deployment/ccip/changeset/cs_update_rmn_config_test.go +++ b/deployment/ccip/changeset/cs_update_rmn_config_test.go @@ -32,7 +32,7 @@ func TestUpdateRMNHomeConfig(t *testing.T) { contractsByChain[e.HomeChainSel] = append(contractsByChain[e.HomeChainSel], state.Chains[e.HomeChainSel].RMNHome.Address()) - // This is required because RMNHome is initially owner by the deployer + // This is required because RMNHome is initially owned by the deployer timelocksPerChain := buildTimelockPerChain(e.Env, state) _, err = commonchangeset.ApplyChangesets(t, e.Env, timelocksPerChain, []commonchangeset.ChangesetApplication{ { From 1715638d23ff340650a71b5d6877dfe24d24210a Mon Sep 17 00:00:00 2001 From: "Simon B.Robert" Date: Mon, 9 Dec 2024 09:30:26 -0500 Subject: [PATCH 5/9] Address PR feedback --- .../ccip/changeset/cs_update_rmn_config.go | 156 ++++++++++-------- .../changeset/cs_update_rmn_config_test.go | 13 ++ 2 files changed, 97 insertions(+), 72 deletions(-) diff --git a/deployment/ccip/changeset/cs_update_rmn_config.go b/deployment/ccip/changeset/cs_update_rmn_config.go index 7bf82fbd19b..02720698cae 100644 --- a/deployment/ccip/changeset/cs_update_rmn_config.go +++ b/deployment/ccip/changeset/cs_update_rmn_config.go @@ -23,7 +23,7 @@ type SetRMNHomeCandidateConfig struct { DigestToOverride [32]byte } -func (c SetRMNHomeCandidateConfig) Validate() error { +func (c SetRMNHomeCandidateConfig) Validate(state CCIPOnChainState) error { err := deployment.IsValidChainSelector(c.HomeChainSelector) if err != nil { return err @@ -40,21 +40,37 @@ func (c SetRMNHomeCandidateConfig) Validate() error { return fmt.Errorf("RMNStaticConfig.Nodes must be less than 256") } - peerIds := make(map[[32]byte]struct{}) + var ( + peerIds = make(map[[32]byte]struct{}) + offchainPublicKeys = make(map[[32]byte]struct{}) + ) + for _, node := range c.RMNStaticConfig.Nodes { if _, exists := peerIds[node.PeerId]; exists { return fmt.Errorf("peerId %x is duplicated", node.PeerId) } peerIds[node.PeerId] = struct{}{} - } - offchainPublicKeys := make(map[[32]byte]struct{}) - for _, node := range c.RMNStaticConfig.Nodes { if _, exists := offchainPublicKeys[node.OffchainPublicKey]; exists { return fmt.Errorf("offchainPublicKey %x is duplicated", node.OffchainPublicKey) } offchainPublicKeys[node.OffchainPublicKey] = struct{}{} } + rmnHome := state.Chains[c.HomeChainSelector].RMNHome + + if rmnHome == nil { + return fmt.Errorf("RMNHome not found for chain %d", c.HomeChainSelector) + } + + currentDigest, err := rmnHome.GetCandidateDigest(nil) + + if err != nil { + return fmt.Errorf("failed to get RMNHome candidate digest: %w", err) + } + + if currentDigest != c.DigestToOverride { + return fmt.Errorf("current digest does not match digest to override") + } return nil } @@ -64,12 +80,26 @@ type PromoteRMNHomeCandidateConfig struct { DigestToPromote [32]byte } -func (c PromoteRMNHomeCandidateConfig) Validate() error { +func (c PromoteRMNHomeCandidateConfig) Validate(state CCIPOnChainState) error { err := deployment.IsValidChainSelector(c.HomeChainSelector) if err != nil { return err } + rmnHome := state.Chains[c.HomeChainSelector].RMNHome + if rmnHome == nil { + return fmt.Errorf("RMNHome not found for chain %d", c.HomeChainSelector) + } + + currentCandidateDigest, err := rmnHome.GetCandidateDigest(nil) + if err != nil { + return fmt.Errorf("failed to get RMNHome candidate digest: %w", err) + } + + if currentCandidateDigest != c.DigestToPromote { + return fmt.Errorf("candidate digest does not match digest to promote") + } + return nil } @@ -79,9 +109,7 @@ func NewSetRMNHomeCandidateConfigChangeset(e deployment.Environment, config SetR return deployment.ChangesetOutput{}, fmt.Errorf("failed to load onchain state: %w", err) } - lggr := e.Logger - - err = config.Validate() + err = config.Validate(state) if err != nil { return deployment.ChangesetOutput{}, err } @@ -95,8 +123,7 @@ func NewSetRMNHomeCandidateConfigChangeset(e deployment.Environment, config SetR setCandidateTx, err := rmnHome.SetCandidate(deployment.SimTransactOpts(), config.RMNStaticConfig, config.RMNDynamicConfig, config.DigestToOverride) if err != nil { - lggr.Errorw("Failed to build call data to set RMNHome candidate digest", "err", err, "chain", homeChain.String()) - return deployment.ChangesetOutput{}, err + return deployment.ChangesetOutput{}, fmt.Errorf("build RMNHome set candidate calldata for chain %s: %w", homeChain.String(), err) } op := mcms.Operation{ @@ -105,7 +132,24 @@ func NewSetRMNHomeCandidateConfigChangeset(e deployment.Environment, config SetR Value: big.NewInt(0), } - prop, err := buildProposal(e, op, state, config.HomeChainSelector) + batches := []timelock.BatchChainOperation{ + { + ChainIdentifier: mcms.ChainIdentifier(config.HomeChainSelector), + Batch: []mcms.Operation{op}, + }, + } + + timelocksPerChain := buildTimelockAddressPerChain(e, state) + + proposerMCMSes := buildProposerPerChain(e, state) + + prop, err := proposalutils.BuildProposalFromBatches( + timelocksPerChain, + proposerMCMSes, + batches, + "proposal to set candidate config", + 0, + ) return deployment.ChangesetOutput{ Proposals: []timelock.MCMSWithTimelockProposal{*prop}, @@ -114,14 +158,11 @@ func NewSetRMNHomeCandidateConfigChangeset(e deployment.Environment, config SetR func NewPromoteCandidateConfigChangeset(e deployment.Environment, config PromoteRMNHomeCandidateConfig) (deployment.ChangesetOutput, error) { state, err := LoadOnchainState(e) - if err != nil { return deployment.ChangesetOutput{}, fmt.Errorf("failed to load onchain state: %w", err) } - lggr := e.Logger - - err = config.Validate() + err = config.Validate(state) if err != nil { return deployment.ChangesetOutput{}, err } @@ -134,24 +175,17 @@ func NewPromoteCandidateConfigChangeset(e deployment.Environment, config Promote currentCandidateDigest, err := rmnHome.GetCandidateDigest(nil) if err != nil { - lggr.Errorw("Failed to get RMNHome candidate digest", "err", err, "chain", homeChain.String()) - return deployment.ChangesetOutput{}, err + return deployment.ChangesetOutput{}, fmt.Errorf("failed to get RMNHome candidate digest for chain %s: %w", homeChain.String(), err) } currentActiveDigest, err := rmnHome.GetActiveDigest(nil) if err != nil { - lggr.Errorw("Failed to get RMNHome active digest", "err", err, "chain", homeChain.String()) - return deployment.ChangesetOutput{}, err - } - - if currentCandidateDigest != config.DigestToPromote { - return deployment.ChangesetOutput{}, fmt.Errorf("candidate digest does not match digest to promote for chain %s", homeChain.String()) + return deployment.ChangesetOutput{}, fmt.Errorf("failed to get RMNHome active digest for chain %s: %w", homeChain.String(), err) } promoteCandidateTx, err := rmnHome.PromoteCandidateAndRevokeActive(deployment.SimTransactOpts(), currentCandidateDigest, currentActiveDigest) if err != nil { - lggr.Errorw("Failed to get call data to promote RMNHome candidate digest", "err", err, "chain", homeChain.String()) - return deployment.ChangesetOutput{}, err + return deployment.ChangesetOutput{}, fmt.Errorf("get call data to promote RMNHome candidate digest for chain %s: %w", homeChain.String(), err) } op := mcms.Operation{ @@ -160,11 +194,27 @@ func NewPromoteCandidateConfigChangeset(e deployment.Environment, config Promote Value: big.NewInt(0), } - prop, err := buildProposal(e, op, state, config.HomeChainSelector) + batches := []timelock.BatchChainOperation{ + { + ChainIdentifier: mcms.ChainIdentifier(config.HomeChainSelector), + Batch: []mcms.Operation{op}, + }, + } + + timelocksPerChain := buildTimelockAddressPerChain(e, state) + + proposerMCMSes := buildProposerPerChain(e, state) + + prop, err := proposalutils.BuildProposalFromBatches( + timelocksPerChain, + proposerMCMSes, + batches, + "proposal to promote candidate config", + 0, + ) if err != nil { - lggr.Errorw("Failed to build proposal", "err", err, "chain", homeChain.String()) - return deployment.ChangesetOutput{}, err + return deployment.ChangesetOutput{}, fmt.Errorf("failed to build proposal for chain %s: %w", homeChain.String(), err) } return deployment.ChangesetOutput{ @@ -205,39 +255,6 @@ func buildRMNRemotePerChain(e deployment.Environment, state CCIPOnChainState) ma return timelocksPerChain } -func buildRMNRemoteAddressPerChain(e deployment.Environment, state CCIPOnChainState) map[uint64]common.Address { - rmnRemotePerChain := buildRMNRemotePerChain(e, state) - rmnRemoteAddressPerChain := make(map[uint64]common.Address) - for chain, remote := range rmnRemotePerChain { - if remote == nil { - continue - } - rmnRemoteAddressPerChain[chain] = remote.Address() - } - return rmnRemoteAddressPerChain -} - -func buildProposal(e deployment.Environment, op mcms.Operation, state CCIPOnChainState, homeChainSelector uint64) (*timelock.MCMSWithTimelockProposal, error) { - batches := []timelock.BatchChainOperation{ - { - ChainIdentifier: mcms.ChainIdentifier(homeChainSelector), - Batch: []mcms.Operation{op}, - }, - } - - timelocksPerChain := buildTimelockAddressPerChain(e, state) - - proposerMCMSes := buildProposerPerChain(e, state) - - return proposalutils.BuildProposalFromBatches( - timelocksPerChain, - proposerMCMSes, - batches, - "proposal to promote candidate config", - 0, - ) -} - type SetRMNRemoteConfig struct { HomeChainSelector uint64 Signers []rmn_remote.RMNRemoteSigner @@ -257,7 +274,7 @@ func (c SetRMNRemoteConfig) Validate() error { } if len(c.Signers) < 2*int(c.F)+1 { - return fmt.Errorf("signers count must great or equal to %d", 2*c.F+1) + return fmt.Errorf("signers count must greater than or equal to %d", 2*c.F+1) } return nil @@ -265,7 +282,6 @@ func (c SetRMNRemoteConfig) Validate() error { func NewSetRMNRemoteConfigChangeset(e deployment.Environment, config SetRMNRemoteConfig) (deployment.ChangesetOutput, error) { state, err := LoadOnchainState(e) - if err != nil { return deployment.ChangesetOutput{}, fmt.Errorf("failed to load onchain state: %w", err) } @@ -285,8 +301,7 @@ func NewSetRMNRemoteConfigChangeset(e deployment.Environment, config SetRMNRemot activeConfig, err := rmnHome.GetActiveDigest(nil) if err != nil { - lggr.Errorw("Failed to get RMNHome active digest", "err", err, "chain", homeChain.String()) - return deployment.ChangesetOutput{}, err + return deployment.ChangesetOutput{}, fmt.Errorf("failed to get RMNHome active digest for chain %s: %w", homeChain.String(), err) } rmnRemotePerChain := buildRMNRemotePerChain(e, state) @@ -298,8 +313,7 @@ func NewSetRMNRemoteConfigChangeset(e deployment.Environment, config SetRMNRemot currentVersionConfig, err := remote.GetVersionedConfig(nil) if err != nil { - lggr.Errorw("Failed to get RMNRemote config", "err", err, "chain", e.Chains[chain].String()) - return deployment.ChangesetOutput{}, err + return deployment.ChangesetOutput{}, fmt.Errorf("failed to get RMNRemote config for chain %s: %w", e.Chains[chain].String(), err) } newConfig := rmn_remote.RMNRemoteConfig{ @@ -316,8 +330,7 @@ func NewSetRMNRemoteConfigChangeset(e deployment.Environment, config SetRMNRemot tx, err := remote.SetConfig(deployment.SimTransactOpts(), newConfig) if err != nil { - lggr.Errorw("Failed to build call data to set RMNRemote config", "err", err, "chain", e.Chains[chain].String()) - return deployment.ChangesetOutput{}, err + return deployment.ChangesetOutput{}, fmt.Errorf("build call data to set RMNRemote config for chain %s: %w", e.Chains[chain].String(), err) } op := mcms.Operation{ @@ -347,8 +360,7 @@ func NewSetRMNRemoteConfigChangeset(e deployment.Environment, config SetRMNRemot ) if err != nil { - lggr.Errorw("Failed to build proposal", "err", err) - return deployment.ChangesetOutput{}, err + return deployment.ChangesetOutput{}, fmt.Errorf("failed to build proposal for chain %s: %w", homeChain.String(), err) } return deployment.ChangesetOutput{ diff --git a/deployment/ccip/changeset/cs_update_rmn_config_test.go b/deployment/ccip/changeset/cs_update_rmn_config_test.go index 3cd3a59e4e5..a59c72f3ca0 100644 --- a/deployment/ccip/changeset/cs_update_rmn_config_test.go +++ b/deployment/ccip/changeset/cs_update_rmn_config_test.go @@ -6,6 +6,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink/deployment" commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" "github.com/smartcontractkit/chainlink/deployment/environment/memory" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_home" @@ -132,3 +133,15 @@ func TestUpdateRMNHomeConfig(t *testing.T) { require.Equal(t, lastEvent.Config.RmnHomeContractConfigDigest, currentActiveDigest) } } + +func buildRMNRemoteAddressPerChain(e deployment.Environment, state CCIPOnChainState) map[uint64]common.Address { + rmnRemotePerChain := buildRMNRemotePerChain(e, state) + rmnRemoteAddressPerChain := make(map[uint64]common.Address) + for chain, remote := range rmnRemotePerChain { + if remote == nil { + continue + } + rmnRemoteAddressPerChain[chain] = remote.Address() + } + return rmnRemoteAddressPerChain +} From a68f20b5c7491e329dd067864775185281761722 Mon Sep 17 00:00:00 2001 From: "Simon B.Robert" Date: Mon, 9 Dec 2024 10:11:52 -0500 Subject: [PATCH 6/9] Address some more feedback on the PR --- deployment/ccip/changeset/cs_update_rmn_config.go | 9 ++++++--- deployment/ccip/changeset/cs_update_rmn_config_test.go | 5 ++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/deployment/ccip/changeset/cs_update_rmn_config.go b/deployment/ccip/changeset/cs_update_rmn_config.go index 02720698cae..f7b13fddc2d 100644 --- a/deployment/ccip/changeset/cs_update_rmn_config.go +++ b/deployment/ccip/changeset/cs_update_rmn_config.go @@ -4,6 +4,7 @@ import ( "fmt" "math/big" "reflect" + "time" "github.com/ethereum/go-ethereum/common" "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" @@ -21,6 +22,7 @@ type SetRMNHomeCandidateConfig struct { RMNStaticConfig rmn_home.RMNHomeStaticConfig RMNDynamicConfig rmn_home.RMNHomeDynamicConfig DigestToOverride [32]byte + MinDelay time.Duration } func (c SetRMNHomeCandidateConfig) Validate(state CCIPOnChainState) error { @@ -63,13 +65,12 @@ func (c SetRMNHomeCandidateConfig) Validate(state CCIPOnChainState) error { } currentDigest, err := rmnHome.GetCandidateDigest(nil) - if err != nil { return fmt.Errorf("failed to get RMNHome candidate digest: %w", err) } if currentDigest != c.DigestToOverride { - return fmt.Errorf("current digest does not match digest to override") + return fmt.Errorf("current digest (%x) does not match digest to override (%x)", currentDigest[:], c.DigestToOverride[:]) } return nil @@ -78,6 +79,7 @@ func (c SetRMNHomeCandidateConfig) Validate(state CCIPOnChainState) error { type PromoteRMNHomeCandidateConfig struct { HomeChainSelector uint64 DigestToPromote [32]byte + MinDelay time.Duration } func (c PromoteRMNHomeCandidateConfig) Validate(state CCIPOnChainState) error { @@ -97,7 +99,7 @@ func (c PromoteRMNHomeCandidateConfig) Validate(state CCIPOnChainState) error { } if currentCandidateDigest != c.DigestToPromote { - return fmt.Errorf("candidate digest does not match digest to promote") + return fmt.Errorf("current digest (%x) does not match digest to promote (%x)", currentCandidateDigest[:], c.DigestToPromote[:]) } return nil @@ -259,6 +261,7 @@ type SetRMNRemoteConfig struct { HomeChainSelector uint64 Signers []rmn_remote.RMNRemoteSigner F uint64 + MinDelay time.Duration } func (c SetRMNRemoteConfig) Validate() error { diff --git a/deployment/ccip/changeset/cs_update_rmn_config_test.go b/deployment/ccip/changeset/cs_update_rmn_config_test.go index a59c72f3ca0..17ad8f8c370 100644 --- a/deployment/ccip/changeset/cs_update_rmn_config_test.go +++ b/deployment/ccip/changeset/cs_update_rmn_config_test.go @@ -62,6 +62,7 @@ func TestUpdateRMNHomeConfig(t *testing.T) { SourceChains: []rmn_home.RMNHomeSourceChain{}, OffchainConfig: []byte(""), }, + MinDelay: 0, } _, err = commonchangeset.ApplyChangesets(t, e.Env, timelocksPerChain, []commonchangeset.ChangesetApplication{ @@ -87,6 +88,7 @@ func TestUpdateRMNHomeConfig(t *testing.T) { promoteConfig := PromoteRMNHomeCandidateConfig{ HomeChainSelector: e.HomeChainSel, DigestToPromote: currentCandidateDigest, + MinDelay: 0, } _, err = commonchangeset.ApplyChangesets(t, e.Env, timelocksPerChain, []commonchangeset.ChangesetApplication{ @@ -110,7 +112,8 @@ func TestUpdateRMNHomeConfig(t *testing.T) { NodeIndex: 0, }, }, - F: 0, + F: 0, + MinDelay: 0, } _, err = commonchangeset.ApplyChangesets(t, e.Env, timelocksPerChain, []commonchangeset.ChangesetApplication{ From 704db476ab8fd523293e62329a0e9907187eaa36 Mon Sep 17 00:00:00 2001 From: "Simon B.Robert" Date: Mon, 9 Dec 2024 12:53:42 -0500 Subject: [PATCH 7/9] Make MCMS optional for RMN changeset --- .../ccip/changeset/cs_update_rmn_config.go | 68 ++++++++++++++++--- .../changeset/cs_update_rmn_config_test.go | 62 +++++++++++++---- 2 files changed, 107 insertions(+), 23 deletions(-) diff --git a/deployment/ccip/changeset/cs_update_rmn_config.go b/deployment/ccip/changeset/cs_update_rmn_config.go index f7b13fddc2d..565a5f3b9ca 100644 --- a/deployment/ccip/changeset/cs_update_rmn_config.go +++ b/deployment/ccip/changeset/cs_update_rmn_config.go @@ -6,6 +6,7 @@ import ( "reflect" "time" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" mcmsWrappers "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" @@ -17,12 +18,24 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_remote" ) +func getDeployer(e deployment.Environment, chain uint64, mcmConfig *MCMSConfig) *bind.TransactOpts { + if mcmConfig == nil { + return e.Chains[chain].DeployerKey + } + + return deployment.SimTransactOpts() +} + +type MCMSConfig struct { + MinDelay time.Duration +} + type SetRMNHomeCandidateConfig struct { HomeChainSelector uint64 RMNStaticConfig rmn_home.RMNHomeStaticConfig RMNDynamicConfig rmn_home.RMNHomeDynamicConfig DigestToOverride [32]byte - MinDelay time.Duration + MCMSConfig *MCMSConfig } func (c SetRMNHomeCandidateConfig) Validate(state CCIPOnChainState) error { @@ -79,7 +92,7 @@ func (c SetRMNHomeCandidateConfig) Validate(state CCIPOnChainState) error { type PromoteRMNHomeCandidateConfig struct { HomeChainSelector uint64 DigestToPromote [32]byte - MinDelay time.Duration + MCMSConfig *MCMSConfig } func (c PromoteRMNHomeCandidateConfig) Validate(state CCIPOnChainState) error { @@ -123,11 +136,23 @@ func NewSetRMNHomeCandidateConfigChangeset(e deployment.Environment, config SetR return deployment.ChangesetOutput{}, fmt.Errorf("RMNHome not found for chain %s", homeChain.String()) } - setCandidateTx, err := rmnHome.SetCandidate(deployment.SimTransactOpts(), config.RMNStaticConfig, config.RMNDynamicConfig, config.DigestToOverride) + deployer := getDeployer(e, config.HomeChainSelector, config.MCMSConfig) + setCandidateTx, err := rmnHome.SetCandidate(deployer, config.RMNStaticConfig, config.RMNDynamicConfig, config.DigestToOverride) if err != nil { return deployment.ChangesetOutput{}, fmt.Errorf("build RMNHome set candidate calldata for chain %s: %w", homeChain.String(), err) } + if config.MCMSConfig == nil { + chain := e.Chains[config.HomeChainSelector] + _, err := chain.Confirm(setCandidateTx) + + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to confirm tx for chain %s: %w", homeChain.String(), err) + } + + return deployment.ChangesetOutput{}, nil + } + op := mcms.Operation{ To: rmnHome.Address(), Data: setCandidateTx.Data(), @@ -150,7 +175,7 @@ func NewSetRMNHomeCandidateConfigChangeset(e deployment.Environment, config SetR proposerMCMSes, batches, "proposal to set candidate config", - 0, + config.MCMSConfig.MinDelay, ) return deployment.ChangesetOutput{ @@ -185,11 +210,23 @@ func NewPromoteCandidateConfigChangeset(e deployment.Environment, config Promote return deployment.ChangesetOutput{}, fmt.Errorf("failed to get RMNHome active digest for chain %s: %w", homeChain.String(), err) } - promoteCandidateTx, err := rmnHome.PromoteCandidateAndRevokeActive(deployment.SimTransactOpts(), currentCandidateDigest, currentActiveDigest) + deployer := getDeployer(e, config.HomeChainSelector, config.MCMSConfig) + promoteCandidateTx, err := rmnHome.PromoteCandidateAndRevokeActive(deployer, currentCandidateDigest, currentActiveDigest) if err != nil { return deployment.ChangesetOutput{}, fmt.Errorf("get call data to promote RMNHome candidate digest for chain %s: %w", homeChain.String(), err) } + if config.MCMSConfig == nil { + chain := e.Chains[config.HomeChainSelector] + _, err := chain.Confirm(promoteCandidateTx) + + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to confirm tx for chain %s: %w", homeChain.String(), err) + } + + return deployment.ChangesetOutput{}, nil + } + op := mcms.Operation{ To: rmnHome.Address(), Data: promoteCandidateTx.Data(), @@ -212,7 +249,7 @@ func NewPromoteCandidateConfigChangeset(e deployment.Environment, config Promote proposerMCMSes, batches, "proposal to promote candidate config", - 0, + config.MCMSConfig.MinDelay, ) if err != nil { @@ -261,7 +298,7 @@ type SetRMNRemoteConfig struct { HomeChainSelector uint64 Signers []rmn_remote.RMNRemoteSigner F uint64 - MinDelay time.Duration + MCMSConfig *MCMSConfig } func (c SetRMNRemoteConfig) Validate() error { @@ -330,12 +367,21 @@ func NewSetRMNRemoteConfigChangeset(e deployment.Environment, config SetRMNRemot continue } - tx, err := remote.SetConfig(deployment.SimTransactOpts(), newConfig) + deployer := getDeployer(e, chain, config.MCMSConfig) + tx, err := remote.SetConfig(deployer, newConfig) if err != nil { return deployment.ChangesetOutput{}, fmt.Errorf("build call data to set RMNRemote config for chain %s: %w", e.Chains[chain].String(), err) } + if config.MCMSConfig == nil { + _, err := e.Chains[chain].Confirm(tx) + + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to confirm tx for chain %s: %w", e.Chains[chain].String(), err) + } + } + op := mcms.Operation{ To: remote.Address(), Data: tx.Data(), @@ -350,6 +396,10 @@ func NewSetRMNRemoteConfigChangeset(e deployment.Environment, config SetRMNRemot batches = append(batches, batch) } + if config.MCMSConfig == nil { + return deployment.ChangesetOutput{}, nil + } + timelocksPerChain := buildTimelockAddressPerChain(e, state) proposerMCMSes := buildProposerPerChain(e, state) @@ -359,7 +409,7 @@ func NewSetRMNRemoteConfigChangeset(e deployment.Environment, config SetRMNRemot proposerMCMSes, batches, "proposal to promote candidate config", - 0, + config.MCMSConfig.MinDelay, ) if err != nil { diff --git a/deployment/ccip/changeset/cs_update_rmn_config_test.go b/deployment/ccip/changeset/cs_update_rmn_config_test.go index 17ad8f8c370..deae3e2e771 100644 --- a/deployment/ccip/changeset/cs_update_rmn_config_test.go +++ b/deployment/ccip/changeset/cs_update_rmn_config_test.go @@ -14,8 +14,32 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/logger" ) -func TestUpdateRMNHomeConfig(t *testing.T) { +type updateRMNConfigTestCase struct { + useMCMS bool + name string +} + +func TestUpdateRMNConfig(t *testing.T) { t.Parallel() + testCases := []updateRMNConfigTestCase{ + { + useMCMS: true, + name: "with MCMS", + }, + { + useMCMS: false, + name: "without MCMS", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + updateRMNConfig(t, tc) + }) + } +} + +func updateRMNConfig(t *testing.T, tc updateRMNConfigTestCase) { e := NewMemoryEnvironmentWithJobsAndContracts(t, logger.TestLogger(t), memory.MemoryEnvironmentConfig{ Chains: 2, Nodes: 4, @@ -33,17 +57,19 @@ func TestUpdateRMNHomeConfig(t *testing.T) { contractsByChain[e.HomeChainSel] = append(contractsByChain[e.HomeChainSel], state.Chains[e.HomeChainSel].RMNHome.Address()) - // This is required because RMNHome is initially owned by the deployer timelocksPerChain := buildTimelockPerChain(e.Env, state) - _, err = commonchangeset.ApplyChangesets(t, e.Env, timelocksPerChain, []commonchangeset.ChangesetApplication{ - { - Changeset: commonchangeset.WrapChangeSet(commonchangeset.TransferToMCMSWithTimelock), - Config: commonchangeset.TransferToMCMSWithTimelockConfig{ - ContractsByChain: contractsByChain, - MinDelay: 0, + if tc.useMCMS { + // This is required because RMNHome is initially owned by the deployer + _, err = commonchangeset.ApplyChangesets(t, e.Env, timelocksPerChain, []commonchangeset.ChangesetApplication{ + { + Changeset: commonchangeset.WrapChangeSet(commonchangeset.TransferToMCMSWithTimelock), + Config: commonchangeset.TransferToMCMSWithTimelockConfig{ + ContractsByChain: contractsByChain, + MinDelay: 0, + }, }, - }, - }) + }) + } rmnHome := state.Chains[e.HomeChainSel].RMNHome @@ -52,6 +78,14 @@ func TestUpdateRMNHomeConfig(t *testing.T) { previousActiveDigest, err := rmnHome.GetActiveDigest(nil) require.NoError(t, err) + var mcmsConfig *MCMSConfig = nil + + if tc.useMCMS { + mcmsConfig = &MCMSConfig{ + MinDelay: 0, + } + } + setRMNHomeCandidateConfig := SetRMNHomeCandidateConfig{ HomeChainSelector: e.HomeChainSel, RMNStaticConfig: rmn_home.RMNHomeStaticConfig{ @@ -62,7 +96,7 @@ func TestUpdateRMNHomeConfig(t *testing.T) { SourceChains: []rmn_home.RMNHomeSourceChain{}, OffchainConfig: []byte(""), }, - MinDelay: 0, + MCMSConfig: mcmsConfig, } _, err = commonchangeset.ApplyChangesets(t, e.Env, timelocksPerChain, []commonchangeset.ChangesetApplication{ @@ -88,7 +122,7 @@ func TestUpdateRMNHomeConfig(t *testing.T) { promoteConfig := PromoteRMNHomeCandidateConfig{ HomeChainSelector: e.HomeChainSel, DigestToPromote: currentCandidateDigest, - MinDelay: 0, + MCMSConfig: mcmsConfig, } _, err = commonchangeset.ApplyChangesets(t, e.Env, timelocksPerChain, []commonchangeset.ChangesetApplication{ @@ -112,8 +146,8 @@ func TestUpdateRMNHomeConfig(t *testing.T) { NodeIndex: 0, }, }, - F: 0, - MinDelay: 0, + F: 0, + MCMSConfig: mcmsConfig, } _, err = commonchangeset.ApplyChangesets(t, e.Env, timelocksPerChain, []commonchangeset.ChangesetApplication{ From 62f532df5be95bfbe93c6de516c89484e1b149f8 Mon Sep 17 00:00:00 2001 From: "Simon B.Robert" Date: Mon, 9 Dec 2024 13:59:27 -0500 Subject: [PATCH 8/9] Update deployment/ccip/changeset/cs_update_rmn_config.go Co-authored-by: Anindita Ghosh <88458927+AnieeG@users.noreply.github.com> --- deployment/ccip/changeset/cs_update_rmn_config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/ccip/changeset/cs_update_rmn_config.go b/deployment/ccip/changeset/cs_update_rmn_config.go index 565a5f3b9ca..6545524f093 100644 --- a/deployment/ccip/changeset/cs_update_rmn_config.go +++ b/deployment/ccip/changeset/cs_update_rmn_config.go @@ -147,7 +147,7 @@ func NewSetRMNHomeCandidateConfigChangeset(e deployment.Environment, config SetR _, err := chain.Confirm(setCandidateTx) if err != nil { - return deployment.ChangesetOutput{}, fmt.Errorf("failed to confirm tx for chain %s: %w", homeChain.String(), err) + return deployment.ChangesetOutput{}, fmt.Errorf("failed to confirm tx for chain %s: %w", homeChain.String(), deployment.maybeDataError(err)) } return deployment.ChangesetOutput{}, nil From 4061ae9065df768f9b535d4ccfec7163178a48e0 Mon Sep 17 00:00:00 2001 From: "Simon B.Robert" Date: Mon, 9 Dec 2024 14:19:23 -0500 Subject: [PATCH 9/9] Address PR comments --- .../ccip/changeset/cs_update_rmn_config.go | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/deployment/ccip/changeset/cs_update_rmn_config.go b/deployment/ccip/changeset/cs_update_rmn_config.go index 6545524f093..7e4d09af20f 100644 --- a/deployment/ccip/changeset/cs_update_rmn_config.go +++ b/deployment/ccip/changeset/cs_update_rmn_config.go @@ -118,6 +118,11 @@ func (c PromoteRMNHomeCandidateConfig) Validate(state CCIPOnChainState) error { return nil } +// NewSetRMNHomeCandidateConfigChangeset creates a changeset to set the RMNHome candidate config +// DigestToOverride is the digest of the current candidate config that the new config will override +// StaticConfig contains the list of nodes with their peerIDs (found in their rageproxy keystore) and offchain public keys (found in the RMN keystore) +// DynamicConfig contains the list of source chains with their chain selectors, f value and the bitmap of the nodes that are oberver for each source chain +// The bitmap is a 256 bit array where each bit represents a node. If the bit matching the index of the node in the static config is set it means that the node is an observer func NewSetRMNHomeCandidateConfigChangeset(e deployment.Environment, config SetRMNHomeCandidateConfig) (deployment.ChangesetOutput, error) { state, err := LoadOnchainState(e) if err != nil { @@ -129,7 +134,10 @@ func NewSetRMNHomeCandidateConfigChangeset(e deployment.Environment, config SetR return deployment.ChangesetOutput{}, err } - homeChain := e.Chains[config.HomeChainSelector] + homeChain, ok := e.Chains[config.HomeChainSelector] + if !ok { + return deployment.ChangesetOutput{}, fmt.Errorf("chain %d not found", config.HomeChainSelector) + } rmnHome := state.Chains[config.HomeChainSelector].RMNHome if rmnHome == nil { @@ -147,7 +155,7 @@ func NewSetRMNHomeCandidateConfigChangeset(e deployment.Environment, config SetR _, err := chain.Confirm(setCandidateTx) if err != nil { - return deployment.ChangesetOutput{}, fmt.Errorf("failed to confirm tx for chain %s: %w", homeChain.String(), deployment.maybeDataError(err)) + return deployment.ChangesetOutput{}, fmt.Errorf("failed to confirm tx for chain %s: %w", homeChain.String(), deployment.MaybeDataErr(err)) } return deployment.ChangesetOutput{}, nil @@ -194,7 +202,12 @@ func NewPromoteCandidateConfigChangeset(e deployment.Environment, config Promote return deployment.ChangesetOutput{}, err } - homeChain := e.Chains[config.HomeChainSelector] + homeChain, ok := e.Chains[config.HomeChainSelector] + + if !ok { + return deployment.ChangesetOutput{}, fmt.Errorf("chain %d not found", config.HomeChainSelector) + } + rmnHome := state.Chains[config.HomeChainSelector].RMNHome if rmnHome == nil { return deployment.ChangesetOutput{}, fmt.Errorf("RMNHome not found for chain %s", homeChain.String()) @@ -221,7 +234,7 @@ func NewPromoteCandidateConfigChangeset(e deployment.Environment, config Promote _, err := chain.Confirm(promoteCandidateTx) if err != nil { - return deployment.ChangesetOutput{}, fmt.Errorf("failed to confirm tx for chain %s: %w", homeChain.String(), err) + return deployment.ChangesetOutput{}, fmt.Errorf("failed to confirm tx for chain %s: %w", homeChain.String(), deployment.MaybeDataErr(err)) } return deployment.ChangesetOutput{}, nil @@ -333,7 +346,12 @@ func NewSetRMNRemoteConfigChangeset(e deployment.Environment, config SetRMNRemot return deployment.ChangesetOutput{}, err } - homeChain := e.Chains[config.HomeChainSelector] + homeChain, ok := e.Chains[config.HomeChainSelector] + + if !ok { + return deployment.ChangesetOutput{}, fmt.Errorf("chain %d not found", config.HomeChainSelector) + } + rmnHome := state.Chains[config.HomeChainSelector].RMNHome if rmnHome == nil { return deployment.ChangesetOutput{}, fmt.Errorf("RMNHome not found for chain %s", homeChain.String()) @@ -378,7 +396,7 @@ func NewSetRMNRemoteConfigChangeset(e deployment.Environment, config SetRMNRemot _, err := e.Chains[chain].Confirm(tx) if err != nil { - return deployment.ChangesetOutput{}, fmt.Errorf("failed to confirm tx for chain %s: %w", e.Chains[chain].String(), err) + return deployment.ChangesetOutput{}, fmt.Errorf("failed to confirm tx for chain %s: %w", e.Chains[chain].String(), deployment.MaybeDataErr(err)) } }