Skip to content

Commit

Permalink
Replace NodesFromJD with NodeInfo (#15256)
Browse files Browse the repository at this point in the history
* Replace NodesFromJD with NodeInfo

* Remove ocr2Node and use deployment.Node directly

* "robust" chainID handling

* Fix tests

* move code; add test for ocr3 config

* Do not truncate non-EVM keys

* Fix key decoding in ocr3config test

* Slightly simplify

* Add a test for toNodeKeys()

* fix test - update offchainconfig to match value with test secrets

---------

Co-authored-by: krehermann <[email protected]>
  • Loading branch information
archseer and krehermann authored Nov 22, 2024
1 parent cd96fdf commit a267825
Show file tree
Hide file tree
Showing 13 changed files with 838 additions and 865 deletions.
88 changes: 72 additions & 16 deletions deployment/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"math/big"
"sort"
"strings"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
Expand All @@ -21,6 +22,7 @@ import (
csav1 "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/csa"
jobv1 "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/job"
nodev1 "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/node"
"github.com/smartcontractkit/chainlink-protos/job-distributor/v1/shared/ptypes"

"github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey"
)
Expand Down Expand Up @@ -222,11 +224,14 @@ func (n Nodes) BootstrapLocators() []string {

type Node struct {
NodeID string
Name string
CSAKey string
SelToOCRConfig map[chain_selectors.ChainDetails]OCRConfig
PeerID p2pkey.PeerID
IsBootstrap bool
MultiAddr string
AdminAddr string
Labels []*ptypes.Label
}

func (n Node) OCRConfigForChainDetails(details chain_selectors.ChainDetails) (OCRConfig, bool) {
Expand Down Expand Up @@ -269,17 +274,50 @@ func MustPeerIDFromString(s string) p2pkey.PeerID {
}

type NodeChainConfigsLister interface {
ListNodes(ctx context.Context, in *nodev1.ListNodesRequest, opts ...grpc.CallOption) (*nodev1.ListNodesResponse, error)
ListNodeChainConfigs(ctx context.Context, in *nodev1.ListNodeChainConfigsRequest, opts ...grpc.CallOption) (*nodev1.ListNodeChainConfigsResponse, error)
}

// Gathers all the node info through JD required to be able to set
// OCR config for example.
// OCR config for example. nodeIDs can be JD IDs or PeerIDs
func NodeInfo(nodeIDs []string, oc NodeChainConfigsLister) (Nodes, error) {
if len(nodeIDs) == 0 {
return nil, nil
}
// if nodeIDs starts with `p2p_` lookup by p2p_id instead
filterByPeerIDs := strings.HasPrefix(nodeIDs[0], "p2p_")
var filter *nodev1.ListNodesRequest_Filter
if filterByPeerIDs {
selector := strings.Join(nodeIDs, ",")
filter = &nodev1.ListNodesRequest_Filter{
Enabled: 1,
Selectors: []*ptypes.Selector{
{
Key: "p2p_id",
Op: ptypes.SelectorOp_IN,
Value: &selector,
},
},
}
} else {
filter = &nodev1.ListNodesRequest_Filter{
Enabled: 1,
Ids: nodeIDs,
}

}
nodesFromJD, err := oc.ListNodes(context.Background(), &nodev1.ListNodesRequest{
Filter: filter,
})
if err != nil {
return nil, fmt.Errorf("failed to list nodes: %w", err)
}

var nodes []Node
for _, nodeID := range nodeIDs {
for _, node := range nodesFromJD.GetNodes() {
// TODO: Filter should accept multiple nodes
nodeChainConfigs, err := oc.ListNodeChainConfigs(context.Background(), &nodev1.ListNodeChainConfigsRequest{Filter: &nodev1.ListNodeChainConfigsRequest_Filter{
NodeIds: []string{nodeID},
NodeIds: []string{node.Id},
}})
if err != nil {
return nil, err
Expand All @@ -294,7 +332,6 @@ func NodeInfo(nodeIDs []string, oc NodeChainConfigsLister) (Nodes, error) {
// Might make sense to change proto as peerID/multiAddr is 1-1 with nodeID?
peerID = MustPeerIDFromString(chainConfig.Ocr2Config.P2PKeyBundle.PeerId)
multiAddr = chainConfig.Ocr2Config.Multiaddr
adminAddr = chainConfig.AdminAddress
if chainConfig.Ocr2Config.IsBootstrap {
// NOTE: Assume same peerID for all chains.
// Might make sense to change proto as peerID is 1-1 with nodeID?
Expand All @@ -309,40 +346,59 @@ func NodeInfo(nodeIDs []string, oc NodeChainConfigsLister) (Nodes, error) {
var cpk types3.ConfigEncryptionPublicKey
copy(cpk[:], b)

var pubkey types3.OnchainPublicKey
if chainConfig.Chain.Type == nodev1.ChainType_CHAIN_TYPE_EVM {
// convert from pubkey to address
pubkey = common.HexToAddress(chainConfig.Ocr2Config.OcrKeyBundle.OnchainSigningAddress).Bytes()
} else {
pubkey = common.Hex2Bytes(chainConfig.Ocr2Config.OcrKeyBundle.OnchainSigningAddress)
}

ocrConfig := OCRConfig{
OffchainPublicKey: opk,
OnchainPublicKey: common.HexToAddress(chainConfig.Ocr2Config.OcrKeyBundle.OnchainSigningAddress).Bytes(),
OnchainPublicKey: pubkey,
PeerID: MustPeerIDFromString(chainConfig.Ocr2Config.P2PKeyBundle.PeerId),
TransmitAccount: types2.Account(chainConfig.AccountAddress),
ConfigEncryptionPublicKey: cpk,
KeyBundleID: chainConfig.Ocr2Config.OcrKeyBundle.BundleId,
}

var details chain_selectors.ChainDetails
if chainConfig.Chain.Type == nodev1.ChainType_CHAIN_TYPE_EVM {
// NOTE: Assume same adminAddr for all chains. We always use EVM addr
adminAddr = chainConfig.AdminAddress
}

var family string
switch chainConfig.Chain.Type {
case nodev1.ChainType_CHAIN_TYPE_APTOS:
details, err = chain_selectors.GetChainDetailsByChainIDAndFamily(chainConfig.Chain.Id, chain_selectors.FamilyAptos)
if err != nil {
return nil, err
}
case nodev1.ChainType_CHAIN_TYPE_EVM:
details, err = chain_selectors.GetChainDetailsByChainIDAndFamily(chainConfig.Chain.Id, chain_selectors.FamilyEVM)
if err != nil {
return nil, err
}
family = chain_selectors.FamilyEVM
case nodev1.ChainType_CHAIN_TYPE_APTOS:
family = chain_selectors.FamilyAptos
case nodev1.ChainType_CHAIN_TYPE_SOLANA:
family = chain_selectors.FamilySolana
case nodev1.ChainType_CHAIN_TYPE_STARKNET:
family = chain_selectors.FamilyStarknet
default:
return nil, fmt.Errorf("unsupported chain type %s", chainConfig.Chain.Type)
}

details, err := chain_selectors.GetChainDetailsByChainIDAndFamily(chainConfig.Chain.Id, family)
if err != nil {
return nil, err
}

selToOCRConfig[details] = ocrConfig
}
nodes = append(nodes, Node{
NodeID: nodeID,
NodeID: node.Id,
Name: node.Name,
CSAKey: node.PublicKey,
SelToOCRConfig: selToOCRConfig,
IsBootstrap: bootstrap,
PeerID: peerID,
MultiAddr: multiAddr,
AdminAddr: adminAddr,
Labels: node.Labels,
})
}

Expand Down
15 changes: 8 additions & 7 deletions deployment/environment/memory/job_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,16 +190,17 @@ func (j JobClient) ListNodeChainConfigs(ctx context.Context, in *nodev1.ListNode
if err != nil {
return nil, err
}
chainID, err := chainsel.ChainIdFromSelector(selector)
if err != nil {
return nil, err
}

if family == chainsel.FamilyEVM {
// already handled above
continue
}

// NOTE: this supports non-EVM too
chainID, err := chainsel.GetChainIDFromSelector(selector)
if err != nil {
return nil, err
}

var ocrtype chaintype.ChainType
switch family {
case chainsel.FamilyEVM:
Expand All @@ -213,7 +214,7 @@ func (j JobClient) ListNodeChainConfigs(ctx context.Context, in *nodev1.ListNode
case chainsel.FamilyAptos:
ocrtype = chaintype.Aptos
default:
panic(fmt.Sprintf("Unsupported chain family %v", family))
return nil, fmt.Errorf("Unsupported chain family %v", family)
}

bundle := n.Keys.OCRKeyBundles[ocrtype]
Expand Down Expand Up @@ -244,7 +245,7 @@ func (j JobClient) ListNodeChainConfigs(ctx context.Context, in *nodev1.ListNode

chainConfigs = append(chainConfigs, &nodev1.ChainConfig{
Chain: &nodev1.Chain{
Id: strconv.Itoa(int(chainID)),
Id: chainID,
Type: ctype,
},
AccountAddress: "", // TODO: support AccountAddress
Expand Down
4 changes: 4 additions & 0 deletions deployment/environment/memory/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ func NewNode(
) *Node {
evmchains := make(map[uint64]EVMChain)
for _, chain := range chains {
// we're only mapping evm chains here
if family, err := chainsel.GetSelectorFamily(chain.Selector); err != nil || family != chainsel.FamilyEVM {
continue
}
evmChainID, err := chainsel.ChainIdFromSelector(chain.Selector)
if err != nil {
t.Fatal(err)
Expand Down
90 changes: 48 additions & 42 deletions deployment/keystone/changeset/internal/update_don_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@ package internal_test

import (
"bytes"
"encoding/hex"
"fmt"
"math/big"
"sort"
"strconv"
"testing"

"github.com/ethereum/go-ethereum/common"
chainsel "github.com/smartcontractkit/chain-selectors"
"github.com/smartcontractkit/chainlink-common/pkg/logger"
nodev1 "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/node"
"github.com/smartcontractkit/chainlink/deployment"
"github.com/smartcontractkit/chainlink/deployment/keystone"
kslib "github.com/smartcontractkit/chainlink/deployment/keystone"
Expand All @@ -22,9 +24,12 @@ import (
"github.com/stretchr/testify/require"
)

var (
registryChain = chainsel.TEST_90000001
)

func TestUpdateDon(t *testing.T) {
var (
registryChain = chainsel.TEST_90000001
// nodes
p2p_1 = p2pkey.MustNewV2XXXTestingOnly(big.NewInt(100))
pubKey_1 = "11114981a6119ca3f932cdb8c402d71a72d672adae7849f581ecff8b8e1098e7" // valid csa key
Expand Down Expand Up @@ -98,14 +103,14 @@ func TestUpdateDon(t *testing.T) {
dons: []kslib.DonInfo{
{
Name: "don 1",
Nodes: []keystone.Node{node_1, node_2, node_3, node_4},
Nodes: []deployment.Node{node_1, node_2, node_3, node_4},
Capabilities: []kcr.CapabilitiesRegistryCapability{cap_A},
},
},
nops: []keystone.NOP{
{
Name: "nop 1",
Nodes: []string{node_1.ID, node_2.ID, node_3.ID, node_4.ID},
Nodes: []string{node_1.NodeID, node_2.NodeID, node_3.NodeID, node_4.NodeID},
},
},
}
Expand Down Expand Up @@ -170,27 +175,36 @@ type minimalNodeCfg struct {
admin common.Address
}

func newNode(t *testing.T, cfg minimalNodeCfg) keystone.Node {
func newNode(t *testing.T, cfg minimalNodeCfg) deployment.Node {
t.Helper()

return keystone.Node{
ID: cfg.id,
PublicKey: &cfg.pubKey,
ChainConfigs: []*nodev1.ChainConfig{
{
Chain: &nodev1.Chain{
Id: "test chain",
Type: nodev1.ChainType_CHAIN_TYPE_EVM,
},
AdminAddress: cfg.admin.String(),
Ocr2Config: &nodev1.OCR2Config{
P2PKeyBundle: &nodev1.OCR2Config_P2PKeyBundle{
PeerId: cfg.p2p.PeerID().String(),
},
OcrKeyBundle: &nodev1.OCR2Config_OCRKeyBundle{
OnchainSigningAddress: cfg.signingAddr,
},
},
registryChainID, err := chainsel.ChainIdFromSelector(registryChain.Selector)
if err != nil {
panic(err)
}
registryChainDetails, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.Itoa(int(registryChainID)), chainsel.FamilyEVM)
if err != nil {
panic(err)
}

signingAddr, err := hex.DecodeString(cfg.signingAddr)
require.NoError(t, err)

var pubkey [32]byte
if _, err := hex.Decode(pubkey[:], []byte(cfg.pubKey)); err != nil {
panic(fmt.Sprintf("failed to decode pubkey %s: %v", pubkey, err))
}

return deployment.Node{
NodeID: cfg.id,
PeerID: cfg.p2p.PeerID(),
CSAKey: cfg.pubKey,
AdminAddr: cfg.admin.String(),
SelToOCRConfig: map[chainsel.ChainDetails]deployment.OCRConfig{
registryChainDetails: {
OnchainPublicKey: signingAddr,
PeerID: cfg.p2p.PeerID(),
ConfigEncryptionPublicKey: pubkey,
},
},
}
Expand All @@ -214,10 +228,10 @@ func setupUpdateDonTest(t *testing.T, lggr logger.Logger, cfg setupUpdateDonTest

func newSetupTestRegistryRequest(t *testing.T, dons []kslib.DonInfo, nops []keystone.NOP) *kstest.SetupTestRegistryRequest {
t.Helper()
nodes := make(map[string]keystone.Node)
nodes := make(map[string]deployment.Node)
for _, don := range dons {
for _, node := range don.Nodes {
nodes[node.ID] = node
nodes[node.NodeID] = node
}
}
nopsToNodes := makeNopToNodes(t, nops, nodes)
Expand All @@ -231,23 +245,23 @@ func newSetupTestRegistryRequest(t *testing.T, dons []kslib.DonInfo, nops []keys
return req
}

func makeNopToNodes(t *testing.T, nops []keystone.NOP, nodes map[string]keystone.Node) map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc {
func makeNopToNodes(t *testing.T, nops []keystone.NOP, nodes map[string]deployment.Node) map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc {
nopToNodes := make(map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc)

for _, nop := range nops {
// all chain configs are the same wrt admin address & node keys
// so we can just use the first one
crnop := kcr.CapabilitiesRegistryNodeOperator{
Name: nop.Name,
Admin: common.HexToAddress(nodes[nop.Nodes[0]].ChainConfigs[0].AdminAddress),
Admin: common.HexToAddress(nodes[nop.Nodes[0]].AdminAddr),
}
var signers []*internal.P2PSignerEnc
for _, nodeID := range nop.Nodes {
node := nodes[nodeID]
require.NotNil(t, node.PublicKey, "public key is nil %s", node.ID)
require.NotNil(t, node.CSAKey, "public key is nil %s", node.NodeID)
// all chain configs are the same wrt admin address & node keys
p, err := kscs.NewP2PSignerEncFromJD(node.ChainConfigs[0], *node.PublicKey)
require.NoError(t, err, "failed to make p2p signer enc from clo nod %s", node.ID)
p, err := kscs.NewP2PSignerEnc(&node, registryChain.Selector)
require.NoError(t, err, "failed to make p2p signer enc from clo nod %s", node.NodeID)
signers = append(signers, p)
}
nopToNodes[crnop] = signers
Expand All @@ -260,8 +274,8 @@ func makeP2PToCapabilities(t *testing.T, dons []kslib.DonInfo) map[p2pkey.PeerID
for _, don := range dons {
for _, node := range don.Nodes {
for _, cap := range don.Capabilities {
p, err := kscs.NewP2PSignerEncFromJD(node.ChainConfigs[0], *node.PublicKey)
require.NoError(t, err, "failed to make p2p signer enc from clo nod %s", node.ID)
p, err := kscs.NewP2PSignerEnc(&node, registryChain.Selector)
require.NoError(t, err, "failed to make p2p signer enc from clo nod %s", node.NodeID)
p2pToCapabilities[p.P2PKey] = append(p2pToCapabilities[p.P2PKey], cap)
}
}
Expand All @@ -282,8 +296,8 @@ func testDon(t *testing.T, don kslib.DonInfo) kstest.Don {
for _, node := range don.Nodes {
// all chain configs are the same wrt admin address & node keys
// so we can just use the first one
p, err := kscs.NewP2PSignerEncFromJD(node.ChainConfigs[0], *node.PublicKey)
require.NoError(t, err, "failed to make p2p signer enc from clo nod %s", node.ID)
p, err := kscs.NewP2PSignerEnc(&node, registryChain.Selector)
require.NoError(t, err, "failed to make p2p signer enc from clo nod %s", node.NodeID)
p2pids = append(p2pids, p.P2PKey)
}

Expand All @@ -299,11 +313,3 @@ func testDon(t *testing.T, don kslib.DonInfo) kstest.Don {
CapabilityConfigs: capabilityConfigs,
}
}

func newP2PSignerEnc(signer [32]byte, p2pkey p2pkey.PeerID, encryptionPublicKey [32]byte) *internal.P2PSignerEnc {
return &internal.P2PSignerEnc{
Signer: signer,
P2PKey: p2pkey,
EncryptionPublicKey: encryptionPublicKey,
}
}
Loading

0 comments on commit a267825

Please sign in to comment.