Skip to content

Commit

Permalink
Allow to hydrate contract from the capability registry view (#15309)
Browse files Browse the repository at this point in the history
* Adds `Hydrate()` functionality to `CapabilityRegistryView`

* Extracts hydration functionality into a `common` package

* Add `UnmarshalJSON` functionality to `CapabilityRegistryView`

* Adds test - WIP

* Completes tests comparing capability registry views

* Refactors to use existing deployment logic

* Gets deployed contract from contract sets

* Uses uint64 type annotation

* Moves implementation to `keystone/test`
  • Loading branch information
vyzaldysanchez authored Nov 25, 2024
1 parent e094909 commit f5d228e
Show file tree
Hide file tree
Showing 11 changed files with 717 additions and 10 deletions.
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

0 comments on commit f5d228e

Please sign in to comment.