-
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.
support adding capabilities existing nodes
- Loading branch information
1 parent
574fd93
commit 0ffcb9c
Showing
13 changed files
with
1,307 additions
and
82 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
146 changes: 146 additions & 0 deletions
146
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,146 @@ | ||
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" | ||
"github.com/smartcontractkit/chainlink/integration-tests/deployment/memory" | ||
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{ | ||
p2pId("0x1"): []kcr.CapabilitiesRegistryCapability{ | ||
{ | ||
LabelledName: "test", | ||
Version: "1.0.0", | ||
CapabilityType: 0, | ||
}, | ||
}, | ||
} | ||
nopToNodes = map[kcr.CapabilitiesRegistryNodeOperator][]*kslib.P2PSigner{ | ||
testNop(t, "testNop"): []*kslib.P2PSigner{ | ||
&kslib.P2PSigner{ | ||
Signer: [32]byte{0: 1}, | ||
P2PKey: p2pId("0x1"), | ||
}, | ||
}, | ||
} | ||
) | ||
|
||
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{ | ||
p2pId("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 p2pId(s string) p2pkey.PeerID { | ||
var out [32]byte | ||
b := []byte(s) | ||
copy(out[:], b) | ||
return p2pkey.PeerID(out) | ||
} | ||
|
||
func testChain(t *testing.T) deployment.Chain { | ||
chains := memory.NewMemoryChains(t, 1) | ||
var chain deployment.Chain | ||
for _, c := range chains { | ||
chain = c | ||
break | ||
} | ||
require.NotEmpty(t, chain) | ||
return chain | ||
} | ||
|
||
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.P2PSigner | ||
} | ||
|
||
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 | ||
} |
4 changes: 4 additions & 0 deletions
4
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,4 @@ | ||
package changeset | ||
|
||
// AppendNodeCapabilitiesImpl a helper exported for testing | ||
var AppendNodeCapabilitiesImpl = appendNodeCapabilitiesImpl |
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
Oops, something went wrong.