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

CCIP- 4158 deploy home changeset #15143

Merged
merged 20 commits into from
Nov 9, 2024
Merged
Show file tree
Hide file tree
Changes from 10 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
75 changes: 73 additions & 2 deletions deployment/ccip/changeset/cap_reg.go
Original file line number Diff line number Diff line change
@@ -1,30 +1,101 @@
package changeset

import (
"fmt"

"github.com/ethereum/go-ethereum/common"
"github.com/pkg/errors"
"github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock"

"github.com/smartcontractkit/chainlink/deployment"
ccipdeployment "github.com/smartcontractkit/chainlink/deployment/ccip"
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_home"
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry"
)

var _ deployment.ChangeSet = DeployCapReg

// DeployCapReg is a separate changeset because cap reg is an env var for CL nodes.
func DeployCapReg(env deployment.Environment, config interface{}) (deployment.ChangesetOutput, error) {
AnieeG marked this conversation as resolved.
Show resolved Hide resolved
homeChainSel, ok := config.(uint64)
cfg, ok := config.(DeployHomeChainConfig)
if !ok {
return deployment.ChangesetOutput{}, deployment.ErrInvalidConfig
}
err := cfg.Validate()
if err != nil {
return deployment.ChangesetOutput{}, errors.Wrapf(deployment.ErrInvalidConfig, "%v", err)
}
homeChainSel := cfg.HomeChainSel
// Note we also deploy the cap reg.
ab := deployment.NewMemoryAddressBook()
_, err := ccipdeployment.DeployCapReg(env.Logger, ab, env.Chains[homeChainSel])
capReg, err := ccipdeployment.DeployCapReg(env.Logger, ab, env.Chains[homeChainSel], cfg.RMNStaticConfig, cfg.RMNDynamicConfig, cfg.NodeOperators)
if err != nil {
env.Logger.Errorw("Failed to deploy cap reg", "err", err, "addresses", ab)
return deployment.ChangesetOutput{}, err
}
nopIdByName, err := ccipdeployment.GetNodeOperatorIDMap(capReg.Contract, 50) // assuming 50 is sufficient for all node operators
AnieeG marked this conversation as resolved.
Show resolved Hide resolved
// validate all node operators have nopIds
for _, nop := range cfg.NodeOperators {
if _, ok := nopIdByName[nop.Name]; !ok {
return deployment.ChangesetOutput{}, fmt.Errorf("node operator %s does not have a nopId", nop.Name)
}
}
// Adds initial set of nodes to CR, who all have the CCIP capability
p2pIDsByNodeOpId := make(map[uint32][][32]byte)
for nopName, p2pId := range cfg.NodeP2PIDsPerNodeOpAdmin {
nopId, ok := nopIdByName[nopName]
if !ok {
return deployment.ChangesetOutput{}, fmt.Errorf("node operator %s does not have a nopId", nopName)
}
p2pIDsByNodeOpId[nopId] = p2pId
}
if err := ccipdeployment.AddNodes(
env.Logger,
capReg.Contract,
env.Chains[homeChainSel],
p2pIDsByNodeOpId,
); err != nil {
return deployment.ChangesetOutput{}, err
}
return deployment.ChangesetOutput{
Proposals: []timelock.MCMSWithTimelockProposal{},
AddressBook: ab,
JobSpecs: nil,
}, nil
}

type DeployHomeChainConfig struct {
HomeChainSel uint64
RMNStaticConfig rmn_home.RMNHomeStaticConfig
RMNDynamicConfig rmn_home.RMNHomeDynamicConfig
NodeOperators []capabilities_registry.CapabilitiesRegistryNodeOperator
NodeP2PIDsPerNodeOpAdmin map[string][][32]byte
}

func (c DeployHomeChainConfig) Validate() error {
if c.HomeChainSel == 0 {
return fmt.Errorf("home chain selector must be set")
}
if c.RMNDynamicConfig.OffchainConfig == nil {
return fmt.Errorf("offchain config for RMNHomeDynamicConfig must be set")
}
if c.RMNStaticConfig.OffchainConfig == nil {
return fmt.Errorf("offchain config for RMNHomeStaticConfig must be set")
}
if c.NodeOperators == nil || len(c.NodeOperators) == 0 {
return fmt.Errorf("node operators must be set")
}
for _, nop := range c.NodeOperators {
if nop.Admin == (common.Address{}) {
return fmt.Errorf("node operator admin address must be set")
}
if nop.Name == "" {
return fmt.Errorf("node operator name must be set")
}
if c.NodeP2PIDsPerNodeOpAdmin[nop.Name] == nil || len(c.NodeP2PIDsPerNodeOpAdmin[nop.Name]) == 0 {
return fmt.Errorf("node operator %s must have node p2p ids provided", nop.Name)
}
}

return nil
}
63 changes: 63 additions & 0 deletions deployment/ccip/changeset/cap_reg_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package changeset

import (
"testing"

chainsel "github.com/smartcontractkit/chain-selectors"
"github.com/stretchr/testify/require"
"go.uber.org/zap/zapcore"

"github.com/smartcontractkit/chainlink/deployment"
ccdeploy "github.com/smartcontractkit/chainlink/deployment/ccip"
"github.com/smartcontractkit/chainlink/deployment/common/view/v1_0"
"github.com/smartcontractkit/chainlink/deployment/environment/memory"
"github.com/smartcontractkit/chainlink/v2/core/logger"
)

func TestDeployCapReg(t *testing.T) {
lggr := logger.TestLogger(t)
e := memory.NewMemoryEnvironment(t, lggr, zapcore.InfoLevel, memory.MemoryEnvironmentConfig{
Bootstraps: 1,
Chains: 2,
Nodes: 4,
})
homeChainSel := e.AllChainSelectors()[0]
nodes, err := deployment.NodeInfo(e.NodeIDs, e.Offchain)
require.NoError(t, err)
p2pIds := nodes.NonBootstraps().PeerIDs()
homeChainCfg := DeployHomeChainConfig{
HomeChainSel: homeChainSel,
RMNStaticConfig: ccdeploy.NewTestRMNStaticConfig(),
RMNDynamicConfig: ccdeploy.NewTestRMNDynamicConfig(),
NodeOperators: ccdeploy.NewTestNodeOperator(e.Chains[homeChainSel].DeployerKey.From),
NodeP2PIDsPerNodeOpAdmin: map[string][][32]byte{
"NodeOperator": p2pIds,
},
}
output, err := DeployCapReg(e, homeChainCfg)
require.NoError(t, err)
require.NoError(t, e.ExistingAddresses.Merge(output.AddressBook))
state, err := ccdeploy.LoadOnchainState(e)
require.NoError(t, err)
require.NotNil(t, state.Chains[homeChainSel].CapabilityRegistry)
require.NotNil(t, state.Chains[homeChainSel].CCIPHome)
require.NotNil(t, state.Chains[homeChainSel].RMNHome)
snap, err := state.View([]uint64{homeChainSel})
require.NoError(t, err)
chainid, err := chainsel.ChainIdFromSelector(homeChainSel)
require.NoError(t, err)
chainName, err := chainsel.NameFromChainId(chainid)
require.NoError(t, err)
_, ok := snap[chainName]
require.True(t, ok)
capRegSnap, ok := snap[chainName].CapabilityRegistry[state.Chains[homeChainSel].CapabilityRegistry.Address().String()]
require.True(t, ok)
require.NotNil(t, capRegSnap)
require.Equal(t, capRegSnap.Nops, []v1_0.NopView{
{
Admin: e.Chains[homeChainSel].DeployerKey.From,
Name: "NodeOperator",
},
})
require.Len(t, capRegSnap.Nodes, len(p2pIds))
}
6 changes: 5 additions & 1 deletion deployment/ccip/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
owner_helpers "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers"

"github.com/smartcontractkit/chainlink-common/pkg/logger"

"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/fee_quoter"
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/registry_module_owner_custom"
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_home"
Expand Down Expand Up @@ -188,11 +189,14 @@ func DeployCCIPContracts(e deployment.Environment, ab deployment.AddressBook, c
}

// Signal to CR that our nodes support CCIP capability.
p2pIdsByNop := map[uint32][][32]byte{
NodeOperatorID: nodes.NonBootstraps().PeerIDs(),
}
if err := AddNodes(
AnieeG marked this conversation as resolved.
Show resolved Hide resolved
e.Logger,
capReg,
e.Chains[c.HomeChainSel],
nodes.NonBootstraps().PeerIDs(),
p2pIdsByNop,
); err != nil {
return err
}
Expand Down
89 changes: 54 additions & 35 deletions deployment/ccip/deploy_home_chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,14 @@ func MustABIEncode(abiString string, args ...interface{}) []byte {
return encoded
}

func DeployCapReg(lggr logger.Logger, ab deployment.AddressBook, chain deployment.Chain) (*ContractDeploy[*capabilities_registry.CapabilitiesRegistry], error) {
func DeployCapReg(
lggr logger.Logger,
ab deployment.AddressBook,
chain deployment.Chain,
rmnHomeStatic rmn_home.RMNHomeStaticConfig,
rmnHomeDynamic rmn_home.RMNHomeDynamicConfig,
nodeOps []capabilities_registry.CapabilitiesRegistryNodeOperator,
) (*ContractDeploy[*capabilities_registry.CapabilitiesRegistry], error) {
capReg, err := deployContract(lggr, chain, ab,
func(chain deployment.Chain) ContractDeploy[*capabilities_registry.CapabilitiesRegistry] {
crAddr, tx, cr, err2 := capabilities_registry.DeployCapabilitiesRegistry(
Expand Down Expand Up @@ -138,14 +145,8 @@ func DeployCapReg(lggr logger.Logger, ab deployment.AddressBook, chain deploymen
}
lggr.Infow("deployed RMNHome", "addr", rmnHome.Address)

// TODO: properly configure RMNHome
tx, err := rmnHome.Contract.SetCandidate(chain.DeployerKey, rmn_home.RMNHomeStaticConfig{
Nodes: []rmn_home.RMNHomeNode{},
OffchainConfig: []byte("static config"),
}, rmn_home.RMNHomeDynamicConfig{
SourceChains: []rmn_home.RMNHomeSourceChain{},
OffchainConfig: []byte("dynamic config"),
}, [32]byte{})
// considering the RMNHome is recently deployed, there is no digest to overwrite
tx, err := rmnHome.Contract.SetCandidate(chain.DeployerKey, rmnHomeStatic, rmnHomeDynamic, [32]byte{})
if _, err := deployment.ConfirmIfNoError(chain, tx, err); err != nil {
lggr.Errorw("Failed to set candidate on RMNHome", "err", err)
return nil, err
Expand Down Expand Up @@ -189,20 +190,36 @@ func DeployCapReg(lggr logger.Logger, ab deployment.AddressBook, chain deploymen
lggr.Errorw("Failed to add capabilities", "err", err)
return nil, err
}
// TODO: Just one for testing.
tx, err = capReg.Contract.AddNodeOperators(chain.DeployerKey, []capabilities_registry.CapabilitiesRegistryNodeOperator{
{
Admin: chain.DeployerKey.From,
Name: "NodeOperator",
},
})

tx, err = capReg.Contract.AddNodeOperators(chain.DeployerKey, nodeOps)
if _, err := deployment.ConfirmIfNoError(chain, tx, err); err != nil {
lggr.Errorw("Failed to add node operators", "err", err)
return nil, err
}
return capReg, nil
}

// GetNodeOperatorIDMap returns a map of node operator names to their IDs
// If maxNops is greater than the number of node operators, it will return all node operators
func GetNodeOperatorIDMap(capReg *capabilities_registry.CapabilitiesRegistry, maxNops uint32) (map[string]uint32, error) {
nopIdByName := make(map[string]uint32)
operators, err := capReg.GetNodeOperators(nil)
if err != nil {
return nil, err
}
if len(operators) < int(maxNops) {
maxNops = uint32(len(operators))
}
for i := uint32(1); i <= maxNops; i++ {
operator, err := capReg.GetNodeOperator(nil, i)
if err != nil {
return nil, err
}
nopIdByName[operator.Name] = i
}
return nopIdByName, nil
}

func isEqualCapabilitiesRegistryNodeParams(a, b capabilities_registry.CapabilitiesRegistryNodeParams) (bool, error) {
aBytes, err := json.Marshal(a)
if err != nil {
Expand All @@ -219,7 +236,7 @@ func AddNodes(
lggr logger.Logger,
capReg *capabilities_registry.CapabilitiesRegistry,
chain deployment.Chain,
p2pIDs [][32]byte,
p2pIDsByNodeOpId map[uint32][][32]byte,
) error {
var nodeParams []capabilities_registry.CapabilitiesRegistryNodeParams
nodes, err := capReg.GetNodes(nil)
Expand All @@ -235,26 +252,28 @@ func AddNodes(
HashedCapabilityIds: node.HashedCapabilityIds,
}
}
for _, p2pID := range p2pIDs {
// if any p2pIDs are empty throw error
if bytes.Equal(p2pID[:], make([]byte, 32)) {
return errors.Wrapf(errors.New("empty p2pID"), "p2pID: %x selector: %d", p2pID, chain.Selector)
}
nodeParam := capabilities_registry.CapabilitiesRegistryNodeParams{
NodeOperatorId: NodeOperatorID,
Signer: p2pID, // Not used in tests
P2pId: p2pID,
EncryptionPublicKey: p2pID, // Not used in tests
HashedCapabilityIds: [][32]byte{CCIPCapabilityID},
}
if existing, ok := existingNodeParams[p2pID]; ok {
if isEqual, err := isEqualCapabilitiesRegistryNodeParams(existing, nodeParam); err != nil && isEqual {
lggr.Infow("Node already exists", "p2pID", p2pID)
continue
for nopID, p2pIDs := range p2pIDsByNodeOpId {
for _, p2pID := range p2pIDs {
// if any p2pIDs are empty throw error
if bytes.Equal(p2pID[:], make([]byte, 32)) {
return errors.Wrapf(errors.New("empty p2pID"), "p2pID: %x selector: %d", p2pID, chain.Selector)
}
nodeParam := capabilities_registry.CapabilitiesRegistryNodeParams{
NodeOperatorId: nopID,
Signer: p2pID, // Not used in tests
P2pId: p2pID,
EncryptionPublicKey: p2pID, // Not used in tests
HashedCapabilityIds: [][32]byte{CCIPCapabilityID},
}
if existing, ok := existingNodeParams[p2pID]; ok {
if isEqual, err := isEqualCapabilitiesRegistryNodeParams(existing, nodeParam); err != nil && isEqual {
lggr.Infow("Node already exists", "p2pID", p2pID)
continue
}
}
}

nodeParams = append(nodeParams, nodeParam)
nodeParams = append(nodeParams, nodeParam)
}
}
if len(nodeParams) == 0 {
lggr.Infow("No new nodes to add")
Expand Down
9 changes: 7 additions & 2 deletions deployment/ccip/test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ package ccipdeployment
import (
"context"
"fmt"
mapset "github.com/deckarep/golang-set/v2"
"math/big"
"sort"
"testing"
"time"

mapset "github.com/deckarep/golang-set/v2"

"github.com/ethereum/go-ethereum/accounts/abi/bind"

"github.com/ethereum/go-ethereum/core/types"
Expand Down Expand Up @@ -111,7 +112,11 @@ func DeployTestContracts(t *testing.T,
feedChainSel uint64,
chains map[uint64]deployment.Chain,
) deployment.CapabilityRegistryConfig {
capReg, err := DeployCapReg(lggr, ab, chains[homeChainSel])
capReg, err := DeployCapReg(lggr, ab, chains[homeChainSel],
NewTestRMNStaticConfig(),
NewTestRMNDynamicConfig(),
NewTestNodeOperator(chains[homeChainSel].DeployerKey.From),
)
require.NoError(t, err)
_, err = DeployFeeds(lggr, ab, chains[feedChainSel])
require.NoError(t, err)
Expand Down
31 changes: 31 additions & 0 deletions deployment/ccip/test_params.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package ccipdeployment

import (
"github.com/ethereum/go-ethereum/common"

"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_home"
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry"
)

func NewTestRMNStaticConfig() rmn_home.RMNHomeStaticConfig {
AnieeG marked this conversation as resolved.
Show resolved Hide resolved
return rmn_home.RMNHomeStaticConfig{
Nodes: []rmn_home.RMNHomeNode{},
OffchainConfig: []byte("static config"),
}
}

func NewTestRMNDynamicConfig() rmn_home.RMNHomeDynamicConfig {
return rmn_home.RMNHomeDynamicConfig{
SourceChains: []rmn_home.RMNHomeSourceChain{},
OffchainConfig: []byte("dynamic config"),
}
}

func NewTestNodeOperator(admin common.Address) []capabilities_registry.CapabilitiesRegistryNodeOperator {
return []capabilities_registry.CapabilitiesRegistryNodeOperator{
{
Admin: admin,
Name: "NodeOperator",
},
}
}
2 changes: 1 addition & 1 deletion deployment/changeset.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
)

var (
ErrInvalidConfig = errors.New("invalid config")
ErrInvalidConfig = errors.New("invalid changeset config")
)

// ChangeSet represents a set of changes to be made to an environment.
Expand Down
Loading