diff --git a/deployment/keystone/capability_management.go b/deployment/keystone/capability_management.go index 19b6d9e9398..4e15ea897ab 100644 --- a/deployment/keystone/capability_management.go +++ b/deployment/keystone/capability_management.go @@ -62,3 +62,9 @@ func AddCapabilities(lggr logger.Logger, registry *kcr.CapabilitiesRegistry, cha } return nil } + +// CapabilityID returns a unique id for the capability +// TODO: mv to chainlink-common? ref https://github.com/smartcontractkit/chainlink/blob/4fb06b4525f03c169c121a68defa9b13677f5f20/contracts/src/v0.8/keystone/CapabilitiesRegistry.sol#L170 +func CapabilityID(c kcr.CapabilitiesRegistryCapability) string { + return fmt.Sprintf("%s@%s", c.LabelledName, c.Version) +} diff --git a/deployment/keystone/changeset/append_node_capbilities.go b/deployment/keystone/changeset/append_node_capbilities.go index 14b69307eae..20988825110 100644 --- a/deployment/keystone/changeset/append_node_capbilities.go +++ b/deployment/keystone/changeset/append_node_capbilities.go @@ -3,20 +3,23 @@ package changeset import ( "fmt" - "github.com/smartcontractkit/chainlink-common/pkg/logger" + chainsel "github.com/smartcontractkit/chain-selectors" kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" "github.com/smartcontractkit/chainlink/deployment" kslib "github.com/smartcontractkit/chainlink/deployment/keystone" + "github.com/smartcontractkit/chainlink/deployment/keystone/changeset/internal" ) +var _ deployment.ChangeSet = AppendNodeCapabilities + type AppendNodeCapabilitiesRequest struct { - Chain deployment.Chain - Registry *kcr.CapabilitiesRegistry + AddressBook deployment.AddressBook + RegistryChainSel uint64 P2pToCapabilities map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability - NopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*kslib.P2PSignerEnc + NopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*P2PSignerEnc } func (req *AppendNodeCapabilitiesRequest) Validate() error { @@ -26,12 +29,18 @@ func (req *AppendNodeCapabilitiesRequest) Validate() error { if len(req.NopToNodes) == 0 { return fmt.Errorf("nopToNodes is empty") } - if req.Registry == nil { + if req.AddressBook == nil { return fmt.Errorf("registry is nil") } + _, exists := chainsel.ChainBySelector(req.RegistryChainSel) + if !exists { + return fmt.Errorf("registry chain selector %d does not exist", req.RegistryChainSel) + } + return nil } +/* // AppendNodeCapabilibity 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(lggr logger.Logger, req *AppendNodeCapabilitiesRequest) (deployment.ChangesetOutput, error) { @@ -41,40 +50,51 @@ func AppendNodeCapabilities(lggr logger.Logger, req *AppendNodeCapabilitiesReque } return deployment.ChangesetOutput{}, nil } +*/ -func appendNodeCapabilitiesImpl(lggr logger.Logger, req *AppendNodeCapabilitiesRequest) (*kslib.UpdateNodesResponse, error) { - if err := req.Validate(); err != nil { - return nil, fmt.Errorf("failed to validate request: %w", err) +// AppendNodeCapabilibity 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, config any) (deployment.ChangesetOutput, error) { + req, ok := config.(*AppendNodeCapabilitiesRequest) + if !ok { + return deployment.ChangesetOutput{}, fmt.Errorf("invalid config type") } - // collect all the capabilities and add them to the registry - var capabilities []kcr.CapabilitiesRegistryCapability - for _, cap := range req.P2pToCapabilities { - capabilities = append(capabilities, cap...) + + cfg, err := req.convert(env) + if err != nil { + return deployment.ChangesetOutput{}, err } - err := kslib.AddCapabilities(lggr, req.Registry, req.Chain, capabilities) + _, err = internal.AppendNodeCapabilitiesImpl(env.Logger, cfg) if err != nil { - return nil, fmt.Errorf("failed to add capabilities: %w", err) + return deployment.ChangesetOutput{}, err } + return deployment.ChangesetOutput{}, nil +} - // for each node, merge the new capabilities with the existing ones and update the node - capsByPeer := make(map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability) - for p2pID, caps := range req.P2pToCapabilities { - caps, err := kslib.AppendCapabilities(lggr, req.Registry, req.Chain, []p2pkey.PeerID{p2pID}, caps) - if err != nil { - return nil, fmt.Errorf("failed to append capabilities for p2p %s: %w", p2pID, err) - } - capsByPeer[p2pID] = caps[p2pID] +func (req *AppendNodeCapabilitiesRequest) convert(e deployment.Environment) (*internal.AppendNodeCapabilitiesRequest, error) { + if err := req.Validate(); err != nil { + return nil, fmt.Errorf("failed to validate UpdateNodeCapabilitiesRequest: %w", err) } - - updateNodesReq := &kslib.UpdateNodesRequest{ - Chain: req.Chain, - Registry: req.Registry, - P2pToCapabilities: capsByPeer, - NopToNodes: req.NopToNodes, + registryChain, ok := e.Chains[req.RegistryChainSel] + if !ok { + return nil, fmt.Errorf("registry chain selector %d does not exist in environment", req.RegistryChainSel) } - resp, err := kslib.UpdateNodes(lggr, updateNodesReq) + contracts, err := kslib.GetContractSets(&kslib.GetContractSetsRequest{ + Chains: map[uint64]deployment.Chain{req.RegistryChainSel: registryChain}, + AddressBook: req.AddressBook, + }) if err != nil { - return nil, fmt.Errorf("failed to update nodes: %w", err) + 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) } - return resp, nil + + return &internal.AppendNodeCapabilitiesRequest{ + Chain: registryChain, + Registry: registry, + P2pToCapabilities: req.P2pToCapabilities, + NopToNodes: req.NopToNodes, + }, nil } diff --git a/deployment/keystone/changeset/helpers_test.go b/deployment/keystone/changeset/helpers_test.go deleted file mode 100644 index c15b59fc400..00000000000 --- a/deployment/keystone/changeset/helpers_test.go +++ /dev/null @@ -1,7 +0,0 @@ -package changeset - -// AppendNodeCapabilitiesImpl exported so we can test the onchain result of the AppendNodeCapability Changeset function -var AppendNodeCapabilitiesImpl = appendNodeCapabilitiesImpl - -// UpdateNodeCapabilitiesImpl exported so we can test the onchain result of UpdateNodeCapability Changeset function -var UpdateNodeCapabilitiesImpl = updateNodeCapabilitiesImpl diff --git a/deployment/keystone/changeset/internal/append_node_capabilities.go b/deployment/keystone/changeset/internal/append_node_capabilities.go new file mode 100644 index 00000000000..f917039e8e0 --- /dev/null +++ b/deployment/keystone/changeset/internal/append_node_capabilities.go @@ -0,0 +1,69 @@ +package internal + +import ( + "fmt" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/deployment" + kslib "github.com/smartcontractkit/chainlink/deployment/keystone" + kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" +) + +type AppendNodeCapabilitiesRequest struct { + Chain deployment.Chain + Registry *kcr.CapabilitiesRegistry + + P2pToCapabilities map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability + NopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*P2PSignerEnc +} + +func (req *AppendNodeCapabilitiesRequest) Validate() error { + if len(req.P2pToCapabilities) == 0 { + return fmt.Errorf("p2pToCapabilities is empty") + } + if len(req.NopToNodes) == 0 { + return fmt.Errorf("nopToNodes is empty") + } + if req.Registry == nil { + return fmt.Errorf("registry is nil") + } + return nil +} + +func AppendNodeCapabilitiesImpl(lggr logger.Logger, req *AppendNodeCapabilitiesRequest) (*UpdateNodesResponse, error) { + 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...) + } + err := kslib.AddCapabilities(lggr, req.Registry, req.Chain, capabilities) + 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 + capsByPeer := make(map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability) + for p2pID, caps := range req.P2pToCapabilities { + caps, err := AppendCapabilities(lggr, req.Registry, req.Chain, []p2pkey.PeerID{p2pID}, caps) + if err != nil { + return nil, fmt.Errorf("failed to append capabilities for p2p %s: %w", p2pID, err) + } + capsByPeer[p2pID] = caps[p2pID] + } + + updateNodesReq := &UpdateNodesRequest{ + Chain: req.Chain, + Registry: req.Registry, + P2pToCapabilities: capsByPeer, + NopToNodes: req.NopToNodes, + } + resp, err := UpdateNodes(lggr, updateNodesReq) + if err != nil { + return nil, fmt.Errorf("failed to update nodes: %w", err) + } + return resp, nil +} diff --git a/deployment/keystone/changeset/append_node_capabilities_test.go b/deployment/keystone/changeset/internal/append_node_capabilities_test.go similarity index 74% rename from deployment/keystone/changeset/append_node_capabilities_test.go rename to deployment/keystone/changeset/internal/append_node_capabilities_test.go index e6b94b495fc..48a9af3e38d 100644 --- a/deployment/keystone/changeset/append_node_capabilities_test.go +++ b/deployment/keystone/changeset/internal/append_node_capabilities_test.go @@ -1,17 +1,16 @@ -package changeset_test +package internal_test import ( "testing" - "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink/deployment" - kslib "github.com/smartcontractkit/chainlink/deployment/keystone" - "github.com/smartcontractkit/chainlink/deployment/keystone/changeset" - kstest "github.com/smartcontractkit/chainlink/deployment/keystone/test" + "github.com/smartcontractkit/chainlink/deployment/keystone/changeset/internal" + + kstest "github.com/smartcontractkit/chainlink/deployment/keystone/changeset/internal/test" kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" ) @@ -27,9 +26,9 @@ func TestAppendNodeCapabilities(t *testing.T) { }, }, } - nopToNodes = map[kcr.CapabilitiesRegistryNodeOperator][]*kslib.P2PSignerEnc{ - testNop(t, "testNop"): []*kslib.P2PSignerEnc{ - &kslib.P2PSignerEnc{ + nopToNodes = map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc{ + testNop(t, "testNop"): []*internal.P2PSignerEnc{ + &internal.P2PSignerEnc{ Signer: [32]byte{0: 1}, P2PKey: testPeerID(t, "0x1"), EncryptionPublicKey: [32]byte{7: 7, 13: 13}, @@ -42,7 +41,7 @@ func TestAppendNodeCapabilities(t *testing.T) { type args struct { lggr logger.Logger - req *changeset.AppendNodeCapabilitiesRequest + req *internal.AppendNodeCapabilitiesRequest initialState *kstest.SetupTestRegistryRequest } tests := []struct { @@ -55,7 +54,7 @@ func TestAppendNodeCapabilities(t *testing.T) { name: "invalid request", args: args{ lggr: lggr, - req: &changeset.AppendNodeCapabilitiesRequest{ + req: &internal.AppendNodeCapabilitiesRequest{ Chain: deployment.Chain{}, }, initialState: &kstest.SetupTestRegistryRequest{}, @@ -70,7 +69,7 @@ func TestAppendNodeCapabilities(t *testing.T) { P2pToCapabilities: initialp2pToCapabilities, NopToNodes: nopToNodes, }, - req: &changeset.AppendNodeCapabilitiesRequest{ + req: &internal.AppendNodeCapabilitiesRequest{ P2pToCapabilities: map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability{ testPeerID(t, "0x1"): []kcr.CapabilitiesRegistryCapability{ { @@ -100,9 +99,9 @@ func TestAppendNodeCapabilities(t *testing.T) { tt.args.req.Registry = setupResp.Registry tt.args.req.Chain = setupResp.Chain - got, err := changeset.AppendNodeCapabilitiesImpl(tt.args.lggr, tt.args.req) + got, err := internal.AppendNodeCapabilitiesImpl(tt.args.lggr, tt.args.req) if (err != nil) != tt.wantErr { - t.Errorf("AppendNodeCapabilities() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("internal.AppendNodeCapabilities() error = %v, wantErr %v", err, tt.wantErr) return } if tt.wantErr { @@ -119,17 +118,3 @@ func TestAppendNodeCapabilities(t *testing.T) { }) } } - -func testPeerID(t *testing.T, s string) p2pkey.PeerID { - var out [32]byte - b := []byte(s) - copy(out[:], b) - return p2pkey.PeerID(out) -} - -func testNop(t *testing.T, name string) kcr.CapabilitiesRegistryNodeOperator { - return kcr.CapabilitiesRegistryNodeOperator{ - Admin: common.HexToAddress("0xFFFFFFFF45297A703e4508186d4C1aa1BAf80000"), - Name: name, - } -} diff --git a/deployment/keystone/test/utils.go b/deployment/keystone/changeset/internal/test/utils.go similarity index 92% rename from deployment/keystone/test/utils.go rename to deployment/keystone/changeset/internal/test/utils.go index 2e864fa7afc..64b08a70150 100644 --- a/deployment/keystone/test/utils.go +++ b/deployment/keystone/changeset/internal/test/utils.go @@ -14,19 +14,21 @@ import ( "github.com/smartcontractkit/chainlink/deployment/environment/memory" kslib "github.com/smartcontractkit/chainlink/deployment/keystone" + internal "github.com/smartcontractkit/chainlink/deployment/keystone/changeset/internal" kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" ) type SetupTestRegistryRequest struct { P2pToCapabilities map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability - NopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*kslib.P2PSignerEnc - DonToNodes map[string][]*kslib.P2PSignerEnc + NopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc + DonToNodes map[string][]*internal.P2PSignerEnc } type SetupTestRegistryResponse struct { - Registry *kcr.CapabilitiesRegistry - Chain deployment.Chain + Registry *kcr.CapabilitiesRegistry + Chain deployment.Chain + RegistrySelector uint64 } func SetupTestRegistry(t *testing.T, lggr logger.Logger, req *SetupTestRegistryRequest) *SetupTestRegistryResponse { @@ -63,7 +65,7 @@ func SetupTestRegistry(t *testing.T, lggr logger.Logger, req *SetupTestRegistryR for p2pID := range req.P2pToCapabilities { initialp2pToCapabilities[p2pID] = vanillaCapabilities(registeredCapabilities) } - phonyRequest := &kslib.UpdateNodesRequest{ + phonyRequest := &internal.UpdateNodesRequest{ Chain: chain, Registry: registry, P2pToCapabilities: req.P2pToCapabilities, @@ -73,8 +75,9 @@ func SetupTestRegistry(t *testing.T, lggr logger.Logger, req *SetupTestRegistryR require.NoError(t, err) addNodes(t, lggr, chain, registry, nodeParams) return &SetupTestRegistryResponse{ - Registry: registry, - Chain: chain, + Registry: registry, + Chain: chain, + RegistrySelector: chain.Selector, } } diff --git a/deployment/keystone/changeset/internal/update_node_capabilities.go b/deployment/keystone/changeset/internal/update_node_capabilities.go new file mode 100644 index 00000000000..3d3e1e80607 --- /dev/null +++ b/deployment/keystone/changeset/internal/update_node_capabilities.go @@ -0,0 +1,116 @@ +package internal + +import ( + "fmt" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/deployment" + kslib "github.com/smartcontractkit/chainlink/deployment/keystone" + kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" +) + +type UpdateNodeCapabilitiesImplRequest struct { + Chain deployment.Chain + Registry *kcr.CapabilitiesRegistry + + P2pToCapabilities map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability + NopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*P2PSignerEnc +} + +func (req *UpdateNodeCapabilitiesImplRequest) Validate() error { + if len(req.P2pToCapabilities) == 0 { + return fmt.Errorf("p2pToCapabilities is empty") + } + if len(req.NopToNodes) == 0 { + return fmt.Errorf("nopToNodes is empty") + } + if req.Registry == nil { + return fmt.Errorf("registry is nil") + } + + return nil +} + +func UpdateNodeCapabilitiesImpl(lggr logger.Logger, req *UpdateNodeCapabilitiesImplRequest) (*UpdateNodesResponse, error) { + 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...) + } + err := kslib.AddCapabilities(lggr, req.Registry, req.Chain, capabilities) + if err != nil { + return nil, fmt.Errorf("failed to add capabilities: %w", err) + } + + updateNodesReq := &UpdateNodesRequest{ + Chain: req.Chain, + Registry: req.Registry, + P2pToCapabilities: req.P2pToCapabilities, + NopToNodes: req.NopToNodes, + } + resp, err := UpdateNodes(lggr, updateNodesReq) + if err != nil { + return nil, fmt.Errorf("failed to update nodes: %w", err) + } + return resp, nil +} + +/* +// 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) error { + if len(capabilities) == 0 { + return nil + } + // dedup capabilities + var deduped []kcr.CapabilitiesRegistryCapability + seen := make(map[string]struct{}) + for _, cap := range capabilities { + if _, ok := seen[CapabilityID(cap)]; !ok { + seen[CapabilityID(cap)] = struct{}{} + deduped = append(deduped, cap) + } + } + + tx, err := registry.AddCapabilities(chain.DeployerKey, deduped) + if err != nil { + err = DecodeErr(kcr.CapabilitiesRegistryABI, err) + // no typed errors in the abi, so we have to do string matching + // try to add all capabilities in one go, if that fails, fall back to 1-by-1 + if !strings.Contains(err.Error(), "CapabilityAlreadyExists") { + return fmt.Errorf("failed to call AddCapabilities: %w", err) + } + lggr.Warnw("capabilities already exist, falling back to 1-by-1", "capabilities", deduped) + for _, cap := range deduped { + tx, err = registry.AddCapabilities(chain.DeployerKey, []kcr.CapabilitiesRegistryCapability{cap}) + if err != nil { + err = DecodeErr(kcr.CapabilitiesRegistryABI, err) + if strings.Contains(err.Error(), "CapabilityAlreadyExists") { + lggr.Warnw("capability already exists, skipping", "capability", cap) + continue + } + return fmt.Errorf("failed to call AddCapabilities for capability %v: %w", cap, err) + } + // 1-by-1 tx is pending and we need to wait for it to be mined + _, err = chain.Confirm(tx) + if err != nil { + return fmt.Errorf("failed to confirm AddCapabilities confirm transaction %s: %w", tx.Hash().String(), err) + } + lggr.Debugw("registered capability", "capability", cap) + + } + } else { + // the bulk add tx is pending and we need to wait for it to be mined + _, err = chain.Confirm(tx) + if err != nil { + return fmt.Errorf("failed to confirm AddCapabilities confirm transaction %s: %w", tx.Hash().String(), err) + } + lggr.Info("registered capabilities", "capabilities", deduped) + } + return nil +} +*/ diff --git a/deployment/keystone/changeset/update_node_capabilities_test.go b/deployment/keystone/changeset/internal/update_node_capabilities_test.go similarity index 87% rename from deployment/keystone/changeset/update_node_capabilities_test.go rename to deployment/keystone/changeset/internal/update_node_capabilities_test.go index 26dbedc602a..d90840f5d13 100644 --- a/deployment/keystone/changeset/update_node_capabilities_test.go +++ b/deployment/keystone/changeset/internal/update_node_capabilities_test.go @@ -1,4 +1,4 @@ -package changeset_test +package internal_test import ( "testing" @@ -8,9 +8,9 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink/deployment" - kslib "github.com/smartcontractkit/chainlink/deployment/keystone" - "github.com/smartcontractkit/chainlink/deployment/keystone/changeset" - kstest "github.com/smartcontractkit/chainlink/deployment/keystone/test" + //"github.com/smartcontractkit/chainlink/deployment/keystone/changeset" + kslib "github.com/smartcontractkit/chainlink/deployment/keystone/changeset/internal" + kstest "github.com/smartcontractkit/chainlink/deployment/keystone/changeset/internal/test" kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" ) @@ -41,7 +41,7 @@ func TestUpdateNodeCapabilities(t *testing.T) { type args struct { lggr logger.Logger - req *changeset.UpdateNodeCapabilitiesRequest + req *kslib.UpdateNodeCapabilitiesImplRequest // chain and registry are set in the test setup initialState *kstest.SetupTestRegistryRequest } tests := []struct { @@ -54,7 +54,7 @@ func TestUpdateNodeCapabilities(t *testing.T) { name: "invalid request", args: args{ lggr: lggr, - req: &changeset.UpdateNodeCapabilitiesRequest{ + req: &kslib.UpdateNodeCapabilitiesImplRequest{ Chain: deployment.Chain{}, }, initialState: &kstest.SetupTestRegistryRequest{}, @@ -69,7 +69,7 @@ func TestUpdateNodeCapabilities(t *testing.T) { P2pToCapabilities: initialp2pToCapabilities, NopToNodes: nopToNodes, }, - req: &changeset.UpdateNodeCapabilitiesRequest{ + req: &kslib.UpdateNodeCapabilitiesImplRequest{ P2pToCapabilities: map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability{ testPeerID(t, "0x1"): []kcr.CapabilitiesRegistryCapability{ { @@ -93,13 +93,11 @@ func TestUpdateNodeCapabilities(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - // chagen the name and args to be mor egeneral setupResp := kstest.SetupTestRegistry(t, lggr, tt.args.initialState) - tt.args.req.Registry = setupResp.Registry tt.args.req.Chain = setupResp.Chain - got, err := changeset.UpdateNodeCapabilitiesImpl(tt.args.lggr, tt.args.req) + got, err := kslib.UpdateNodeCapabilitiesImpl(tt.args.lggr, tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("UpdateNodeCapabilities() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/deployment/keystone/update_nodes.go b/deployment/keystone/changeset/internal/update_nodes.go similarity index 89% rename from deployment/keystone/update_nodes.go rename to deployment/keystone/changeset/internal/update_nodes.go index 1e87d3af311..48face7086b 100644 --- a/deployment/keystone/update_nodes.go +++ b/deployment/keystone/changeset/internal/update_nodes.go @@ -1,4 +1,4 @@ -package keystone +package internal import ( "errors" @@ -11,6 +11,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" "github.com/smartcontractkit/chainlink/deployment" + kslib "github.com/smartcontractkit/chainlink/deployment/keystone" ) type UpdateNodesRequest struct { @@ -64,7 +65,7 @@ func UpdateNodes(lggr logger.Logger, req *UpdateNodesRequest) (*UpdateNodesRespo } tx, err := req.Registry.UpdateNodes(req.Chain.DeployerKey, params) if err != nil { - err = DecodeErr(kcr.CapabilitiesRegistryABI, err) + err = kslib.DecodeErr(kcr.CapabilitiesRegistryABI, err) return nil, fmt.Errorf("failed to call UpdateNodes: %w", err) } @@ -113,8 +114,8 @@ func AppendCapabilities(lggr logger.Logger, registry *kcr.CapabilitiesRegistry, var deduped []kcr.CapabilitiesRegistryCapability seen := make(map[string]struct{}) for _, cap := range mergedCaps { - if _, ok := seen[CapabilityID(cap)]; !ok { - seen[CapabilityID(cap)] = struct{}{} + if _, ok := seen[kslib.CapabilityID(cap)]; !ok { + seen[kslib.CapabilityID(cap)] = struct{}{} deduped = append(deduped, cap) } } @@ -159,9 +160,9 @@ func makeNodeParams(registry *kcr.CapabilitiesRegistry, } hashedCaps := make([][32]byte, len(caps)) for i, cap := range caps { - hashedCap, exists := capMap[CapabilityID(cap)] + hashedCap, exists := capMap[kslib.CapabilityID(cap)] if !exists { - return nil, fmt.Errorf("capability id not found for %s", CapabilityID(cap)) + return nil, fmt.Errorf("capability id not found for %s", kslib.CapabilityID(cap)) } hashedCaps[i] = hashedCap } @@ -178,17 +179,11 @@ func makeNodeParams(registry *kcr.CapabilitiesRegistry, return out, nil } -// CapabilityID returns a unique id for the capability -// TODO: mv to chainlink-common? ref https://github.com/smartcontractkit/chainlink/blob/4fb06b4525f03c169c121a68defa9b13677f5f20/contracts/src/v0.8/keystone/CapabilitiesRegistry.sol#L170 -func CapabilityID(c kcr.CapabilitiesRegistryCapability) string { - return fmt.Sprintf("%s@%s", c.LabelledName, c.Version) -} - -// fetchCapabilityIDs fetches the capability ids for the given capabilities +// fetchkslib.CapabilityIDs fetches the capability ids for the given capabilities func fetchCapabilityIDs(registry *kcr.CapabilitiesRegistry, caps []kcr.CapabilitiesRegistryCapability) (map[string][32]byte, error) { out := make(map[string][32]byte) for _, cap := range caps { - name := CapabilityID(cap) + name := kslib.CapabilityID(cap) if _, exists := out[name]; exists { continue } diff --git a/deployment/keystone/update_nodes_test.go b/deployment/keystone/changeset/internal/update_nodes_test.go similarity index 87% rename from deployment/keystone/update_nodes_test.go rename to deployment/keystone/changeset/internal/update_nodes_test.go index 24d40047823..3515ef13cbe 100644 --- a/deployment/keystone/update_nodes_test.go +++ b/deployment/keystone/changeset/internal/update_nodes_test.go @@ -1,4 +1,4 @@ -package keystone_test +package internal_test import ( "bytes" @@ -14,7 +14,8 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink/deployment" kslib "github.com/smartcontractkit/chainlink/deployment/keystone" - kstest "github.com/smartcontractkit/chainlink/deployment/keystone/test" + internal "github.com/smartcontractkit/chainlink/deployment/keystone/changeset/internal" + kstest "github.com/smartcontractkit/chainlink/deployment/keystone/changeset/internal/test" "github.com/smartcontractkit/chainlink/deployment/environment/memory" kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" @@ -24,8 +25,8 @@ import ( func Test_UpdateNodesRequest_validate(t *testing.T) { type fields struct { p2pToCapabilities map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability - //nopToNodes map[uint32][]*kslib.P2PSigner - nopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*kslib.P2PSignerEnc + //nopToNodes map[uint32][]*internal.P2PSigner + nopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc chain deployment.Chain registry *kcr.CapabilitiesRegistry } @@ -47,14 +48,14 @@ func Test_UpdateNodesRequest_validate(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - req := &kslib.UpdateNodesRequest{ + req := &internal.UpdateNodesRequest{ P2pToCapabilities: tt.fields.p2pToCapabilities, NopToNodes: tt.fields.nopToNodes, Chain: tt.fields.chain, Registry: tt.fields.registry, } if err := req.Validate(); (err != nil) != tt.wantErr { - t.Errorf("kslib.UpdateNodesRequest.validate() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("internal.UpdateNodesRequest.validate() error = %v, wantErr %v", err, tt.wantErr) } }) } @@ -67,19 +68,19 @@ func TestUpdateNodes(t *testing.T) { type args struct { lggr logger.Logger - req *kslib.UpdateNodesRequest + req *internal.UpdateNodesRequest } tests := []struct { name string args args - want *kslib.UpdateNodesResponse + want *internal.UpdateNodesResponse wantErr bool }{ { name: "one node, one capability", args: args{ lggr: lggr, - req: &kslib.UpdateNodesRequest{ + req: &internal.UpdateNodesRequest{ P2pToCapabilities: map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability{ testPeerID(t, "peerID_1"): []kcr.CapabilitiesRegistryCapability{ { @@ -89,8 +90,8 @@ func TestUpdateNodes(t *testing.T) { }, }, }, - NopToNodes: map[kcr.CapabilitiesRegistryNodeOperator][]*kslib.P2PSignerEnc{ - testNop(t, "nop1"): []*kslib.P2PSignerEnc{ + NopToNodes: map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc{ + testNop(t, "nop1"): []*internal.P2PSignerEnc{ { P2PKey: testPeerID(t, "peerID_1"), Signer: [32]byte{0: 1, 1: 2}, @@ -102,7 +103,7 @@ func TestUpdateNodes(t *testing.T) { Registry: nil, // set in test to ensure no conflicts }, }, - want: &kslib.UpdateNodesResponse{ + want: &internal.UpdateNodesResponse{ NodeParams: []kcr.CapabilitiesRegistryNodeParams{ { NodeOperatorId: 1, @@ -119,7 +120,7 @@ func TestUpdateNodes(t *testing.T) { name: "one node, two capabilities", args: args{ lggr: lggr, - req: &kslib.UpdateNodesRequest{ + req: &internal.UpdateNodesRequest{ P2pToCapabilities: map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability{ testPeerID(t, "peerID_1"): []kcr.CapabilitiesRegistryCapability{ { @@ -134,8 +135,8 @@ func TestUpdateNodes(t *testing.T) { }, }, }, - NopToNodes: map[kcr.CapabilitiesRegistryNodeOperator][]*kslib.P2PSignerEnc{ - testNop(t, "nop1"): []*kslib.P2PSignerEnc{ + NopToNodes: map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc{ + testNop(t, "nop1"): []*internal.P2PSignerEnc{ { P2PKey: testPeerID(t, "peerID_1"), Signer: [32]byte{0: 1, 1: 2}, @@ -147,7 +148,7 @@ func TestUpdateNodes(t *testing.T) { Registry: nil, // set in test to ensure no conflicts }, }, - want: &kslib.UpdateNodesResponse{ + want: &internal.UpdateNodesResponse{ NodeParams: []kcr.CapabilitiesRegistryNodeParams{ { NodeOperatorId: 1, @@ -171,7 +172,7 @@ func TestUpdateNodes(t *testing.T) { name: "twos node, one shared capability", args: args{ lggr: lggr, - req: &kslib.UpdateNodesRequest{ + req: &internal.UpdateNodesRequest{ P2pToCapabilities: map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability{ testPeerID(t, "peerID_1"): []kcr.CapabilitiesRegistryCapability{ { @@ -188,15 +189,15 @@ func TestUpdateNodes(t *testing.T) { }, }, }, - NopToNodes: map[kcr.CapabilitiesRegistryNodeOperator][]*kslib.P2PSignerEnc{ - testNop(t, "nopA"): []*kslib.P2PSignerEnc{ + NopToNodes: map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc{ + testNop(t, "nopA"): []*internal.P2PSignerEnc{ { P2PKey: testPeerID(t, "peerID_1"), Signer: [32]byte{0: 1, 31: 1}, EncryptionPublicKey: [32]byte{0: 7, 1: 7}, }, }, - testNop(t, "nopB"): []*kslib.P2PSignerEnc{ + testNop(t, "nopB"): []*internal.P2PSignerEnc{ { P2PKey: testPeerID(t, "peerID_2"), Signer: [32]byte{0: 2, 31: 2}, @@ -208,7 +209,7 @@ func TestUpdateNodes(t *testing.T) { Registry: nil, // set in test to ensure no conflicts }, }, - want: &kslib.UpdateNodesResponse{ + want: &internal.UpdateNodesResponse{ NodeParams: []kcr.CapabilitiesRegistryNodeParams{ { NodeOperatorId: 1, @@ -232,7 +233,7 @@ func TestUpdateNodes(t *testing.T) { name: "twos node, different capabilities", args: args{ lggr: lggr, - req: &kslib.UpdateNodesRequest{ + req: &internal.UpdateNodesRequest{ P2pToCapabilities: map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability{ testPeerID(t, "peerID_1"): []kcr.CapabilitiesRegistryCapability{ { @@ -249,15 +250,15 @@ func TestUpdateNodes(t *testing.T) { }, }, }, - NopToNodes: map[kcr.CapabilitiesRegistryNodeOperator][]*kslib.P2PSignerEnc{ - testNop(t, "nopA"): []*kslib.P2PSignerEnc{ + NopToNodes: map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc{ + testNop(t, "nopA"): []*internal.P2PSignerEnc{ { P2PKey: testPeerID(t, "peerID_1"), Signer: [32]byte{0: 1, 31: 1}, EncryptionPublicKey: [32]byte{0: 7, 1: 7}, }, }, - testNop(t, "nopB"): []*kslib.P2PSignerEnc{ + testNop(t, "nopB"): []*internal.P2PSignerEnc{ { P2PKey: testPeerID(t, "peerID_2"), Signer: [32]byte{0: 2, 31: 2}, @@ -269,7 +270,7 @@ func TestUpdateNodes(t *testing.T) { Registry: nil, // set in test to ensure no conflicts }, }, - want: &kslib.UpdateNodesResponse{ + want: &internal.UpdateNodesResponse{ NodeParams: []kcr.CapabilitiesRegistryNodeParams{ { NodeOperatorId: 1, @@ -319,7 +320,7 @@ func TestUpdateNodes(t *testing.T) { expectedCaps := capCache.AddCapabilities(tt.args.lggr, tt.args.req.Chain, registry, newCaps) expectedUpdatedCaps[p2p] = expectedCaps } - got, err := kslib.UpdateNodes(tt.args.lggr, tt.args.req) + got, err := internal.UpdateNodes(tt.args.lggr, tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("UpdateNodes() error = %v, wantErr %v", err, tt.wantErr) return @@ -376,8 +377,8 @@ func TestUpdateNodes(t *testing.T) { }, }, } - nopToNodes = map[kcr.CapabilitiesRegistryNodeOperator][]*kslib.P2PSignerEnc{ - testNop(t, "nopA"): []*kslib.P2PSignerEnc{ + nopToNodes = map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc{ + testNop(t, "nopA"): []*internal.P2PSignerEnc{ { P2PKey: testPeerID(t, "peerID_1"), Signer: [32]byte{0: 1, 1: 2}, @@ -411,13 +412,13 @@ func TestUpdateNodes(t *testing.T) { _, err = chain.Confirm(tx) require.NoError(t, err) - var req = &kslib.UpdateNodesRequest{ + var req = &internal.UpdateNodesRequest{ P2pToCapabilities: p2pToCapabilitiesUpdated, NopToNodes: nopToNodes, Chain: chain, Registry: registry, } - _, err = kslib.UpdateNodes(lggr, req) + _, err = internal.UpdateNodes(lggr, req) require.NoError(t, err) info, err = registry.GetNode(&bind.CallOpts{}, testPeerID(t, "peerID_1")) require.NoError(t, err) @@ -425,7 +426,7 @@ func TestUpdateNodes(t *testing.T) { want := info.HashedCapabilityIds[0] // update again and ensure the result is the same - _, err = kslib.UpdateNodes(lggr, req) + _, err = internal.UpdateNodes(lggr, req) require.NoError(t, err) info, err = registry.GetNode(&bind.CallOpts{}, testPeerID(t, "peerID_1")) require.NoError(t, err) @@ -447,8 +448,8 @@ func TestAppendCapabilities(t *testing.T) { }, }, } - nopToNodes = map[kcr.CapabilitiesRegistryNodeOperator][]*kslib.P2PSignerEnc{ - testNop(t, "nop"): []*kslib.P2PSignerEnc{ + nopToNodes = map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc{ + testNop(t, "nop"): []*internal.P2PSignerEnc{ { P2PKey: testPeerID(t, "peerID_1"), Signer: [32]byte{0: 1, 1: 2}, @@ -483,7 +484,7 @@ func TestAppendCapabilities(t *testing.T) { CapabilityType: 0, }, } - appendedResp, err := kslib.AppendCapabilities(lggr, registry, chain, []p2pkey.PeerID{testPeerID(t, "peerID_1")}, newCaps) + appendedResp, err := internal.AppendCapabilities(lggr, registry, chain, []p2pkey.PeerID{testPeerID(t, "peerID_1")}, newCaps) require.NoError(t, err) require.Len(t, appendedResp, 1) gotCaps := appendedResp[testPeerID(t, "peerID_1")] @@ -496,7 +497,7 @@ func TestAppendCapabilities(t *testing.T) { } // trying to append an existing capability should not change the result - appendedResp2, err := kslib.AppendCapabilities(lggr, registry, chain, []p2pkey.PeerID{testPeerID(t, "peerID_1")}, newCaps) + appendedResp2, err := internal.AppendCapabilities(lggr, registry, chain, []p2pkey.PeerID{testPeerID(t, "peerID_1")}, newCaps) require.NoError(t, err) require.Len(t, appendedResp2, 1) gotCaps2 := appendedResp2[testPeerID(t, "peerID_1")] diff --git a/deployment/keystone/changeset/update_node_capabilities.go b/deployment/keystone/changeset/update_node_capabilities.go index 701aa260836..462a527273d 100644 --- a/deployment/keystone/changeset/update_node_capabilities.go +++ b/deployment/keystone/changeset/update_node_capabilities.go @@ -1,69 +1,113 @@ package changeset import ( + "encoding/json" "fmt" - "github.com/smartcontractkit/chainlink-common/pkg/logger" + chainsel "github.com/smartcontractkit/chain-selectors" "github.com/smartcontractkit/chainlink/deployment" kslib "github.com/smartcontractkit/chainlink/deployment/keystone" + "github.com/smartcontractkit/chainlink/deployment/keystone/changeset/internal" + kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" ) +var _ deployment.ChangeSet = UpdateNodeCapabilities + +type P2PSignerEnc = internal.P2PSignerEnc + type UpdateNodeCapabilitiesRequest struct { - Chain deployment.Chain - Registry *kcr.CapabilitiesRegistry + AddressBook deployment.AddressBook + RegistryChainSel uint64 P2pToCapabilities map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability - NopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*kslib.P2PSignerEnc + NopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*P2PSignerEnc } func (req *UpdateNodeCapabilitiesRequest) Validate() error { + if req.AddressBook == nil { + return fmt.Errorf("address book is nil") + } if len(req.P2pToCapabilities) == 0 { return fmt.Errorf("p2pToCapabilities is empty") } if len(req.NopToNodes) == 0 { return fmt.Errorf("nopToNodes is empty") } - if req.Registry == nil { - return fmt.Errorf("registry is nil") + _, exists := chainsel.ChainBySelector(req.RegistryChainSel) + if !exists { + return fmt.Errorf("registry chain selector %d does not exist", req.RegistryChainSel) } return nil } -// UpdateNodeCapabilibity sets the capabilities of the node to the new capabilities. -// New capabilities are added to the onchain registry and the node is updated to host the new capabilities. -func UpdateNodeCapabilities(lggr logger.Logger, req *UpdateNodeCapabilitiesRequest) (deployment.ChangesetOutput, error) { - _, err := updateNodeCapabilitiesImpl(lggr, req) - if err != nil { - return deployment.ChangesetOutput{}, err +type UpdateNodeCapabilitiesImplRequest struct { + Chain deployment.Chain + Registry *kcr.CapabilitiesRegistry + + P2pToCapabilities map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability + NopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc +} + +func (req *UpdateNodeCapabilitiesImplRequest) Validate() error { + if len(req.P2pToCapabilities) == 0 { + return fmt.Errorf("p2pToCapabilities is empty") + } + if len(req.NopToNodes) == 0 { + return fmt.Errorf("nopToNodes is empty") } - return deployment.ChangesetOutput{}, nil + if req.Registry == nil { + return fmt.Errorf("registry is nil") + } + + return nil } -func updateNodeCapabilitiesImpl(lggr logger.Logger, req *UpdateNodeCapabilitiesRequest) (*kslib.UpdateNodesResponse, error) { +func (req *UpdateNodeCapabilitiesRequest) updateNodeCapabilitiesImplRequest(e deployment.Environment) (*internal.UpdateNodeCapabilitiesImplRequest, error) { if err := req.Validate(); err != nil { - return nil, fmt.Errorf("failed to validate request: %w", err) + return nil, fmt.Errorf("failed to validate UpdateNodeCapabilitiesRequest: %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...) + registryChain, ok := e.Chains[req.RegistryChainSel] + if !ok { + return nil, fmt.Errorf("registry chain selector %d does not exist in environment", req.RegistryChainSel) } - err := kslib.AddCapabilities(lggr, req.Registry, req.Chain, capabilities) + contracts, err := kslib.GetContractSets(&kslib.GetContractSetsRequest{ + Chains: map[uint64]deployment.Chain{req.RegistryChainSel: registryChain}, + AddressBook: req.AddressBook, + }) if err != nil { - return nil, fmt.Errorf("failed to add capabilities: %w", err) + return nil, fmt.Errorf("failed to get contract sets: %w", err) } - - updateNodesReq := &kslib.UpdateNodesRequest{ - Chain: req.Chain, - Registry: req.Registry, + registry := contracts.ContractSets[req.RegistryChainSel].CapabilitiesRegistry + if registry == nil { + return nil, fmt.Errorf("capabilities registry not found for chain %d", req.RegistryChainSel) + } + return &internal.UpdateNodeCapabilitiesImplRequest{ + Chain: registryChain, + Registry: registry, P2pToCapabilities: req.P2pToCapabilities, NopToNodes: req.NopToNodes, + }, nil +} + +// UpdateNodeCapabilities updates the capabilities of nodes in the registry +func UpdateNodeCapabilities(env deployment.Environment, config any) (deployment.ChangesetOutput, error) { + req, ok := config.(*UpdateNodeCapabilitiesRequest) + if !ok { + return deployment.ChangesetOutput{}, fmt.Errorf("invalid config type. want %T, got %T", &UpdateNodeCapabilitiesRequest{}, config) } - resp, err := kslib.UpdateNodes(lggr, updateNodesReq) + c, err := req.updateNodeCapabilitiesImplRequest(env) if err != nil { - return nil, fmt.Errorf("failed to update nodes: %w", err) + return deployment.ChangesetOutput{}, fmt.Errorf("failed to convert request: %w", err) + } + + r, err := internal.UpdateNodeCapabilitiesImpl(env.Logger, c) + if err == nil { + b, err2 := json.Marshal(r) + if err2 != nil { + env.Logger.Debugf("Updated node capabilities '%s'", b) + } } - return resp, nil + return deployment.ChangesetOutput{}, err }