-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
KS-471: update capabilities of nodes in capability registry (#14742)
* cleanup name and better error decoding * update deployment owners * wip * support adding capabilities existing nodes * linter * add UpdateNodeCapability changeset func; fix bad merge; minor renames * add missing files * linter
- Loading branch information
1 parent
ccd9956
commit 5b66644
Showing
15 changed files
with
1,470 additions
and
98 deletions.
There are no files selected for viewing
64 changes: 64 additions & 0 deletions
64
integration-tests/deployment/keystone/capability_management.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package keystone | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
|
||
"github.com/smartcontractkit/chainlink-common/pkg/logger" | ||
"github.com/smartcontractkit/chainlink/integration-tests/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) 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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
135 changes: 135 additions & 0 deletions
135
integration-tests/deployment/keystone/changeset/append_node_capabilities_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
package changeset_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/integration-tests/deployment" | ||
kslib "github.com/smartcontractkit/chainlink/integration-tests/deployment/keystone" | ||
"github.com/smartcontractkit/chainlink/integration-tests/deployment/keystone/changeset" | ||
kstest "github.com/smartcontractkit/chainlink/integration-tests/deployment/keystone/test" | ||
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) { | ||
var ( | ||
initialp2pToCapabilities = map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability{ | ||
testPeerID(t, "0x1"): []kcr.CapabilitiesRegistryCapability{ | ||
{ | ||
LabelledName: "test", | ||
Version: "1.0.0", | ||
CapabilityType: 0, | ||
}, | ||
}, | ||
} | ||
nopToNodes = map[kcr.CapabilitiesRegistryNodeOperator][]*kslib.P2PSignerEnc{ | ||
testNop(t, "testNop"): []*kslib.P2PSignerEnc{ | ||
&kslib.P2PSignerEnc{ | ||
Signer: [32]byte{0: 1}, | ||
P2PKey: testPeerID(t, "0x1"), | ||
EncryptionPublicKey: [32]byte{7: 7, 13: 13}, | ||
}, | ||
}, | ||
} | ||
) | ||
|
||
lggr := logger.Test(t) | ||
|
||
type args struct { | ||
lggr logger.Logger | ||
req *changeset.AppendNodeCapabilitiesRequest | ||
initialState *kstest.SetupTestRegistryRequest | ||
} | ||
tests := []struct { | ||
name string | ||
args args | ||
want deployment.ChangesetOutput | ||
wantErr bool | ||
}{ | ||
{ | ||
name: "invalid request", | ||
args: args{ | ||
lggr: lggr, | ||
req: &changeset.AppendNodeCapabilitiesRequest{ | ||
Chain: deployment.Chain{}, | ||
}, | ||
initialState: &kstest.SetupTestRegistryRequest{}, | ||
}, | ||
wantErr: true, | ||
}, | ||
{ | ||
name: "happy path", | ||
args: args{ | ||
lggr: lggr, | ||
initialState: &kstest.SetupTestRegistryRequest{ | ||
P2pToCapabilities: initialp2pToCapabilities, | ||
NopToNodes: nopToNodes, | ||
}, | ||
req: &changeset.AppendNodeCapabilitiesRequest{ | ||
P2pToCapabilities: map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability{ | ||
testPeerID(t, "0x1"): []kcr.CapabilitiesRegistryCapability{ | ||
{ | ||
LabelledName: "cap2", | ||
Version: "1.0.0", | ||
CapabilityType: 0, | ||
}, | ||
{ | ||
LabelledName: "cap3", | ||
Version: "1.0.0", | ||
CapabilityType: 3, | ||
}, | ||
}, | ||
}, | ||
NopToNodes: nopToNodes, | ||
}, | ||
}, | ||
want: deployment.ChangesetOutput{}, | ||
wantErr: false, | ||
}, | ||
} | ||
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.AppendNodeCapabilitiesImpl(tt.args.lggr, tt.args.req) | ||
if (err != nil) != tt.wantErr { | ||
t.Errorf("AppendNodeCapabilities() error = %v, wantErr %v", err, tt.wantErr) | ||
return | ||
} | ||
if tt.wantErr { | ||
return | ||
} | ||
require.NotNil(t, got) | ||
// should be one node param for each input p2p id | ||
assert.Len(t, got.NodeParams, len(tt.args.req.P2pToCapabilities)) | ||
for _, nodeParam := range got.NodeParams { | ||
initialCapsOnNode := tt.args.initialState.P2pToCapabilities[nodeParam.P2pId] | ||
appendCaps := tt.args.req.P2pToCapabilities[nodeParam.P2pId] | ||
assert.Len(t, nodeParam.HashedCapabilityIds, len(initialCapsOnNode)+len(appendCaps)) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
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, | ||
} | ||
} |
80 changes: 80 additions & 0 deletions
80
integration-tests/deployment/keystone/changeset/append_node_capbilities.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
package changeset | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/smartcontractkit/chainlink-common/pkg/logger" | ||
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/integration-tests/deployment" | ||
kslib "github.com/smartcontractkit/chainlink/integration-tests/deployment/keystone" | ||
) | ||
|
||
type AppendNodeCapabilitiesRequest struct { | ||
Chain deployment.Chain | ||
Registry *kcr.CapabilitiesRegistry | ||
|
||
P2pToCapabilities map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability | ||
NopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*kslib.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 | ||
} | ||
|
||
// 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) { | ||
_, err := appendNodeCapabilitiesImpl(lggr, req) | ||
if err != nil { | ||
return deployment.ChangesetOutput{}, err | ||
} | ||
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) | ||
} | ||
// 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 := 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] | ||
} | ||
|
||
updateNodesReq := &kslib.UpdateNodesRequest{ | ||
Chain: req.Chain, | ||
Registry: req.Registry, | ||
P2pToCapabilities: capsByPeer, | ||
NopToNodes: req.NopToNodes, | ||
} | ||
resp, err := kslib.UpdateNodes(lggr, updateNodesReq) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to update nodes: %w", err) | ||
} | ||
return resp, nil | ||
} |
7 changes: 7 additions & 0 deletions
7
integration-tests/deployment/keystone/changeset/helpers_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
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 |
69 changes: 69 additions & 0 deletions
69
integration-tests/deployment/keystone/changeset/update_node_capabilities.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package changeset | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/smartcontractkit/chainlink-common/pkg/logger" | ||
"github.com/smartcontractkit/chainlink/integration-tests/deployment" | ||
kslib "github.com/smartcontractkit/chainlink/integration-tests/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 UpdateNodeCapabilitiesRequest struct { | ||
Chain deployment.Chain | ||
Registry *kcr.CapabilitiesRegistry | ||
|
||
P2pToCapabilities map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability | ||
NopToNodes map[kcr.CapabilitiesRegistryNodeOperator][]*kslib.P2PSignerEnc | ||
} | ||
|
||
func (req *UpdateNodeCapabilitiesRequest) 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 | ||
} | ||
|
||
// 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 | ||
} | ||
return deployment.ChangesetOutput{}, nil | ||
} | ||
|
||
func updateNodeCapabilitiesImpl(lggr logger.Logger, req *UpdateNodeCapabilitiesRequest) (*kslib.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 := &kslib.UpdateNodesRequest{ | ||
Chain: req.Chain, | ||
Registry: req.Registry, | ||
P2pToCapabilities: req.P2pToCapabilities, | ||
NopToNodes: req.NopToNodes, | ||
} | ||
resp, err := kslib.UpdateNodes(lggr, updateNodesReq) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to update nodes: %w", err) | ||
} | ||
return resp, nil | ||
} |
Oops, something went wrong.