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

Allow to hydrate contract from the capability registry view #15309

Merged
merged 9 commits into from
Nov 25, 2024
123 changes: 119 additions & 4 deletions deployment/common/view/v1_0/capreg.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ type CapabilityRegistryView struct {

// MarshalJSON marshals the CapabilityRegistryView to JSON. It includes the Capabilities, Nodes, Nops, and Dons
// and a denormalized summary of the Dons with their associated Nodes and Capabilities, which is useful for a high-level view
func (v CapabilityRegistryView) MarshalJSON() ([]byte, error) {
func (v *CapabilityRegistryView) MarshalJSON() ([]byte, error) {
// Alias to avoid recursive calls
type Alias struct {
types.ContractMetaData
Expand All @@ -51,6 +51,36 @@ func (v CapabilityRegistryView) MarshalJSON() ([]byte, error) {
return json.MarshalIndent(&a, "", " ")
}

// UnmarshalJSON unmarshals the CapabilityRegistryView from JSON. Since the CapabilityRegistryView doesn't hold a DonCapabilities field,
// it is not unmarshaled.
func (v *CapabilityRegistryView) UnmarshalJSON(data []byte) error {
// Alias to avoid recursive calls
type Alias struct {
types.ContractMetaData
Capabilities []CapabilityView `json:"capabilities,omitempty"`
Nodes []NodeView `json:"nodes,omitempty"`
Nops []NopView `json:"nops,omitempty"`
Dons []DonView `json:"dons,omitempty"`
DonCapabilities []DonDenormalizedView `json:"don_capabilities_summary,omitempty"`
}
a := Alias{
ContractMetaData: v.ContractMetaData,
Capabilities: v.Capabilities,
Nodes: v.Nodes,
Nops: v.Nops,
Dons: v.Dons,
}
if err := json.Unmarshal(data, &a); err != nil {
return err
}
v.ContractMetaData = a.ContractMetaData
v.Capabilities = a.Capabilities
v.Nodes = a.Nodes
v.Nops = a.Nops
v.Dons = a.Dons
return nil
}

// GenerateCapabilityRegistryView generates a CapRegView from a CapabilitiesRegistry contract.
func GenerateCapabilityRegistryView(capReg *capabilities_registry.CapabilitiesRegistry) (CapabilityRegistryView, error) {
tv, err := types.NewContractMetaData(capReg, capReg.Address())
Expand Down Expand Up @@ -112,7 +142,7 @@ type DonDenormalizedView struct {
// Nodes and Capabilities. This is a useful form of the CapabilityRegistryView, but it is not definitive.
// The full CapRegView should be used for the most accurate information as it can contain
// Capabilities and Nodes the are not associated with any Don.
func (v CapabilityRegistryView) DonDenormalizedView() ([]DonDenormalizedView, error) {
func (v *CapabilityRegistryView) DonDenormalizedView() ([]DonDenormalizedView, error) {
var out []DonDenormalizedView
for _, don := range v.Dons {
var nodes []NodeDenormalizedView
Expand Down Expand Up @@ -140,6 +170,91 @@ func (v CapabilityRegistryView) DonDenormalizedView() ([]DonDenormalizedView, er
return out, nil
}

func (v *CapabilityRegistryView) NodesToNodesParams() ([]capabilities_registry.CapabilitiesRegistryNodeParams, error) {
var nodesParams []capabilities_registry.CapabilitiesRegistryNodeParams
for _, node := range v.Nodes {
signer, err := hexTo32Bytes(node.Signer)
if err != nil {
return nil, err
}
encryptionPubKey, err := hexTo32Bytes(node.EncryptionPublicKey)
if err != nil {
return nil, err
}
capIDs := make([][32]byte, len(node.CapabilityIDs))
for i, id := range node.CapabilityIDs {
cid, err := hexTo32Bytes(id)
if err != nil {
return nil, err
}
capIDs[i] = cid
}
nodesParams = append(nodesParams, capabilities_registry.CapabilitiesRegistryNodeParams{
Signer: signer,
P2pId: node.P2pId,
EncryptionPublicKey: encryptionPubKey,
NodeOperatorId: node.NodeOperatorID,
HashedCapabilityIds: capIDs,
})
}

return nodesParams, nil
}

func (v *CapabilityRegistryView) CapabilitiesToCapabilitiesParams() []capabilities_registry.CapabilitiesRegistryCapability {
var capabilitiesParams []capabilities_registry.CapabilitiesRegistryCapability
for _, capability := range v.Capabilities {
capabilitiesParams = append(capabilitiesParams, capabilities_registry.CapabilitiesRegistryCapability{
LabelledName: capability.LabelledName,
Version: capability.Version,
CapabilityType: capability.CapabilityType,
ResponseType: capability.ResponseType,
ConfigurationContract: capability.ConfigurationContract,
})
}
return capabilitiesParams
}

func (v *CapabilityRegistryView) NopsToNopsParams() []capabilities_registry.CapabilitiesRegistryNodeOperator {
var nopsParams []capabilities_registry.CapabilitiesRegistryNodeOperator
for _, nop := range v.Nops {
nopsParams = append(nopsParams, capabilities_registry.CapabilitiesRegistryNodeOperator{
Admin: nop.Admin,
Name: nop.Name,
})
}
return nopsParams
}

func (v *CapabilityRegistryView) CapabilityConfigToCapabilityConfigParams(don DonView) ([]capabilities_registry.CapabilitiesRegistryCapabilityConfiguration, error) {
var cfgs []capabilities_registry.CapabilitiesRegistryCapabilityConfiguration
for _, cfg := range don.CapabilityConfigurations {
cid, err := hexTo32Bytes(cfg.ID)
if err != nil {
return nil, err
}
config, err := hex.DecodeString(cfg.Config)
if err != nil {
return nil, err
}
cfgs = append(cfgs, capabilities_registry.CapabilitiesRegistryCapabilityConfiguration{
CapabilityId: cid,
Config: config,
})
}
return cfgs, nil
}

func hexTo32Bytes(val string) ([32]byte, error) {
var out [32]byte
b, err := hex.DecodeString(val)
if err != nil {
return out, err
}
copy(out[:], b)
return out, nil
}

// CapabilityView is a serialization-friendly view of a capability in the capabilities registry.
type CapabilityView struct {
ID string `json:"id"` // hex 32 bytes
Expand Down Expand Up @@ -272,7 +387,7 @@ func NewNodeView(n capabilities_registry.INodeInfoProviderNodeInfo) NodeView {
ConfigCount: n.ConfigCount,
WorkflowDONID: n.WorkflowDONId,
Signer: hex.EncodeToString(n.Signer[:]),
P2pId: p2pkey.PeerID(n.P2pId),
P2pId: n.P2pId,
EncryptionPublicKey: hex.EncodeToString(n.EncryptionPublicKey[:]),
},
NodeOperatorID: n.NodeOperatorId,
Expand Down Expand Up @@ -328,7 +443,7 @@ func NewNopView(nop capabilities_registry.CapabilitiesRegistryNodeOperator) NopV
}
}

func (v CapabilityRegistryView) nodeDenormalizedView(n NodeView) (NodeDenormalizedView, error) {
func (v *CapabilityRegistryView) nodeDenormalizedView(n NodeView) (NodeDenormalizedView, error) {
nop, err := nodeNop(n, v.Nops)
if err != nil {
return NodeDenormalizedView{}, err
Expand Down
3 changes: 2 additions & 1 deletion deployment/common/view/v1_0/capreg_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import (
"math/big"
"testing"

"github.com/stretchr/testify/assert"

"github.com/smartcontractkit/chainlink/deployment/common/view/types"
cr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry"
"github.com/stretchr/testify/assert"
)

func TestCapRegView_Denormalize(t *testing.T) {
Expand Down
1 change: 1 addition & 0 deletions deployment/keystone/capability_registry_deployer.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/ethereum/go-ethereum/common"

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

"github.com/smartcontractkit/chainlink/deployment"
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry"
)
Expand Down
8 changes: 3 additions & 5 deletions deployment/keystone/changeset/deploy_registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@ import (
kslib "github.com/smartcontractkit/chainlink/deployment/keystone"
)

func DeployCapabilityRegistry(env deployment.Environment, config interface{}) (deployment.ChangesetOutput, error) {
registrySelector, ok := config.(uint64)
if !ok {
return deployment.ChangesetOutput{}, deployment.ErrInvalidConfig
}
var _ deployment.ChangeSet[uint64] = DeployCapabilityRegistry

func DeployCapabilityRegistry(env deployment.Environment, registrySelector uint64) (deployment.ChangesetOutput, error) {
chain, ok := env.Chains[registrySelector]
if !ok {
return deployment.ChangesetOutput{}, fmt.Errorf("chain not found in environment")
Expand Down
1 change: 1 addition & 0 deletions deployment/keystone/changeset/deploy_registry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/stretchr/testify/require"

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

"github.com/smartcontractkit/chainlink/deployment/environment/memory"
"github.com/smartcontractkit/chainlink/deployment/keystone/changeset"
)
Expand Down
1 change: 1 addition & 0 deletions deployment/keystone/changeset/view_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"go.uber.org/zap/zapcore"

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

"github.com/smartcontractkit/chainlink/deployment/environment/memory"
)

Expand Down
1 change: 1 addition & 0 deletions deployment/keystone/contract_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"

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

"github.com/smartcontractkit/chainlink/deployment"
)

Expand Down
1 change: 1 addition & 0 deletions deployment/keystone/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/ethereum/go-ethereum/common"

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

"github.com/smartcontractkit/chainlink/deployment"
common_v1_0 "github.com/smartcontractkit/chainlink/deployment/common/view/v1_0"
"github.com/smartcontractkit/chainlink/deployment/keystone/view"
Expand Down
87 changes: 87 additions & 0 deletions deployment/keystone/test/changeset/capability_registry.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package changeset

import (
"fmt"
"testing"

chainsel "github.com/smartcontractkit/chain-selectors"

"github.com/smartcontractkit/chainlink/deployment"
"github.com/smartcontractkit/chainlink/deployment/common/view/v1_0"
"github.com/smartcontractkit/chainlink/deployment/keystone"
"github.com/smartcontractkit/chainlink/deployment/keystone/changeset"
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry"
)

type HydrateConfig struct {
ChainID uint64
}

// HydrateCapabilityRegistry deploys a new capabilities registry contract and hydrates it with the provided data.
func HydrateCapabilityRegistry(t *testing.T, v v1_0.CapabilityRegistryView, env deployment.Environment, cfg HydrateConfig) (*capabilities_registry.CapabilitiesRegistry, error) {
t.Helper()
chainSelector, err := chainsel.SelectorFromChainId(cfg.ChainID)
if err != nil {
return nil, fmt.Errorf("failed to get chain selector from chain id: %w", err)
}
chain, ok := env.Chains[chainSelector]
if !ok {
return nil, fmt.Errorf("chain with id %d not found", cfg.ChainID)
}
changesetOutput, err := changeset.DeployCapabilityRegistry(env, chainSelector)
if err != nil {
return nil, fmt.Errorf("failed to deploy contract: %w", err)
}

resp, err := keystone.GetContractSets(env.Logger, &keystone.GetContractSetsRequest{
Chains: env.Chains,
AddressBook: changesetOutput.AddressBook,
})
if err != nil {
return nil, fmt.Errorf("failed to get contract sets: %w", err)
}
cs, ok := resp.ContractSets[chainSelector]
if !ok {
return nil, fmt.Errorf("failed to get contract set for chain selector: %d, chain ID: %d", chainSelector, cfg.ChainID)
}

deployedContract := cs.CapabilitiesRegistry

nopsParams := v.NopsToNopsParams()
tx, err := deployedContract.AddNodeOperators(chain.DeployerKey, nopsParams)
if _, err = deployment.ConfirmIfNoError(chain, tx, keystone.DecodeErr(capabilities_registry.CapabilitiesRegistryABI, err)); err != nil {
return nil, fmt.Errorf("failed to add node operators: %w", err)
}

capabilitiesParams := v.CapabilitiesToCapabilitiesParams()
tx, err = deployedContract.AddCapabilities(chain.DeployerKey, capabilitiesParams)
if _, err = deployment.ConfirmIfNoError(chain, tx, keystone.DecodeErr(capabilities_registry.CapabilitiesRegistryABI, err)); err != nil {
return nil, fmt.Errorf("failed to add capabilities: %w", err)
}

nodesParams, err := v.NodesToNodesParams()
if err != nil {
return nil, fmt.Errorf("failed to convert nodes to nodes params: %w", err)
}
tx, err = deployedContract.AddNodes(chain.DeployerKey, nodesParams)
if _, err = deployment.ConfirmIfNoError(chain, tx, keystone.DecodeErr(capabilities_registry.CapabilitiesRegistryABI, err)); err != nil {
return nil, fmt.Errorf("failed to add nodes: %w", err)
}

for _, don := range v.Dons {
cfgs, err := v.CapabilityConfigToCapabilityConfigParams(don)
if err != nil {
return nil, fmt.Errorf("failed to convert capability configurations to capability configuration params: %w", err)
}
var peerIds [][32]byte
for _, id := range don.NodeP2PIds {
peerIds = append(peerIds, id)
}
tx, err = deployedContract.AddDON(chain.DeployerKey, peerIds, cfgs, don.IsPublic, don.AcceptsWorkflows, don.F)
if _, err = deployment.ConfirmIfNoError(chain, tx, keystone.DecodeErr(capabilities_registry.CapabilitiesRegistryABI, err)); err != nil {
return nil, fmt.Errorf("failed to add don: %w", err)
}
}

return deployedContract, nil
}
46 changes: 46 additions & 0 deletions deployment/keystone/test/changeset/capability_registry_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package changeset

import (
"encoding/json"
"os"
"testing"

"github.com/stretchr/testify/require"
"go.uber.org/zap/zapcore"

chainsel "github.com/smartcontractkit/chain-selectors"

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

func TestHydrateCapabilityRegistry(t *testing.T) {
b, err := os.ReadFile("testdata/capability_registry_view.json")
require.NoError(t, err)
require.NotEmpty(t, b)
var capabilityRegistryView v1_0.CapabilityRegistryView
require.NoError(t, json.Unmarshal(b, &capabilityRegistryView))

chainID := chainsel.TEST_90000001.EvmChainID
cfg := HydrateConfig{ChainID: chainID}
env := memory.NewMemoryEnvironment(t, logger.TestLogger(t), zapcore.InfoLevel, memory.MemoryEnvironmentConfig{
Bootstraps: 1,
Chains: 1,
Nodes: 4,
})
hydrated, err := HydrateCapabilityRegistry(t, capabilityRegistryView, env, cfg)
require.NoError(t, err)
require.NotNil(t, hydrated)
hydratedCapView, err := v1_0.GenerateCapabilityRegistryView(hydrated)
require.NoError(t, err)

// Setting address/owner values to be the same in order to compare the views
hydratedCapView.Address = capabilityRegistryView.Address
hydratedCapView.Owner = capabilityRegistryView.Owner
b1, err := capabilityRegistryView.MarshalJSON()
require.NoError(t, err)
b2, err := hydratedCapView.MarshalJSON()
require.NoError(t, err)
require.Equal(t, string(b1), string(b2))
}
Loading
Loading