diff --git a/deployment/environment.go b/deployment/environment.go index 104db4c5c37..cd606b65742 100644 --- a/deployment/environment.go +++ b/deployment/environment.go @@ -8,6 +8,7 @@ import ( "math/big" "sort" "strconv" + "strings" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" @@ -22,6 +23,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" ) @@ -215,11 +217,14 @@ func (n Nodes) BootstrapLocators() []string { type Node struct { NodeID string + Name string + CSAKey string SelToOCRConfig map[uint64]OCRConfig PeerID p2pkey.PeerID IsBootstrap bool MultiAddr string AdminAddr string + Labels []*ptypes.Label } func (n Node) FirstOCRKeybundle() OCRConfig { @@ -238,17 +243,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("p2p_", nodeIDs[0]) + 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 @@ -259,10 +297,6 @@ func NodeInfo(nodeIDs []string, oc NodeChainConfigsLister) (Nodes, error) { var multiAddr string var adminAddr string for _, chainConfig := range nodeChainConfigs.ChainConfigs { - if chainConfig.Chain.Type == nodev1.ChainType_CHAIN_TYPE_SOLANA { - // Note supported for CCIP yet. - continue - } // NOTE: Assume same peerID/multiAddr for all chains. // Might make sense to change proto as peerID/multiAddr is 1-1 with nodeID? peerID = MustPeerIDFromString(chainConfig.Ocr2Config.P2PKeyBundle.PeerId) @@ -278,6 +312,7 @@ func NodeInfo(nodeIDs []string, oc NodeChainConfigsLister) (Nodes, error) { if err != nil { return nil, err } + // TODO: this is evm specific, make it work with aptos sel, err := chain_selectors.SelectorFromChainId(uint64(evmChainID)) if err != nil { return nil, err @@ -300,12 +335,15 @@ func NodeInfo(nodeIDs []string, oc NodeChainConfigsLister) (Nodes, error) { } } 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, }) } diff --git a/deployment/keystone/changeset/internal/update_don_test.go b/deployment/keystone/changeset/internal/update_don_test.go index 12ccfe290b1..05981f42bb7 100644 --- a/deployment/keystone/changeset/internal/update_don_test.go +++ b/deployment/keystone/changeset/internal/update_don_test.go @@ -2,6 +2,7 @@ package internal_test import ( "bytes" + "encoding/hex" "math/big" "sort" "testing" @@ -9,7 +10,6 @@ import ( "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" @@ -22,9 +22,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 @@ -98,14 +101,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}, }, }, } @@ -170,27 +173,20 @@ 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, - }, - }, + signingAddr, err := hex.DecodeString(cfg.signingAddr) + require.NoError(t, err) + + return deployment.Node{ + NodeID: cfg.id, + CSAKey: cfg.pubKey, + AdminAddr: cfg.admin.String(), + SelToOCRConfig: map[uint64]deployment.OCRConfig{ + registryChain.Selector: { + OnchainPublicKey: signingAddr, + PeerID: cfg.p2p.PeerID(), }, }, } @@ -214,10 +210,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) @@ -231,7 +227,7 @@ 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 { @@ -239,15 +235,15 @@ func makeNopToNodes(t *testing.T, nops []keystone.NOP, nodes map[string]keystone // 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 @@ -260,8 +256,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) } } @@ -282,8 +278,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) } diff --git a/deployment/keystone/changeset/types.go b/deployment/keystone/changeset/types.go deleted file mode 100644 index fb609041792..00000000000 --- a/deployment/keystone/changeset/types.go +++ /dev/null @@ -1,43 +0,0 @@ -package changeset - -import ( - "encoding/hex" - "errors" - "fmt" - - v1 "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/node" - "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" -) - -func NewP2PSignerEncFromJD(ccfg *v1.ChainConfig, pubkeyStr string) (*P2PSignerEnc, error) { - if ccfg == nil { - return nil, errors.New("nil ocr2config") - } - var pubkey [32]byte - if _, err := hex.Decode(pubkey[:], []byte(pubkeyStr)); err != nil { - return nil, fmt.Errorf("failed to decode pubkey %s: %w", pubkey, err) - } - ocfg := ccfg.Ocr2Config - p2p := p2pkey.PeerID{} - if err := p2p.UnmarshalString(ocfg.P2PKeyBundle.PeerId); err != nil { - return nil, fmt.Errorf("failed to unmarshal peer id %s: %w", ocfg.P2PKeyBundle.PeerId, err) - } - - signer := ocfg.OcrKeyBundle.OnchainSigningAddress - if len(signer) != 40 { - return nil, fmt.Errorf("invalid onchain signing address %s", ocfg.OcrKeyBundle.OnchainSigningAddress) - } - signerB, err := hex.DecodeString(signer) - if err != nil { - return nil, fmt.Errorf("failed to convert signer %s: %w", signer, err) - } - - var sigb [32]byte - copy(sigb[:], signerB) - - return &P2PSignerEnc{ - Signer: sigb, - P2PKey: p2p, - EncryptionPublicKey: pubkey, // TODO. no current way to get this from the node itself (and therefore not in clo or jd) - }, nil -} diff --git a/deployment/keystone/changeset/update_node_capabilities.go b/deployment/keystone/changeset/update_node_capabilities.go index 0b6c4fb5462..a51706723cc 100644 --- a/deployment/keystone/changeset/update_node_capabilities.go +++ b/deployment/keystone/changeset/update_node_capabilities.go @@ -7,7 +7,6 @@ import ( chainsel "github.com/smartcontractkit/chain-selectors" "github.com/smartcontractkit/chainlink/deployment" - "github.com/smartcontractkit/chainlink/deployment/keystone" kslib "github.com/smartcontractkit/chainlink/deployment/keystone" "github.com/smartcontractkit/chainlink/deployment/keystone/changeset/internal" @@ -19,7 +18,7 @@ var _ deployment.ChangeSet[*MutateNodeCapabilitiesRequest] = UpdateNodeCapabilit type P2PSignerEnc = internal.P2PSignerEnc -func NewP2PSignerEnc(n *keystone.Node, registryChainSel uint64) (*P2PSignerEnc, error) { +func NewP2PSignerEnc(n *deployment.Node, registryChainSel uint64) (*P2PSignerEnc, error) { p2p, signer, enc, err := kslib.ExtractKeys(n, registryChainSel) if err != nil { return nil, fmt.Errorf("failed to extract keys: %w", err) diff --git a/deployment/keystone/deploy.go b/deployment/keystone/deploy.go index a43f906178e..32286db0aad 100644 --- a/deployment/keystone/deploy.go +++ b/deployment/keystone/deploy.go @@ -7,7 +7,6 @@ import ( "encoding/hex" "errors" "fmt" - "slices" "sort" "strings" "time" @@ -16,8 +15,6 @@ import ( "github.com/ethereum/go-ethereum/rpc" "golang.org/x/exp/maps" - nodev1 "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/node" - "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/shared/ptypes" "github.com/smartcontractkit/chainlink/deployment" "google.golang.org/protobuf/proto" @@ -150,78 +147,10 @@ func DeployContracts(lggr logger.Logger, e *deployment.Environment, chainSel uin // DonInfo is DonCapabilities, but expanded to contain node information type DonInfo struct { Name string - Nodes []Node + Nodes []deployment.Node Capabilities []kcr.CapabilitiesRegistryCapability // every capability is hosted on each node } -// TODO: merge with deployment/environment.go Node -type Node struct { - ID string - P2PID string - Name string - PublicKey *string - ChainConfigs []*nodev1.ChainConfig -} - -// TODO: merge with deployment/environment.go NodeInfo, we currently lookup based on p2p_id, and chain-selectors needs non-EVM support -func NodesFromJD(name string, nodeIDs []string, jd deployment.OffchainClient) ([]Node, error) { - // lookup nodes based on p2p_ids - var nodes []Node - selector := strings.Join(nodeIDs, ",") - nodesFromJD, err := jd.ListNodes(context.Background(), &nodev1.ListNodesRequest{ - Filter: &nodev1.ListNodesRequest_Filter{ - Enabled: 1, - Selectors: []*ptypes.Selector{ - { - Key: "p2p_id", - Op: ptypes.SelectorOp_IN, - Value: &selector, - }, - }, - }, - }) - if err != nil { - return nil, fmt.Errorf("failed to list nodes '%s': %w", name, err) - } - - for _, id := range nodeIDs { - idx := slices.IndexFunc(nodesFromJD.GetNodes(), func(node *nodev1.Node) bool { - return slices.ContainsFunc(node.Labels, func(label *ptypes.Label) bool { - return label.Key == "p2p_id" && *label.Value == id - }) - }) - if idx < 0 { - var got []string - for _, node := range nodesFromJD.GetNodes() { - for _, label := range node.Labels { - if label.Key == "p2p_id" { - got = append(got, *label.Value) - } - } - } - return nil, fmt.Errorf("node id %s not found in list '%s'", id, strings.Join(got, ",")) - } - - jdNode := nodesFromJD.Nodes[idx] - // TODO: Filter should accept multiple nodes - nodeChainConfigs, err := jd.ListNodeChainConfigs(context.Background(), &nodev1.ListNodeChainConfigsRequest{Filter: &nodev1.ListNodeChainConfigsRequest_Filter{ - NodeIds: []string{jdNode.Id}, // must use the jd-specific internal node id - }}) - if err != nil { - return nil, err - } - - nodes = append(nodes, Node{ - ID: jdNode.Id, - P2PID: id, - Name: name, - PublicKey: &jdNode.PublicKey, - ChainConfigs: nodeChainConfigs.GetChainConfigs(), - }) - } - return nodes, nil -} - func DonInfos(dons []DonCapabilities, jd deployment.OffchainClient) ([]DonInfo, error) { var donInfos []DonInfo for _, don := range dons { @@ -229,7 +158,7 @@ func DonInfos(dons []DonCapabilities, jd deployment.OffchainClient) ([]DonInfo, for _, nop := range don.Nops { nodeIDs = append(nodeIDs, nop.Nodes...) } - nodes, err := NodesFromJD(don.Name, nodeIDs, jd) + nodes, err := deployment.NodeInfo(nodeIDs, jd) if err != nil { return nil, err } @@ -443,7 +372,7 @@ func ConfigureOCR3ContractFromJD(env *deployment.Environment, chainSel uint64, n if contract == nil { return fmt.Errorf("no ocr3 contract found for chain %d", chainSel) } - nodes, err := NodesFromJD("nodes", nodeIDs, env.Offchain) + nodes, err := deployment.NodeInfo(nodeIDs, env.Offchain) if err != nil { return err } @@ -707,6 +636,7 @@ func registerNodes(lggr logger.Logger, req *registerNodesRequest) (*registerNode if n.IsBoostrap { // bootstraps are part of the DON but don't host capabilities continue } + fmt.Printf("nodeToRegisterNop %+v\n", nodeToRegisterNop) nop, ok := nodeToRegisterNop[n.ID] if !ok { return nil, fmt.Errorf("node operator not found for node %s", n.ID) diff --git a/deployment/keystone/deploy_test.go b/deployment/keystone/deploy_test.go index 4e0d2a52dcc..17c7c433c08 100644 --- a/deployment/keystone/deploy_test.go +++ b/deployment/keystone/deploy_test.go @@ -37,7 +37,8 @@ func TestDeploy(t *testing.T) { require.NoError(t, err) // sepoliaArbitrumChainSel, err := chainsel.SelectorFromChainId(sepoliaArbitrumChainId) // require.NoError(t, err) - // aptosChainSel := uint64(999) // TODO: + // aptos-testnet + aptosChainSel := chainsel.AptosChainIdToChainSelector()[2] crConfig := deployment.CapabilityRegistryConfig{ EVMChainID: sepoliaChainId, @@ -45,11 +46,11 @@ func TestDeploy(t *testing.T) { } evmChains := memory.NewMemoryChainsWithChainIDs(t, []uint64{sepoliaChainId, sepoliaArbitrumChainId}) - // aptosChain := memory.NewMemoryChain(t, aptosChainSel) + aptosChain := memory.NewMemoryChain(t, aptosChainSel) wfChains := map[uint64]deployment.Chain{} wfChains[sepoliaChainSel] = evmChains[sepoliaChainSel] - // wfChains[aptosChainSel] = aptosChain + wfChains[aptosChainSel] = aptosChain wfNodes := memory.NewNodes(t, zapcore.InfoLevel, wfChains, 4, 0, crConfig) require.Len(t, wfNodes, 4) diff --git a/deployment/keystone/types.go b/deployment/keystone/types.go index e5657657ed9..801e80cd930 100644 --- a/deployment/keystone/types.go +++ b/deployment/keystone/types.go @@ -6,7 +6,6 @@ import ( "fmt" "slices" "sort" - "strconv" "strings" "github.com/ethereum/go-ethereum/common" @@ -19,7 +18,6 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" - "github.com/smartcontractkit/chainlink/v2/core/services/keystore/chaintype" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" ) @@ -65,7 +63,6 @@ type ocr2Node struct { EncryptionPublicKey [32]byte IsBoostrap bool // useful when have to register the ocr3 contract config - p2pKeyBundle *v1.OCR2Config_P2PKeyBundle ethOcr2KeyBundle *v1.OCR2Config_OCRKeyBundle aptosOcr2KeyBundle *v1.OCR2Config_OCRKeyBundle csaKey string // *v1.Node.PublicKey @@ -86,7 +83,7 @@ func (o *ocr2Node) toNodeKeys() NodeKeys { } return NodeKeys{ EthAddress: o.accountAddress, - P2PPeerID: strings.TrimPrefix(o.p2pKeyBundle.PeerId, "p2p_"), + P2PPeerID: strings.TrimPrefix(o.P2PKey.String(), "p2p_"), OCR2BundleID: o.ethOcr2KeyBundle.BundleId, OCR2OnchainPublicKey: o.ethOcr2KeyBundle.OnchainSigningAddress, OCR2OffchainPublicKey: o.ethOcr2KeyBundle.OffchainPublicKey, @@ -100,93 +97,78 @@ func (o *ocr2Node) toNodeKeys() NodeKeys { AptosOnchainPublicKey: aptosOnchainPublicKey, } } -func newOcr2NodeFromJD(n *Node, registryChainSel uint64) (*ocr2Node, error) { - if n.PublicKey == nil { - return nil, errors.New("no public key") - } - // the chain configs are equivalent as far as the ocr2 config is concerned so take the first one - if len(n.ChainConfigs) == 0 { - return nil, errors.New("no chain configs") - } - // all nodes should have an evm chain config, specifically the registry chain - evmCC, err := registryChainConfig(n.ChainConfigs, v1.ChainType_CHAIN_TYPE_EVM, registryChainSel) - if err != nil { - return nil, fmt.Errorf("failed to get registry chain config for sel %d: %w", registryChainSel, err) - } - cfgs := map[chaintype.ChainType]*v1.ChainConfig{ - chaintype.EVM: evmCC, - } - aptosCC, exists := firstChainConfigByType(n.ChainConfigs, v1.ChainType_CHAIN_TYPE_APTOS) - if exists { - cfgs[chaintype.Aptos] = aptosCC - } - return newOcr2Node(n.ID, cfgs, *n.PublicKey) +func newOcr2NodeFromJD(n *deployment.Node, registryChainSel uint64) (*ocr2Node, error) { + return newOcr2Node(n, registryChainSel) } -func ExtractKeys(n *Node, registerChainSel uint64) (p2p p2pkey.PeerID, signer [32]byte, encPubKey [32]byte, err error) { +func ExtractKeys(n *deployment.Node, registerChainSel uint64) (p2p p2pkey.PeerID, signer [32]byte, encPubKey [32]byte, err error) { orc2n, err := newOcr2NodeFromJD(n, registerChainSel) if err != nil { - return p2p, signer, encPubKey, fmt.Errorf("failed to create ocr2 node for node %s: %w", n.ID, err) + return p2p, signer, encPubKey, fmt.Errorf("failed to create ocr2 node for node %s: %w", n.NodeID, err) } return orc2n.P2PKey, orc2n.Signer, orc2n.EncryptionPublicKey, nil } -func newOcr2Node(id string, ccfgs map[chaintype.ChainType]*v1.ChainConfig, csaPubKey string) (*ocr2Node, error) { - if ccfgs == nil { - return nil, errors.New("nil ocr2config") +func newOcr2Node(node *deployment.Node, registryChainSel uint64) (*ocr2Node, error) { + if node.CSAKey == "" { + return nil, errors.New("no public key") } - evmCC, exists := ccfgs[chaintype.EVM] - if !exists { - return nil, errors.New("no evm chain config for node id " + id) + // the chain configs are equivalent as far as the ocr2 config is concerned so take the first one + if len(node.SelToOCRConfig) == 0 { + return nil, errors.New("no chain configs") } - - if csaPubKey == "" { - return nil, errors.New("empty csa public key") + evmCC, ok := node.SelToOCRConfig[registryChainSel] + if !ok { + return nil, fmt.Errorf("failed to get registry chain config for sel %d", registryChainSel) } - // parse csapublic key to - csaKey, err := hex.DecodeString(csaPubKey) + // parse csapublic key + csaKey, err := hex.DecodeString(node.CSAKey) if err != nil { - return nil, fmt.Errorf("failed to decode csa public key %s: %w", csaPubKey, err) + return nil, fmt.Errorf("failed to decode csa public key %s: %w", node.CSAKey, err) } if len(csaKey) != 32 { - return nil, fmt.Errorf("invalid csa public key '%s'. expected len 32 got %d", csaPubKey, len(csaKey)) + return nil, fmt.Errorf("invalid csa public key '%s'. expected len 32 got %d", node.CSAKey, len(csaKey)) } var csaKeyb [32]byte copy(csaKeyb[:], csaKey) - ocfg := evmCC.Ocr2Config - p := p2pkey.PeerID{} - if err := p.UnmarshalString(ocfg.P2PKeyBundle.PeerId); err != nil { - return nil, fmt.Errorf("failed to unmarshal peer id %s: %w", ocfg.P2PKeyBundle.PeerId, err) - } - - signer := ocfg.OcrKeyBundle.OnchainSigningAddress - if len(signer) != 40 { - return nil, fmt.Errorf("invalid onchain signing address %s", ocfg.OcrKeyBundle.OnchainSigningAddress) + signer := evmCC.OnchainPublicKey + if len(signer) != 20 { + return nil, fmt.Errorf("invalid onchain signing address %s", signer) } - signerB, err := hex.DecodeString(signer) - if err != nil { - return nil, fmt.Errorf("failed to convert signer %s: %w", signer, err) - } - var sigb [32]byte - copy(sigb[:], signerB) + copy(sigb[:], signer) n := &ocr2Node{ - ID: id, + ID: node.NodeID, Signer: sigb, - P2PKey: p, + P2PKey: evmCC.PeerID, EncryptionPublicKey: csaKeyb, - IsBoostrap: ocfg.IsBootstrap, - p2pKeyBundle: ocfg.P2PKeyBundle, - ethOcr2KeyBundle: evmCC.Ocr2Config.OcrKeyBundle, - aptosOcr2KeyBundle: nil, - accountAddress: evmCC.AccountAddress, - csaKey: csaPubKey, + IsBoostrap: node.IsBootstrap, + ethOcr2KeyBundle: &v1.OCR2Config_OCRKeyBundle{ + BundleId: evmCC.KeyBundleID, + ConfigPublicKey: fmt.Sprintf("%x", evmCC.ConfigEncryptionPublicKey[:]), + OffchainPublicKey: fmt.Sprintf("%x", evmCC.OffchainPublicKey[:]), + OnchainSigningAddress: fmt.Sprintf("%x", evmCC.OnchainPublicKey[:]), + }, + aptosOcr2KeyBundle: nil, + accountAddress: string(evmCC.TransmitAccount), + csaKey: node.CSAKey, + } + + var aptosCC *deployment.OCRConfig + for sel, cfg := range node.SelToOCRConfig { + if family, err := chainsel.GetSelectorFamily(sel); err == nil && family == chainsel.FamilyAptos { + aptosCC = &cfg + break + } } // aptos chain config is optional - if aptosCC, exists := ccfgs[chaintype.Aptos]; exists { - n.aptosOcr2KeyBundle = aptosCC.Ocr2Config.OcrKeyBundle + if aptosCC != nil { + n.aptosOcr2KeyBundle = &v1.OCR2Config_OCRKeyBundle{ + BundleId: aptosCC.KeyBundleID, + OnchainSigningAddress: fmt.Sprintf("%x", evmCC.OnchainPublicKey[:]), + } } return n, nil @@ -255,21 +237,6 @@ func NodeOperator(name string, adminAddress string) capabilities_registry.Capabi } } -func AdminAddress(n *Node, chainSel uint64) (string, error) { - cid, err := chainsel.ChainIdFromSelector(chainSel) - if err != nil { - return "", fmt.Errorf("failed to get chain id from selector %d: %w", chainSel, err) - } - cidStr := strconv.FormatUint(cid, 10) - for _, chain := range n.ChainConfigs { - //TODO validate chainType field - if chain.Chain.Id == cidStr { - return chain.AdminAddress, nil - } - } - return "", fmt.Errorf("no chain config for chain %d", cid) -} - func nopsToNodes(donInfos []DonInfo, dons []DonCapabilities, chainSelector uint64) (map[capabilities_registry.CapabilitiesRegistryNodeOperator][]string, error) { out := make(map[capabilities_registry.CapabilitiesRegistryNodeOperator][]string) for _, don := range dons { @@ -281,28 +248,23 @@ func nopsToNodes(donInfos []DonInfo, dons []DonCapabilities, chainSelector uint6 return nil, fmt.Errorf("couldn't find donInfo for %v", don.Name) } donInfo := donInfos[idx] - idx = slices.IndexFunc(donInfo.Nodes, func(node Node) bool { - return node.P2PID == nop.Nodes[0] + idx = slices.IndexFunc(donInfo.Nodes, func(node deployment.Node) bool { + return node.PeerID.String() == nop.Nodes[0] }) if idx < 0 { return nil, fmt.Errorf("couldn't find node with p2p_id %v", nop.Nodes[0]) } node := donInfo.Nodes[idx] - a, err := AdminAddress(&node, chainSelector) - if err != nil { - return nil, fmt.Errorf("failed to get admin address for node %s: %w", node.ID, err) - } + a := node.AdminAddr nodeOperator := NodeOperator(nop.Name, a) for _, node := range nop.Nodes { - - idx = slices.IndexFunc(donInfo.Nodes, func(n Node) bool { - return n.P2PID == node + idx = slices.IndexFunc(donInfo.Nodes, func(n deployment.Node) bool { + return n.PeerID.String() == node }) if idx < 0 { return nil, fmt.Errorf("couldn't find node with p2p_id %v", node) } - out[nodeOperator] = append(out[nodeOperator], donInfo.Nodes[idx].ID) - + out[nodeOperator] = append(out[nodeOperator], donInfo.Nodes[idx].NodeID) } } } @@ -330,7 +292,7 @@ func mapDonsToNodes(dons []DonInfo, excludeBootstraps bool, registryChainSel uin for _, node := range don.Nodes { ocr2n, err := newOcr2NodeFromJD(&node, registryChainSel) if err != nil { - return nil, fmt.Errorf("failed to create ocr2 node for node %s: %w", node.ID, err) + return nil, fmt.Errorf("failed to create ocr2 node for node %s: %w", node.NodeID, err) } if excludeBootstraps && ocr2n.IsBoostrap { continue @@ -345,29 +307,6 @@ func mapDonsToNodes(dons []DonInfo, excludeBootstraps bool, registryChainSel uin return donToOcr2Nodes, nil } -func firstChainConfigByType(ccfgs []*v1.ChainConfig, t v1.ChainType) (*v1.ChainConfig, bool) { - for _, c := range ccfgs { - if c.Chain.Type == t { - return c, true - } - } - return nil, false -} - -func registryChainConfig(ccfgs []*v1.ChainConfig, t v1.ChainType, sel uint64) (*v1.ChainConfig, error) { - chainId, err := chainsel.ChainIdFromSelector(sel) - if err != nil { - return nil, fmt.Errorf("failed to get chain id from selector %d: %w", sel, err) - } - chainIdStr := strconv.FormatUint(chainId, 10) - for _, c := range ccfgs { - if c.Chain.Type == t && c.Chain.Id == chainIdStr { - return c, nil - } - } - return nil, fmt.Errorf("no chain config for chain %d", chainId) -} - // RegisteredDon is a representation of a don that exists in the in the capabilities registry all with the enriched node data type RegisteredDon struct { Name string diff --git a/deployment/keystone/types_test.go b/deployment/keystone/types_test.go deleted file mode 100644 index 925649bba0d..00000000000 --- a/deployment/keystone/types_test.go +++ /dev/null @@ -1,402 +0,0 @@ -package keystone - -import ( - "testing" - - "github.com/stretchr/testify/assert" - - v1 "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/node" - "github.com/smartcontractkit/chainlink/v2/core/services/keystore/chaintype" -) - -func Test_newOcr2Node(t *testing.T) { - type args struct { - id string - ccfgs map[chaintype.ChainType]*v1.ChainConfig - csaPubKey string - } - tests := []struct { - name string - args args - wantAptos bool - wantErr bool - }{ - { - name: "no aptos", - args: args{ - id: "1", - ccfgs: map[chaintype.ChainType]*v1.ChainConfig{ - chaintype.EVM: { - - Ocr2Config: &v1.OCR2Config{ - P2PKeyBundle: &v1.OCR2Config_P2PKeyBundle{ - PeerId: "p2p_12D3KooWMWUKdoAc2ruZf9f55p7NVFj7AFiPm67xjQ8BZBwkqyYv", - PublicKey: "pubKey", - }, - OcrKeyBundle: &v1.OCR2Config_OCRKeyBundle{ - BundleId: "bundleId", - ConfigPublicKey: "03dacd15fc96c965c648e3623180de002b71a97cf6eeca9affb91f461dcd6ce1", - OffchainPublicKey: "03dacd15fc96c965c648e3623180de002b71a97cf6eeca9affb91f461dcd6ce1", - OnchainSigningAddress: "b35409a8d4f9a18da55c5b2bb08a3f5f68d44442", - }, - }, - }, - }, - csaPubKey: "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", - }, - }, - { - name: "with aptos", - args: args{ - id: "1", - ccfgs: map[chaintype.ChainType]*v1.ChainConfig{ - chaintype.EVM: { - - Ocr2Config: &v1.OCR2Config{ - P2PKeyBundle: &v1.OCR2Config_P2PKeyBundle{ - PeerId: "p2p_12D3KooWMWUKdoAc2ruZf9f55p7NVFj7AFiPm67xjQ8BZBwkqyYv", - PublicKey: "pubKey", - }, - OcrKeyBundle: &v1.OCR2Config_OCRKeyBundle{ - BundleId: "bundleId", - ConfigPublicKey: "03dacd15fc96c965c648e3623180de002b71a97cf6eeca9affb91f461dcd6ce1", - OffchainPublicKey: "03dacd15fc96c965c648e3623180de002b71a97cf6eeca9affb91f461dcd6ce1", - OnchainSigningAddress: "b35409a8d4f9a18da55c5b2bb08a3f5f68d44442", - }, - }, - }, - chaintype.Aptos: { - - Ocr2Config: &v1.OCR2Config{ - P2PKeyBundle: &v1.OCR2Config_P2PKeyBundle{ - PeerId: "p2p_12D3KooWMWUKdoAc2ruZf9f55p7NVFj7AFiPm67xjQ8BZB11111", - PublicKey: "pubKey", - }, - OcrKeyBundle: &v1.OCR2Config_OCRKeyBundle{ - BundleId: "bundleId2", - ConfigPublicKey: "0000015fc96c965c648e3623180de002b71a97cf6eeca9affb91f461dcd6ce1", - OffchainPublicKey: "03dacd15fc96c965c648e3623180de002b71a97cf6eeca9affb91f461dcd6ce1", - OnchainSigningAddress: "111409a8d4f9a18da55c5b2bb08a3f5f68d44777", - }, - }, - }, - }, - csaPubKey: "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", - }, - wantAptos: true, - }, - { - name: "bad csa key", - args: args{ - id: "1", - ccfgs: map[chaintype.ChainType]*v1.ChainConfig{ - chaintype.EVM: { - - Ocr2Config: &v1.OCR2Config{ - P2PKeyBundle: &v1.OCR2Config_P2PKeyBundle{ - PeerId: "p2p_12D3KooWMWUKdoAc2ruZf9f55p7NVFj7AFiPm67xjQ8BZBwkqyYv", - PublicKey: "pubKey", - }, - OcrKeyBundle: &v1.OCR2Config_OCRKeyBundle{ - BundleId: "bundleId", - ConfigPublicKey: "03dacd15fc96c965c648e3623180de002b71a97cf6eeca9affb91f461dcd6ce1", - OffchainPublicKey: "03dacd15fc96c965c648e3623180de002b71a97cf6eeca9affb91f461dcd6ce1", - OnchainSigningAddress: "b35409a8d4f9a18da55c5b2bb08a3f5f68d44442", - }, - }, - }, - }, - csaPubKey: "not hex", - }, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := newOcr2Node(tt.args.id, tt.args.ccfgs, tt.args.csaPubKey) - if (err != nil) != tt.wantErr { - t.Errorf("newOcr2Node() error = %v, wantErr %v", err, tt.wantErr) - return - } - if tt.wantErr { - return - } - assert.NotNil(t, got.ethOcr2KeyBundle) - assert.NotNil(t, got.p2pKeyBundle) - assert.NotNil(t, got.Signer) - assert.NotNil(t, got.EncryptionPublicKey) - assert.NotEmpty(t, got.csaKey) - assert.NotEmpty(t, got.P2PKey) - assert.Equal(t, tt.wantAptos, got.aptosOcr2KeyBundle != nil) - }) - } -} - -// func Test_mapDonsToNodes(t *testing.T) { -// var ( -// pubKey = "03dacd15fc96c965c648e3623180de002b71a97cf6eeca9affb91f461dcd6ce1" -// evmSig = "b35409a8d4f9a18da55c5b2bb08a3f5f68d44442" -// aptosSig = "b35409a8d4f9a18da55c5b2bb08a3f5f68d44442b35409a8d4f9a18da55c5b2bb08a3f5f68d44442" -// peerID = "p2p_12D3KooWMWUKdoAc2ruZf9f55p7NVFj7AFiPm67xjQ8BZBwkqyYv" -// // todo: these should be defined in common -// writerCap = 3 -// ocr3Cap = 2 -// registryChainSel = chainsel.ETHEREUM_TESTNET_SEPOLIA.Selector -// registryChainID = strconv.FormatUint(chainsel.ETHEREUM_TESTNET_SEPOLIA.EvmChainID, 10) -// ) -// type args struct { -// dons []DonCapabilities -// excludeBootstraps bool -// } -// tests := []struct { -// name string -// args args -// wantErr bool -// }{ -// { -// name: "writer evm only", -// args: args{ -// dons: []DonCapabilities{ -// { -// Name: "ok writer", -// Nops: []*models.NodeOperator{ -// { -// Nodes: []*models.Node{ -// { -// PublicKey: &pubKey, -// ChainConfigs: []*models.NodeChainConfig{ -// { -// ID: "1", -// Network: &models.Network{ -// ChainType: models.ChainTypeEvm, -// ChainID: registryChainID, -// }, -// Ocr2Config: &models.NodeOCR2Config{ -// P2pKeyBundle: &models.NodeOCR2ConfigP2PKeyBundle{ -// PeerID: peerID, -// }, -// OcrKeyBundle: &models.NodeOCR2ConfigOCRKeyBundle{ -// ConfigPublicKey: pubKey, -// OffchainPublicKey: pubKey, -// OnchainSigningAddress: evmSig, -// }, -// }, -// }, -// }, -// }, -// }, -// }, -// }, -// Capabilities: []kcr.CapabilitiesRegistryCapability{ -// { -// LabelledName: "writer", -// Version: "1", -// CapabilityType: uint8(writerCap), -// }, -// }, -// }, -// }, -// }, -// wantErr: false, -// }, -// { -// name: "err if no evm chain", -// args: args{ -// dons: []DonCapabilities{ -// { -// Name: "bad chain", -// Nops: []*models.NodeOperator{ -// { -// Nodes: []*models.Node{ -// { -// PublicKey: &pubKey, -// ChainConfigs: []*models.NodeChainConfig{ -// { -// ID: "1", -// Network: &models.Network{ -// ChainType: models.ChainTypeSolana, -// }, -// Ocr2Config: &models.NodeOCR2Config{ -// P2pKeyBundle: &models.NodeOCR2ConfigP2PKeyBundle{ -// PeerID: peerID, -// }, -// OcrKeyBundle: &models.NodeOCR2ConfigOCRKeyBundle{ -// ConfigPublicKey: pubKey, -// OffchainPublicKey: pubKey, -// OnchainSigningAddress: evmSig, -// }, -// }, -// }, -// }, -// }, -// }, -// }, -// }, -// Capabilities: []kcr.CapabilitiesRegistryCapability{ -// { -// LabelledName: "writer", -// Version: "1", -// CapabilityType: uint8(writerCap), -// }, -// }, -// }, -// }, -// }, -// wantErr: true, -// }, -// { -// name: "ocr3 cap evm only", -// args: args{ -// dons: []DonCapabilities{ -// { -// Name: "bad chain", -// Nops: []*models.NodeOperator{ -// { -// Nodes: []*models.Node{ -// { -// PublicKey: &pubKey, -// ChainConfigs: []*models.NodeChainConfig{ -// { -// ID: "1", -// Network: &models.Network{ -// ChainType: models.ChainTypeEvm, -// ChainID: registryChainID, -// }, -// Ocr2Config: &models.NodeOCR2Config{ -// P2pKeyBundle: &models.NodeOCR2ConfigP2PKeyBundle{ -// PeerID: peerID, -// }, -// OcrKeyBundle: &models.NodeOCR2ConfigOCRKeyBundle{ -// ConfigPublicKey: pubKey, -// OffchainPublicKey: pubKey, -// OnchainSigningAddress: evmSig, -// }, -// }, -// }, -// }, -// }, -// }, -// }, -// }, -// Capabilities: []kcr.CapabilitiesRegistryCapability{ -// { -// LabelledName: "ocr3", -// Version: "1", -// CapabilityType: uint8(ocr3Cap), -// }, -// }, -// }, -// }, -// }, -// wantErr: false, -// }, -// { -// name: "ocr3 cap evm & aptos", -// args: args{ -// dons: []DonCapabilities{ -// { -// Name: "ok chain", -// Nops: []*models.NodeOperator{ -// { -// Nodes: []*models.Node{ -// { -// PublicKey: &pubKey, -// ChainConfigs: []*models.NodeChainConfig{ -// { -// ID: "1", -// Network: &models.Network{ -// ChainType: models.ChainTypeEvm, -// ChainID: registryChainID, -// }, -// Ocr2Config: &models.NodeOCR2Config{ -// P2pKeyBundle: &models.NodeOCR2ConfigP2PKeyBundle{ -// PeerID: peerID, -// }, -// OcrKeyBundle: &models.NodeOCR2ConfigOCRKeyBundle{ -// ConfigPublicKey: pubKey, -// OffchainPublicKey: pubKey, -// OnchainSigningAddress: evmSig, -// }, -// }, -// }, -// { -// ID: "2", -// Network: &models.Network{ -// ChainType: models.ChainTypeAptos, -// }, -// Ocr2Config: &models.NodeOCR2Config{ -// P2pKeyBundle: &models.NodeOCR2ConfigP2PKeyBundle{ -// PeerID: peerID, -// }, -// OcrKeyBundle: &models.NodeOCR2ConfigOCRKeyBundle{ -// ConfigPublicKey: pubKey, -// OffchainPublicKey: pubKey, -// OnchainSigningAddress: aptosSig, -// }, -// }, -// }, -// }, -// }, -// }, -// }, -// }, -// Capabilities: []kcr.CapabilitiesRegistryCapability{ -// { -// LabelledName: "ocr3", -// Version: "1", -// CapabilityType: uint8(ocr3Cap), -// }, -// }, -// }, -// }, -// }, -// wantErr: false, -// }, -// } -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// _, err := mapDonsToNodes(tt.args.dons, tt.args.excludeBootstraps, registryChainSel) -// if (err != nil) != tt.wantErr { -// t.Errorf("mapDonsToNodes() error = %v, wantErr %v", err, tt.wantErr) -// return -// } -// }) -// } -// // make sure the clo test data is correct -// wfNops := loadTestNops(t, "testdata/workflow_nodes.json") -// cwNops := loadTestNops(t, "testdata/chain_writer_nodes.json") -// assetNops := loadTestNops(t, "testdata/asset_nodes.json") -// require.Len(t, wfNops, 10) -// require.Len(t, cwNops, 10) -// require.Len(t, assetNops, 16) - -// wfDon := DonCapabilities{ -// Name: WFDonName, -// Nops: wfNops, -// Capabilities: []kcr.CapabilitiesRegistryCapability{OCR3Cap}, -// } -// cwDon := DonCapabilities{ -// Name: TargetDonName, -// Nops: cwNops, -// Capabilities: []kcr.CapabilitiesRegistryCapability{WriteChainCap}, -// } -// assetDon := DonCapabilities{ -// Name: StreamDonName, -// Nops: assetNops, -// Capabilities: []kcr.CapabilitiesRegistryCapability{StreamTriggerCap}, -// } -// _, err := mapDonsToNodes([]DonCapabilities{wfDon}, false, registryChainSel) -// require.NoError(t, err, "failed to map wf don") -// _, err = mapDonsToNodes([]DonCapabilities{cwDon}, false, registryChainSel) -// require.NoError(t, err, "failed to map cw don") -// _, err = mapDonsToNodes([]DonCapabilities{assetDon}, false, registryChainSel) -// require.NoError(t, err, "failed to map asset don") -// } - -// func loadTestNops(t *testing.T, pth string) []*models.NodeOperator { -// f, err := os.ReadFile(pth) -// require.NoError(t, err) -// var nops []*models.NodeOperator -// require.NoError(t, json.Unmarshal(f, &nops)) -// return nops -// }