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

refactor update & append capabilities #15563

Merged
merged 4 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
22 changes: 16 additions & 6 deletions deployment/keystone/capability_management.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,21 @@ package keystone

import (
"fmt"
"math/big"

"github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms"
"github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock"
"github.com/smartcontractkit/chainlink-common/pkg/logger"
"github.com/smartcontractkit/chainlink/deployment"
kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry"
)

// AddCapabilities adds the capabilities to the registry
// it tries to add all capabilities in one go, if that fails, it falls back to adding them one by one

func AddCapabilities(lggr logger.Logger, registry *kcr.CapabilitiesRegistry, chain deployment.Chain, capabilities []kcr.CapabilitiesRegistryCapability, useMCMS bool) ([]timelock.MCMSWithTimelockProposal, error) {
func AddCapabilities(lggr logger.Logger, contractSet *ContractSet, chain deployment.Chain, capabilities []kcr.CapabilitiesRegistryCapability, useMCMS bool) (*timelock.BatchChainOperation, error) {
if len(capabilities) == 0 {
return nil, nil
}
registry := contractSet.CapabilitiesRegistry
deduped, err := dedupCapabilities(registry, capabilities)
if err != nil {
return nil, fmt.Errorf("failed to dedup capabilities: %w", err)
Expand All @@ -29,17 +30,26 @@ func AddCapabilities(lggr logger.Logger, registry *kcr.CapabilitiesRegistry, cha
err = DecodeErr(kcr.CapabilitiesRegistryABI, err)
return nil, fmt.Errorf("failed to add capabilities: %w", err)
}
var proposals []timelock.MCMSWithTimelockProposal
var batch *timelock.BatchChainOperation
if !useMCMS {
_, err = chain.Confirm(tx)
if err != nil {
return nil, fmt.Errorf("failed to confirm AddCapabilities confirm transaction %s: %w", tx.Hash().String(), err)
}
lggr.Info("registered capabilities", "capabilities", deduped)
} else {
// TODO
batch = &timelock.BatchChainOperation{
ChainIdentifier: mcms.ChainIdentifier(chain.Selector),
Batch: []mcms.Operation{
{
To: registry.Address(),
Data: tx.Data(),
Value: big.NewInt(0),
},
},
}
}
return proposals, nil
return batch, nil
}

// CapabilityID returns a unique id for the capability
Expand Down
4 changes: 4 additions & 0 deletions deployment/keystone/changeset/accept_ownership_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ func TestAcceptAllOwnership(t *testing.T) {
Changeset: commonchangeset.WrapChangeSet(changeset.DeployForwarder),
Config: registrySel,
},
{
Changeset: commonchangeset.WrapChangeSet(changeset.DeployFeedsConsumer),
Config: &changeset.DeployFeedsConsumerRequest{ChainSelector: registrySel},
},
{
Changeset: commonchangeset.WrapChangeSet(commonchangeset.DeployMCMSWithTimelock),
Config: map[uint64]types.MCMSWithTimelockConfig{
Expand Down
47 changes: 37 additions & 10 deletions deployment/keystone/changeset/append_node_capbilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ package changeset
import (
"fmt"

"github.com/ethereum/go-ethereum/common"
"github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers"
"github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock"
"github.com/smartcontractkit/chainlink/deployment"
"github.com/smartcontractkit/chainlink/deployment/common/proposalutils"
kslib "github.com/smartcontractkit/chainlink/deployment/keystone"
"github.com/smartcontractkit/chainlink/deployment/keystone/changeset/internal"
)
Expand All @@ -16,15 +20,39 @@ type AppendNodeCapabilitiesRequest = MutateNodeCapabilitiesRequest
// AppendNodeCapabilities adds any new capabilities to the registry, merges the new capabilities with the existing capabilities
// of the node, and updates the nodes in the registry host the union of the new and existing capabilities.
func AppendNodeCapabilities(env deployment.Environment, req *AppendNodeCapabilitiesRequest) (deployment.ChangesetOutput, error) {
cfg, err := req.convert(env)
c, err := req.convert(env)
if err != nil {
return deployment.ChangesetOutput{}, err
}
_, err = internal.AppendNodeCapabilitiesImpl(env.Logger, cfg)
r, err := internal.AppendNodeCapabilitiesImpl(env.Logger, c)
if err != nil {
return deployment.ChangesetOutput{}, err
}
return deployment.ChangesetOutput{}, nil
out := deployment.ChangesetOutput{}
if req.UseMCMS {
if r.Ops == nil {
return out, fmt.Errorf("expected MCMS operation to be non-nil")
}
timelocksPerChain := map[uint64]common.Address{
c.Chain.Selector: c.ContractSet.Timelock.Address(),
}
proposerMCMSes := map[uint64]*gethwrappers.ManyChainMultiSig{
c.Chain.Selector: c.ContractSet.ProposerMcm,
}

proposal, err := proposalutils.BuildProposalFromBatches(
timelocksPerChain,
proposerMCMSes,
[]timelock.BatchChainOperation{*r.Ops},
"proposal to set update node capabilities",
0,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably worth parameterizing this just in case you want to add timelock delay in the future. @carte7000 had a good idea here https://github.com/smartcontractkit/chainlink/pull/15525/files#diff-f6eeb14804b5d16769abc3f48ad18187965263d5e5d29345e4079e85199de731R38 to use an MCMS struct being nil/not-nil as a way to signal that but also keep the door open for any other MCMS specific params

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack, will address in followup; have one more in the stack to merge

)
if err != nil {
return out, fmt.Errorf("failed to build proposal: %w", err)
}
out.Proposals = []timelock.MCMSWithTimelockProposal{*proposal}
}
return out, nil
}

func (req *AppendNodeCapabilitiesRequest) convert(e deployment.Environment) (*internal.AppendNodeCapabilitiesRequest, error) {
Expand All @@ -35,21 +63,20 @@ func (req *AppendNodeCapabilitiesRequest) convert(e deployment.Environment) (*in
if !ok {
return nil, fmt.Errorf("registry chain selector %d does not exist in environment", req.RegistryChainSel)
}
contracts, err := kslib.GetContractSets(e.Logger, &kslib.GetContractSetsRequest{
resp, err := kslib.GetContractSets(e.Logger, &kslib.GetContractSetsRequest{
Chains: map[uint64]deployment.Chain{req.RegistryChainSel: registryChain},
AddressBook: req.AddressBook,
AddressBook: e.ExistingAddresses,
})
if err != nil {
return nil, fmt.Errorf("failed to get contract sets: %w", err)
}
registry := contracts.ContractSets[req.RegistryChainSel].CapabilitiesRegistry
if registry == nil {
return nil, fmt.Errorf("capabilities registry not found for chain %d", req.RegistryChainSel)
}
contracts := resp.ContractSets[req.RegistryChainSel]

return &internal.AppendNodeCapabilitiesRequest{
Chain: registryChain,
Registry: registry,
Registry: contracts.CapabilitiesRegistry,
ContractSet: &contracts,
P2pToCapabilities: req.P2pToCapabilities,
UseMCMS: req.UseMCMS,
}, nil
}
129 changes: 129 additions & 0 deletions deployment/keystone/changeset/append_node_capbilities_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package changeset_test
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo in file name


import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/exp/maps"

"github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers"
commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset"
"github.com/smartcontractkit/chainlink/deployment/keystone/changeset"
kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry"
"github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey"
)

func TestAppendNodeCapabilities(t *testing.T) {
t.Parallel()

var (
capA = kcr.CapabilitiesRegistryCapability{
LabelledName: "capA",
Version: "0.4.2",
}
capB = kcr.CapabilitiesRegistryCapability{
LabelledName: "capB",
Version: "3.16.0",
}
caps = []kcr.CapabilitiesRegistryCapability{capA, capB}
)
t.Run("no mcms", func(t *testing.T) {
te := SetupTestEnv(t, TestConfig{
WFDonConfig: DonConfig{N: 4},
AssetDonConfig: DonConfig{N: 4},
WriterDonConfig: DonConfig{N: 4},
NumChains: 1,
})

newCapabilities := make(map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability)
for id, _ := range te.WFNodes {
k, err := p2pkey.MakePeerID(id)
require.NoError(t, err)
newCapabilities[k] = caps
}

t.Run("succeeds if existing capabilities not explicit", func(t *testing.T) {
cfg := changeset.AppendNodeCapabilitiesRequest{
RegistryChainSel: te.RegistrySelector,
P2pToCapabilities: newCapabilities,
}

csOut, err := changeset.AppendNodeCapabilities(te.Env, &cfg)
require.NoError(t, err)
require.Len(t, csOut.Proposals, 0)
require.Nil(t, csOut.AddressBook)

validateCapabilityAppends(t, te, newCapabilities)
})
})
t.Run("with mcms", func(t *testing.T) {
te := SetupTestEnv(t, TestConfig{
WFDonConfig: DonConfig{N: 4},
AssetDonConfig: DonConfig{N: 4},
WriterDonConfig: DonConfig{N: 4},
NumChains: 1,
UseMCMS: true,
})

newCapabilities := make(map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability)
for id, _ := range te.WFNodes {
k, err := p2pkey.MakePeerID(id)
require.NoError(t, err)
newCapabilities[k] = caps
}

cfg := changeset.AppendNodeCapabilitiesRequest{
RegistryChainSel: te.RegistrySelector,
P2pToCapabilities: newCapabilities,
UseMCMS: true,
}

csOut, err := changeset.AppendNodeCapabilities(te.Env, &cfg)
require.NoError(t, err)
require.Len(t, csOut.Proposals, 1)
require.Len(t, csOut.Proposals[0].Transactions, 1)
require.Len(t, csOut.Proposals[0].Transactions[0].Batch, 2) // add capabilities, update nodes
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ty for this comment

require.Nil(t, csOut.AddressBook)

// now apply the changeset such that the proposal is signed and execed
contracts := te.ContractSets()[te.RegistrySelector]
timelocks := map[uint64]*gethwrappers.RBACTimelock{
te.RegistrySelector: contracts.Timelock,
}
_, err = commonchangeset.ApplyChangesets(t, te.Env, timelocks, []commonchangeset.ChangesetApplication{
{
Changeset: commonchangeset.WrapChangeSet(changeset.AppendNodeCapabilities),
Config: &cfg,
},
})
require.NoError(t, err)
validateCapabilityAppends(t, te, newCapabilities)
})

}

// validateUpdate checks reads nodes from the registry and checks they have the expected updates
func validateCapabilityAppends(t *testing.T, te TestEnv, appended map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability) {
registry := te.ContractSets()[te.RegistrySelector].CapabilitiesRegistry
wfP2PIDs := p2pIDs(t, maps.Keys(te.WFNodes))
nodes, err := registry.GetNodesByP2PIds(nil, wfP2PIDs)
require.NoError(t, err)
require.Len(t, nodes, len(wfP2PIDs))
for _, node := range nodes {
want := appended[node.P2pId]
require.NotNil(t, want)
assertContainsCapabilities(t, registry, want, node)
}
}

func assertContainsCapabilities(t *testing.T, registry *kcr.CapabilitiesRegistry, want []kcr.CapabilitiesRegistryCapability, got kcr.INodeInfoProviderNodeInfo) {
wantHashes := make([][32]byte, len(want))
for i, c := range want {
h, err := registry.GetHashedCapabilityId(nil, c.LabelledName, c.Version)
require.NoError(t, err)
wantHashes[i] = h
assert.Contains(t, got.HashedCapabilityIds, h, "missing capability %v", c)
}
assert.LessOrEqual(t, len(want), len(got.HashedCapabilityIds))
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type AppendNodeCapabilitiesRequest struct {
Chain deployment.Chain
Registry *kcr.CapabilitiesRegistry

ContractSet *kslib.ContractSet
P2pToCapabilities map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability
UseMCMS bool
}
Expand All @@ -32,15 +33,6 @@ func AppendNodeCapabilitiesImpl(lggr logger.Logger, req *AppendNodeCapabilitiesR
if err := req.Validate(); err != nil {
return nil, fmt.Errorf("failed to validate request: %w", err)
}
// collect all the capabilities and add them to the registry
var capabilities []kcr.CapabilitiesRegistryCapability
for _, cap := range req.P2pToCapabilities {
capabilities = append(capabilities, cap...)
}
proposals, err := kslib.AddCapabilities(lggr, req.Registry, req.Chain, capabilities, req.UseMCMS)
if err != nil {
return nil, fmt.Errorf("failed to add capabilities: %w", err)
}

// for each node, merge the new capabilities with the existing ones and update the node
updatesByPeer := make(map[p2pkey.PeerID]NodeUpdate)
Expand All @@ -52,16 +44,27 @@ func AppendNodeCapabilitiesImpl(lggr logger.Logger, req *AppendNodeCapabilitiesR
updatesByPeer[p2pID] = NodeUpdate{Capabilities: caps[p2pID]}
}

// collect all the capabilities and add them to the registry
var capabilities []kcr.CapabilitiesRegistryCapability
for _, cap := range req.P2pToCapabilities {
capabilities = append(capabilities, cap...)
}
op, err := kslib.AddCapabilities(lggr, req.ContractSet, req.Chain, capabilities, req.UseMCMS)
if err != nil {
return nil, fmt.Errorf("failed to add capabilities: %w", err)
}

updateNodesReq := &UpdateNodesRequest{
Chain: req.Chain,
Registry: req.Registry,
ContractSet: req.ContractSet,
P2pToUpdates: updatesByPeer,
UseMCMS: req.UseMCMS,
Ops: op,
}
resp, err := UpdateNodes(lggr, updateNodesReq)
if err != nil {
return nil, fmt.Errorf("failed to update nodes: %w", err)
}
resp.Proposals = append(proposals, resp.Proposals...)
return resp, nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import (
)

type UpdateNodeCapabilitiesImplRequest struct {
Chain deployment.Chain
Registry *kcr.CapabilitiesRegistry

Chain deployment.Chain
Registry *kcr.CapabilitiesRegistry
ContractSet *kslib.ContractSet
P2pToCapabilities map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability

UseMCMS bool
Expand All @@ -39,7 +39,7 @@ func UpdateNodeCapabilitiesImpl(lggr logger.Logger, req *UpdateNodeCapabilitiesI
for _, cap := range req.P2pToCapabilities {
capabilities = append(capabilities, cap...)
}
proposals, err := kslib.AddCapabilities(lggr, req.Registry, req.Chain, capabilities, req.UseMCMS)
op, err := kslib.AddCapabilities(lggr, req.ContractSet, req.Chain, capabilities, req.UseMCMS)
if err != nil {
return nil, fmt.Errorf("failed to add capabilities: %w", err)
}
Expand All @@ -53,12 +53,13 @@ func UpdateNodeCapabilitiesImpl(lggr logger.Logger, req *UpdateNodeCapabilitiesI
Chain: req.Chain,
Registry: req.Registry,
P2pToUpdates: p2pToUpdates,
ContractSet: req.ContractSet,
Ops: op,
UseMCMS: req.UseMCMS,
}
resp, err := UpdateNodes(lggr, updateNodesReq)
if err != nil {
return nil, fmt.Errorf("failed to update nodes: %w", err)
}
resp.Proposals = append(proposals, resp.Proposals...)
return resp, nil
}
Loading
Loading