From a2678250f20b2ab55f0b994e3032a284fc05b2db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Sat, 23 Nov 2024 05:48:38 +0900 Subject: [PATCH] Replace NodesFromJD with NodeInfo (#15256) * 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 <16602512+krehermann@users.noreply.github.com> --- deployment/environment.go | 88 +++- deployment/environment/memory/job_client.go | 15 +- deployment/environment/memory/node.go | 4 + .../changeset/internal/update_don_test.go | 90 ++-- deployment/keystone/changeset/types.go | 43 -- .../changeset/update_node_capabilities.go | 26 +- deployment/keystone/deploy.go | 200 ++------ deployment/keystone/deploy_test.go | 25 +- deployment/keystone/ocr3config.go | 49 ++ deployment/keystone/ocr3config_test.go | 178 +++++++ .../keystone/testdata/testnet_wf_view.json | 262 ++++++++++ deployment/keystone/types.go | 271 +++-------- deployment/keystone/types_test.go | 452 +++--------------- 13 files changed, 838 insertions(+), 865 deletions(-) delete mode 100644 deployment/keystone/changeset/types.go create mode 100644 deployment/keystone/ocr3config_test.go create mode 100644 deployment/keystone/testdata/testnet_wf_view.json diff --git a/deployment/environment.go b/deployment/environment.go index b9f3700bdc4..c55f4d3efe6 100644 --- a/deployment/environment.go +++ b/deployment/environment.go @@ -7,6 +7,7 @@ import ( "fmt" "math/big" "sort" + "strings" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" @@ -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" ) @@ -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) { @@ -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 @@ -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? @@ -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, }) } diff --git a/deployment/environment/memory/job_client.go b/deployment/environment/memory/job_client.go index 3d98b1f3f8d..98fb90ceffa 100644 --- a/deployment/environment/memory/job_client.go +++ b/deployment/environment/memory/job_client.go @@ -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: @@ -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] @@ -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 diff --git a/deployment/environment/memory/node.go b/deployment/environment/memory/node.go index c2e4e457fbd..fd08d3cf17b 100644 --- a/deployment/environment/memory/node.go +++ b/deployment/environment/memory/node.go @@ -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) diff --git a/deployment/keystone/changeset/internal/update_don_test.go b/deployment/keystone/changeset/internal/update_don_test.go index 12ccfe290b1..e500ade60d7 100644 --- a/deployment/keystone/changeset/internal/update_don_test.go +++ b/deployment/keystone/changeset/internal/update_don_test.go @@ -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" @@ -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 @@ -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}, }, }, } @@ -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, }, }, } @@ -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) @@ -231,7 +245,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 +253,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 +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) } } @@ -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) } @@ -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, - } -} 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..1d6dde6af5a 100644 --- a/deployment/keystone/changeset/update_node_capabilities.go +++ b/deployment/keystone/changeset/update_node_capabilities.go @@ -3,11 +3,11 @@ package changeset import ( "encoding/json" "fmt" + "strconv" 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,15 +19,29 @@ var _ deployment.ChangeSet[*MutateNodeCapabilitiesRequest] = UpdateNodeCapabilit type P2PSignerEnc = internal.P2PSignerEnc -func NewP2PSignerEnc(n *keystone.Node, registryChainSel uint64) (*P2PSignerEnc, error) { - p2p, signer, enc, err := kslib.ExtractKeys(n, registryChainSel) +func NewP2PSignerEnc(n *deployment.Node, registryChainSel uint64) (*P2PSignerEnc, error) { + // TODO: deduplicate everywhere + registryChainID, err := chainsel.ChainIdFromSelector(registryChainSel) if err != nil { - return nil, fmt.Errorf("failed to extract keys: %w", err) + return nil, err } + registryChainDetails, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.Itoa(int(registryChainID)), chainsel.FamilyEVM) + if err != nil { + return nil, err + } + evmCC, exists := n.SelToOCRConfig[registryChainDetails] + if !exists { + return nil, fmt.Errorf("NewP2PSignerEnc: registryChainSel not found on node: %v", registryChainSel) + } + var signer [32]byte + copy(signer[:], evmCC.OnchainPublicKey) + var csakey [32]byte + copy(csakey[:], evmCC.ConfigEncryptionPublicKey[:]) + return &P2PSignerEnc{ Signer: signer, - P2PKey: p2p, - EncryptionPublicKey: enc, + P2PKey: n.PeerID, + EncryptionPublicKey: csakey, }, nil } diff --git a/deployment/keystone/deploy.go b/deployment/keystone/deploy.go index 3019f934a96..874b98600ae 100644 --- a/deployment/keystone/deploy.go +++ b/deployment/keystone/deploy.go @@ -7,8 +7,8 @@ import ( "encoding/hex" "errors" "fmt" - "slices" "sort" + "strconv" "strings" "time" @@ -16,8 +16,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" @@ -30,7 +28,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" kf "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/forwarder" - kocr3 "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/ocr3_capability" "github.com/smartcontractkit/chainlink-common/pkg/logger" ) @@ -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 } @@ -277,7 +206,7 @@ func ConfigureRegistry(ctx context.Context, lggr logger.Logger, req ConfigureCon // all the subsequent calls to the registry are in terms of nodes // compute the mapping of dons to their nodes for reuse in various registry calls - donToOcr2Nodes, err := mapDonsToNodes(donInfos, true, req.RegistryChainSel) + donToNodes, err := mapDonsToNodes(donInfos, true, req.RegistryChainSel) if err != nil { return nil, fmt.Errorf("failed to map dons to nodes: %w", err) } @@ -318,7 +247,7 @@ func ConfigureRegistry(ctx context.Context, lggr logger.Logger, req ConfigureCon registry: registry, chain: registryChain, nopToNodeIDs: nopsToNodeIDs, - donToOcr2Nodes: donToOcr2Nodes, + donToNodes: donToNodes, donToCapabilities: capabilitiesResp.donToCapabilities, nops: nopsResp.Nops, }) @@ -335,7 +264,7 @@ func ConfigureRegistry(ctx context.Context, lggr logger.Logger, req ConfigureCon chain: registryChain, nodeIDToParams: nodesResp.nodeIDToParams, donToCapabilities: capabilitiesResp.donToCapabilities, - donToOcr2Nodes: donToOcr2Nodes, + donToNodes: donToNodes, }) if err != nil { return nil, fmt.Errorf("failed to register DONS: %w", err) @@ -459,25 +388,20 @@ func ConfigureOCR3ContractFromJD(env *deployment.Environment, cfg ConfigureOCR3C if contract == nil { return nil, fmt.Errorf("no ocr3 contract found for chain %d", cfg.ChainSel) } - nodes, err := NodesFromJD("nodes", cfg.NodeIDs, env.Offchain) + nodes, err := deployment.NodeInfo(cfg.NodeIDs, env.Offchain) if err != nil { return nil, err } - var ocr2nodes []*ocr2Node - for _, node := range nodes { - n, err := newOcr2NodeFromJD(&node, cfg.ChainSel) - if err != nil { - return nil, fmt.Errorf("failed to create ocr2 node from clo node %v: %w", node, err) - } - ocr2nodes = append(ocr2nodes, n) - } r, err := configureOCR3contract(configureOCR3Request{ cfg: cfg.OCR3Config, chain: registryChain, contract: contract, - nodes: ocr2nodes, + nodes: nodes, dryRun: cfg.DryRun, }) + if err != nil { + return nil, err + } return &ConfigureOCR3Resp{ OCR2OracleConfig: r.ocrConfig, }, nil @@ -679,7 +603,7 @@ type registerNodesRequest struct { registry *kcr.CapabilitiesRegistry chain deployment.Chain nopToNodeIDs map[kcr.CapabilitiesRegistryNodeOperator][]string - donToOcr2Nodes map[string][]*ocr2Node + donToNodes map[string][]deployment.Node donToCapabilities map[string][]RegisteredCapability nops []*kcr.CapabilitiesRegistryNodeOperatorAdded } @@ -711,8 +635,18 @@ func registerNodes(lggr logger.Logger, req *registerNodesRequest) (*registerNode } } + // TODO: deduplicate everywhere + registryChainID, err := chainsel.ChainIdFromSelector(req.chain.Selector) + if err != nil { + return nil, err + } + registryChainDetails, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.Itoa(int(registryChainID)), chainsel.FamilyEVM) + if err != nil { + return nil, err + } + nodeIDToParams := make(map[string]kcr.CapabilitiesRegistryNodeParams) - for don, ocr2nodes := range req.donToOcr2Nodes { + for don, nodes := range req.donToNodes { caps, ok := req.donToCapabilities[don] if !ok { return nil, fmt.Errorf("capabilities not found for node operator %s", don) @@ -723,22 +657,30 @@ func registerNodes(lggr logger.Logger, req *registerNodesRequest) (*registerNode } lggr.Debugw("hashed capability ids", "don", don, "ids", hashedCapabilityIds) - for _, n := range ocr2nodes { - if n.IsBoostrap { // bootstraps are part of the DON but don't host capabilities + for _, n := range nodes { + if n.IsBootstrap { // bootstraps are part of the DON but don't host capabilities continue } - nop, ok := nodeToRegisterNop[n.ID] + nop, ok := nodeToRegisterNop[n.NodeID] if !ok { - return nil, fmt.Errorf("node operator not found for node %s", n.ID) + return nil, fmt.Errorf("node operator not found for node %s", n.NodeID) } - params, ok := nodeIDToParams[n.ID] + params, ok := nodeIDToParams[n.NodeID] if !ok { + evmCC, exists := n.SelToOCRConfig[registryChainDetails] + if !exists { + return nil, fmt.Errorf("config for selector not found on node: %v", req.chain.Selector) + } + var signer [32]byte + copy(signer[:], evmCC.OnchainPublicKey) + var csakey [32]byte + copy(csakey[:], evmCC.ConfigEncryptionPublicKey[:]) params = kcr.CapabilitiesRegistryNodeParams{ NodeOperatorId: nop.NodeOperatorId, - Signer: n.Signer, - P2pId: n.P2PKey, - EncryptionPublicKey: n.EncryptionPublicKey, + Signer: signer, + P2pId: n.PeerID, + EncryptionPublicKey: csakey, HashedCapabilityIds: hashedCapabilityIds, } } else { @@ -758,7 +700,7 @@ func registerNodes(lggr logger.Logger, req *registerNodesRequest) (*registerNode } params.HashedCapabilityIds = append(params.HashedCapabilityIds, newCapIds...) } - nodeIDToParams[n.ID] = params + nodeIDToParams[n.NodeID] = params } } @@ -811,7 +753,7 @@ type registerDonsRequest struct { nodeIDToParams map[string]kcr.CapabilitiesRegistryNodeParams donToCapabilities map[string][]RegisteredCapability - donToOcr2Nodes map[string][]*ocr2Node + donToNodes map[string][]deployment.Node } type registerDonsResponse struct { @@ -830,7 +772,7 @@ func sortedHash(p2pids [][32]byte) string { } func registerDons(lggr logger.Logger, req registerDonsRequest) (*registerDonsResponse, error) { - lggr.Infow("registering DONs...", "len", len(req.donToOcr2Nodes)) + lggr.Infow("registering DONs...", "len", len(req.donToNodes)) // track hash of sorted p2pids to don name because the registry return value does not include the don name // and we need to map it back to the don name to access the other mapping data such as the don's capabilities & nodes p2pIdsToDon := make(map[string]string) @@ -847,15 +789,15 @@ func registerDons(lggr logger.Logger, req registerDonsRequest) (*registerDonsRes } lggr.Infow("fetched existing DONs...", "len", len(donInfos), "lenByNodesHash", len(existingDONs)) - for don, ocr2nodes := range req.donToOcr2Nodes { + for don, nodes := range req.donToNodes { var p2pIds [][32]byte - for _, n := range ocr2nodes { - if n.IsBoostrap { + for _, n := range nodes { + if n.IsBootstrap { continue } - params, ok := req.nodeIDToParams[n.ID] + params, ok := req.nodeIDToParams[n.NodeID] if !ok { - return nil, fmt.Errorf("node params not found for non-bootstrap node %s", n.ID) + return nil, fmt.Errorf("node params not found for non-bootstrap node %s", n.NodeID) } p2pIds = append(p2pIds, params.P2pId) } @@ -965,7 +907,8 @@ func configureForwarder(lggr logger.Logger, chain deployment.Chain, fwdr *kf.Key continue } ver := dn.Info.ConfigCount // note config count on the don info is the version on the forwarder - tx, err := fwdr.SetConfig(chain.DeployerKey, dn.Info.Id, ver, dn.Info.F, dn.signers()) + signers := dn.signers(chainsel.FamilyEVM) + tx, err := fwdr.SetConfig(chain.DeployerKey, dn.Info.Id, ver, dn.Info.F, signers) if err != nil { err = DecodeErr(kf.KeystoneForwarderABI, err) return fmt.Errorf("failed to call SetConfig for forwarder %s on chain %d: %w", fwdr.Address().String(), chain.Selector, err) @@ -975,50 +918,7 @@ func configureForwarder(lggr logger.Logger, chain deployment.Chain, fwdr *kf.Key err = DecodeErr(kf.KeystoneForwarderABI, err) return fmt.Errorf("failed to confirm SetConfig for forwarder %s: %w", fwdr.Address().String(), err) } - lggr.Debugw("configured forwarder", "forwarder", fwdr.Address().String(), "donId", dn.Info.Id, "version", ver, "f", dn.Info.F, "signers", dn.signers()) + lggr.Debugw("configured forwarder", "forwarder", fwdr.Address().String(), "donId", dn.Info.Id, "version", ver, "f", dn.Info.F, "signers", signers) } return nil } - -type configureOCR3Request struct { - cfg *OracleConfigWithSecrets - chain deployment.Chain - contract *kocr3.OCR3Capability - nodes []*ocr2Node - dryRun bool -} -type configureOCR3Response struct { - ocrConfig OCR2OracleConfig -} - -func configureOCR3contract(req configureOCR3Request) (*configureOCR3Response, error) { - if req.contract == nil { - return nil, fmt.Errorf("OCR3 contract is nil") - } - nks := makeNodeKeysSlice(req.nodes) - ocrConfig, err := GenerateOCR3Config(*req.cfg, nks) - if err != nil { - return nil, fmt.Errorf("failed to generate OCR3 config: %w", err) - } - if req.dryRun { - return &configureOCR3Response{ocrConfig}, nil - } - tx, err := req.contract.SetConfig(req.chain.DeployerKey, - ocrConfig.Signers, - ocrConfig.Transmitters, - ocrConfig.F, - ocrConfig.OnchainConfig, - ocrConfig.OffchainConfigVersion, - ocrConfig.OffchainConfig, - ) - if err != nil { - err = DecodeErr(kocr3.OCR3CapabilityABI, err) - return nil, fmt.Errorf("failed to call SetConfig for OCR3 contract %s: %w", req.contract.Address().String(), err) - } - _, err = req.chain.Confirm(tx) - if err != nil { - err = DecodeErr(kocr3.OCR3CapabilityABI, err) - return nil, fmt.Errorf("failed to confirm SetConfig for OCR3 contract %s: %w", req.contract.Address().String(), err) - } - return &configureOCR3Response{ocrConfig}, nil -} diff --git a/deployment/keystone/deploy_test.go b/deployment/keystone/deploy_test.go index 4e0d2a52dcc..e446405944c 100644 --- a/deployment/keystone/deploy_test.go +++ b/deployment/keystone/deploy_test.go @@ -8,26 +8,24 @@ import ( "testing" "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/stretchr/testify/assert" - "github.com/test-go/testify/require" - "go.uber.org/zap/zapcore" - "golang.org/x/exp/maps" - chainsel "github.com/smartcontractkit/chain-selectors" - + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" - "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/deployment/environment/clo" "github.com/smartcontractkit/chainlink/deployment/environment/clo/models" "github.com/smartcontractkit/chainlink/deployment/environment/memory" "github.com/smartcontractkit/chainlink/deployment/keystone" kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" - "github.com/smartcontractkit/chainlink/v2/core/logger" + + "github.com/stretchr/testify/assert" + "github.com/test-go/testify/require" + "go.uber.org/zap/zapcore" + "golang.org/x/exp/maps" ) func TestDeploy(t *testing.T) { - lggr := logger.TestLogger(t) + lggr := logger.Test(t) // sepolia; all nodes are on the this chain sepoliaChainId := uint64(11155111) @@ -37,7 +35,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 +44,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) @@ -225,7 +224,7 @@ func nodeOperatorsToIDs(t *testing.T, nops []*models.NodeOperator) (nodeIDs []ke } func TestDeployCLO(t *testing.T) { - lggr := logger.TestLogger(t) + lggr := logger.Test(t) wfNops := loadTestNops(t, "testdata/workflow_nodes.json") cwNops := loadTestNops(t, "testdata/chain_writer_nodes.json") diff --git a/deployment/keystone/ocr3config.go b/deployment/keystone/ocr3config.go index 2c12ae3c596..a281a69ed8a 100644 --- a/deployment/keystone/ocr3config.go +++ b/deployment/keystone/ocr3config.go @@ -17,6 +17,7 @@ import ( "github.com/smartcontractkit/libocr/offchainreporting2plus/types" "github.com/smartcontractkit/chainlink/deployment" + kocr3 "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/ocr3_capability" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/chaintype" "github.com/smartcontractkit/chainlink/v2/core/services/ocrcommon" "github.com/smartcontractkit/chainlink/v2/core/services/relay/evm" @@ -237,3 +238,51 @@ func GenerateOCR3Config(cfg OracleConfigWithSecrets, nca []NodeKeys) (OCR2Oracle return config, nil } + +type configureOCR3Request struct { + cfg *OracleConfigWithSecrets + chain deployment.Chain + contract *kocr3.OCR3Capability + nodes []deployment.Node + dryRun bool +} + +func (r configureOCR3Request) generateOCR3Config() (OCR2OracleConfig, error) { + nks := makeNodeKeysSlice(r.nodes, r.chain.Selector) + return GenerateOCR3Config(*r.cfg, nks) +} + +type configureOCR3Response struct { + ocrConfig OCR2OracleConfig +} + +func configureOCR3contract(req configureOCR3Request) (*configureOCR3Response, error) { + if req.contract == nil { + return nil, fmt.Errorf("OCR3 contract is nil") + } + ocrConfig, err := req.generateOCR3Config() + if err != nil { + return nil, fmt.Errorf("failed to generate OCR3 config: %w", err) + } + if req.dryRun { + return &configureOCR3Response{ocrConfig}, nil + } + tx, err := req.contract.SetConfig(req.chain.DeployerKey, + ocrConfig.Signers, + ocrConfig.Transmitters, + ocrConfig.F, + ocrConfig.OnchainConfig, + ocrConfig.OffchainConfigVersion, + ocrConfig.OffchainConfig, + ) + if err != nil { + err = DecodeErr(kocr3.OCR3CapabilityABI, err) + return nil, fmt.Errorf("failed to call SetConfig for OCR3 contract %s: %w", req.contract.Address().String(), err) + } + _, err = req.chain.Confirm(tx) + if err != nil { + err = DecodeErr(kocr3.OCR3CapabilityABI, err) + return nil, fmt.Errorf("failed to confirm SetConfig for OCR3 contract %s: %w", req.contract.Address().String(), err) + } + return &configureOCR3Response{ocrConfig}, nil +} diff --git a/deployment/keystone/ocr3config_test.go b/deployment/keystone/ocr3config_test.go new file mode 100644 index 00000000000..4046787724a --- /dev/null +++ b/deployment/keystone/ocr3config_test.go @@ -0,0 +1,178 @@ +// TODO: KS-458 copied from https://github.com/smartcontractkit/chainlink/blob/65924811dc53a211613927c814d7f04fd85439a4/core/scripts/keystone/src/88_gen_ocr3_config.go#L1 +// to unblock go mod issues when trying to import the scripts package +package keystone + +import ( + "encoding/json" + "os" + "sort" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/common" + chain_selectors "github.com/smartcontractkit/chain-selectors" + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/common/view" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" + types2 "github.com/smartcontractkit/libocr/offchainreporting2/types" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + types3 "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/test-go/testify/require" +) + +var wantOCR3Config = `{ + "Signers": [ + "011400b35409a8d4f9a18da55c5b2bb08a3f5f68d44442052000b8834eaa062f0df4ccfe7832253920071ec14dc4f78b13ecdda10b824e2dd3b6", + "0114008258f4c4761cc445333017608044a204fd0c006a052000247d0189f65f58be83a4e7d87ff338aaf8956e9acb9fcc783f34f9edc29d1b40", + "011400d4dcc573e9d24a8b27a07bba670ba3a2ab36e5bb052000ba20d3da9b07663f1e8039081a514649fd61a48be2d241bc63537ee47d028fcd", + "0114006607c140e558631407f33bafbabd103863cee876052000046faf34ebfe42510251e6098bc34fa3dd5f2de38ac07e47f2d1b34ac770639f", + "011400a6f35436cb7bffd615cc47a0a04aa0a78696a1440520001221e131ef21014a6a99ed22376eb869746a3b5e30fd202cf79e44efaeb8c5c2", + "011400657587eb55cecd6f90b97297b611c3024e488cc0052000425d1354a7b8180252a221040c718cac0ba0251c7efe31a2acefbba578dc2153", + "0114004885973b2fcf061d5cdfb8f74c5139bd3056e9da0520004a94c75cb9fe8b1fba86fd4b71ad130943281fdefad10216c46eb2285d60950f", + "011400213803bb9f9715379aaf11aadb0212369701dc0a05200096dc85670c49caa986de4ad288e680e9afb0f5491160dcbb4868ca718e194fc8", + "0114008c2aa1e6fad88a6006dfb116eb866cbad2910314052000bddafb20cc50d89e0ae2f244908c27b1d639615d8186b28c357669de3359f208", + "011400679296b7c1eb4948efcc87efc550940a182e610c0520004fa557850e4d5c21b3963c97414c1f37792700c4d3b8abdb904b765fd47e39bf" + ], + "Transmitters": [ + "0x2877F08d9c5Cc9F401F730Fa418fAE563A9a2FF3", + "0x415aa1E9a1bcB3929ed92bFa1F9735Dc0D45AD31", + "0xCea84bC1881F3cE14BA13Dc3a00DC1Ff3D553fF0", + "0xA9eFB53c513E413762b2Be5299D161d8E6e7278e", + "0x6F5cAb24Fb7412bB516b3468b9F3a9c471d25fE5", + "0xdAd1F3F8ec690cf335D46c50EdA5547CeF875161", + "0x19e10B063a62B1574AE19020A64fDe6419892dA6", + "0x9ad9f3AD49e5aB0F28bD694d211a90297bD90D7f", + "0x31B179dcF8f9036C30f04bE578793e51bF14A39E", + "0x0b04cE574E80Da73191Ec141c0016a54A6404056" + ], + "F": 3, + "OnchainConfig": "0x", + "OffchainConfigVersion": 30, + "OffchainConfig": "0xc80180e497d012d00180e497d012d80180a8d6b907e00180cab5ee01e80180d88ee16ff0010afa01010a82022003dacd15fc96c965c648e3623180de002b71a97cf6eeca9affb91f461dcd6ce1820220255096a3b7ade10e29c648e0b407fc486180464f713446b1da04f013df6179c8820220dba3c61e5f8bec594be481bcaf67ecea0d1c2950edb15b158ce3dbc77877def3820220b4c4993d6c15fee63800db901a8b35fa419057610962caab1c1d7bed557091278202202a4c7dec127fdd8145e48c5edb9467225098bd8c8ad1dade868325b787affbde820220283471ed66d61fbe11f64eff65d738b59a0301c9a4f846280db26c64c9fdd3f8820220aa3419628ea3536783742d17d8adf05681aa6a6bd2b206fbde78c7e5aa38586d82022001496edce35663071d74472e02119432ba059b3904d205e4358014410e4f2be3820220ad08c2a5878cada53521f4e2bb449f191ccca7899246721a0deeea19f7b83f70820220c805572b813a072067eab2087ddbee8aa719090e12890b15c01094f0d3f74a5f8a02008a02008a02008a02008a02008a02008a02008a02008a02008a020098028094ebdc03a0028094ebdc03a8028094ebdc03b0028094ebdc03ba02f8010a20da47a8cc1c10796dd43f98ed113c648625e2e504c16ac5da9c65669e2377241b1220f5beca3bb11406079dc174183105c474c862a73c257ce8b3d9f5ca065e6264691a10805015e4203740495a23e93c1bd06ba81a10ca58ff36ffb0545dc3f800ddd6f8d0481a1076f664639ca8b5209e488895faa5460f1a104a1e89a7f2d8c89158f18856bf289c2a1a10c2f4330787831f419713ad4990e347d31a10fd403ec0797c001a2794b51d6178916d1a10e14fff88fdd3d1554ed861104ddc56a81a10b0284b9817fec2c3066c6f2651d17fc41a10b090233a67d502f78191c9e19a2a032b1a10e483414860bb612af50ee15ce8cd8ef5c00280e497d012c8028094ebdc03" +}` + +var ocr3Cfg = ` +{ + "MaxQueryLengthBytes": 1000000, + "MaxObservationLengthBytes": 1000000, + "MaxReportLengthBytes": 1000000, + "MaxRequestBatchSize": 1000, + "UniqueReports": true, + "DeltaProgressMillis": 5000, + "DeltaResendMillis": 5000, + "DeltaInitialMillis": 5000, + "DeltaRoundMillis": 2000, + "DeltaGraceMillis": 500, + "DeltaCertifiedCommitRequestMillis": 1000, + "DeltaStageMillis": 30000, + "MaxRoundsPerEpoch": 10, + "TransmissionSchedule": [ + 10 + ], + "MaxDurationQueryMillis": 1000, + "MaxDurationObservationMillis": 1000, + "MaxDurationReportMillis": 1000, + "MaxDurationAcceptMillis": 1000, + "MaxDurationTransmitMillis": 1000, + "MaxFaultyOracles": 3 +}` + +func Test_configureOCR3Request_generateOCR3Config(t *testing.T) { + nodes := loadTestData(t, "testdata/testnet_wf_view.json") + + var cfg OracleConfig + err := json.Unmarshal([]byte(ocr3Cfg), &cfg) + require.NoError(t, err) + + r := configureOCR3Request{ + cfg: &OracleConfigWithSecrets{OracleConfig: cfg, OCRSecrets: deployment.XXXGenerateTestOCRSecrets()}, + nodes: nodes, + chain: deployment.Chain{ + Selector: chain_selectors.ETHEREUM_TESTNET_SEPOLIA.Selector, + }, + } + got, err := r.generateOCR3Config() + require.NoError(t, err) + b, err := json.MarshalIndent(got, "", " ") + require.NoError(t, err) + require.Equal(t, wantOCR3Config, string(b)) +} + +func loadTestData(t *testing.T, path string) []deployment.Node { + data, err := os.ReadFile(path) + require.NoError(t, err) + var nodeViews map[string]*view.NopView + err = json.Unmarshal(data, &nodeViews) + require.NoError(t, err) + require.Len(t, nodeViews, 10) + + names := make([]string, 0) + for k := range nodeViews { + names = append(names, k) + } + sort.Strings(names) + + // in general we can map from the view to the node, but we know the test data + var nodes []deployment.Node + //for _, nv := range nodeViews { + for _, name := range names { + nv := nodeViews[name] + node := deployment.Node{ + NodeID: nv.NodeID, + IsBootstrap: nv.IsBootstrap, + SelToOCRConfig: make(map[chain_selectors.ChainDetails]deployment.OCRConfig), + AdminAddr: nv.PayeeAddress, + } + for chain, ocrKey := range nv.OCRKeys { + // TODO: this decoding could be shared with NodeInfo + p, err := p2pkey.MakePeerID(ocrKey.PeerID) + require.NoError(t, err) + + b := common.Hex2Bytes(ocrKey.OffchainPublicKey) + var opk types2.OffchainPublicKey + copy(opk[:], b) + + b = common.Hex2Bytes(ocrKey.ConfigEncryptionPublicKey) + var cpk types3.ConfigEncryptionPublicKey + copy(cpk[:], b) + + var pubkey types3.OnchainPublicKey + if strings.HasPrefix(chain, "ethereum") { + // convert from pubkey to address + pubkey = common.HexToAddress(ocrKey.OnchainPublicKey).Bytes() + } else { + pubkey = common.Hex2Bytes(ocrKey.OnchainPublicKey) + } + + ocrCfg := deployment.OCRConfig{ + KeyBundleID: ocrKey.KeyBundleID, + OffchainPublicKey: opk, + OnchainPublicKey: pubkey, + PeerID: p, + TransmitAccount: types.Account(ocrKey.TransmitAccount), + ConfigEncryptionPublicKey: cpk, + } + var k chain_selectors.ChainDetails + switch chain { + case "aptos-testnet": + k = chain_selectors.ChainDetails{ + ChainSelector: chain_selectors.APTOS_TESTNET.Selector, + ChainName: chain, + } + + case "ethereum-testnet-sepolia": + k = chain_selectors.ChainDetails{ + ChainSelector: chain_selectors.ETHEREUM_TESTNET_SEPOLIA.Selector, + ChainName: chain, + } + default: + t.Fatalf("unexpected chain %s", chain) + } + node.SelToOCRConfig[k] = ocrCfg + } + + nodes = append(nodes, node) + } + require.Len(t, nodes, 10) + return nodes +} diff --git a/deployment/keystone/testdata/testnet_wf_view.json b/deployment/keystone/testdata/testnet_wf_view.json new file mode 100644 index 00000000000..8a4162f5e58 --- /dev/null +++ b/deployment/keystone/testdata/testnet_wf_view.json @@ -0,0 +1,262 @@ +{ + "cl-keystone-one-0": { + "nodeID": "node_00", + "isBootstrap": false, + "ocrKeys": { + "aptos-testnet": { + "offchainPublicKey": "4ec55bbe76a6b1fdc885c59da85a8fe44cf06afe1e4719f0824a731937526c52", + "onchainPublicKey": "b8834eaa062f0df4ccfe7832253920071ec14dc4f78b13ecdda10b824e2dd3b6", + "peerID": "p2p_12D3KooWMWUKdoAc2ruZf9f55p7NVFj7AFiPm67xjQ8BZBwkqyYv", + "transmitAccount": " ", + "configEncryptionPublicKey": "559ea4ee5774a31d97914a4220d6a47094ae8e2cf0806e80e1eacd851f3e6757", + "keyBundleID": "b4504e84ea307cc2afffca0206bd4bf8e98acc5a03c9bd47b2456e3845a5d1fa" + }, + "ethereum-testnet-sepolia": { + "offchainPublicKey": "03dacd15fc96c965c648e3623180de002b71a97cf6eeca9affb91f461dcd6ce1", + "onchainPublicKey": "b35409a8d4f9a18da55c5b2bb08a3f5f68d44442", + "peerID": "p2p_12D3KooWMWUKdoAc2ruZf9f55p7NVFj7AFiPm67xjQ8BZBwkqyYv", + "transmitAccount": "0x2877F08d9c5Cc9F401F730Fa418fAE563A9a2FF3", + "configEncryptionPublicKey": "5193f72fc7b4323a86088fb0acb4e4494ae351920b3944bd726a59e8dbcdd45f", + "keyBundleID": "665a101d79d310cb0a5ebf695b06e8fc8082b5cbe62d7d362d80d47447a31fea" + } + }, + "payeeAddress": "", + "csaKey": "403b72f0b1b3b5f5a91bcfedb7f28599767502a04b5b7e067fcf3782e23eeb9c", + "isConnected": true, + "isEnabled": true + }, + "cl-keystone-one-1": { + "nodeID": "node_01", + "isBootstrap": false, + "ocrKeys": { + "aptos-testnet": { + "offchainPublicKey": "a38dbe521643479d78ab5477cae78161a5de0030c95098e3fbb09add6aca9508", + "onchainPublicKey": "247d0189f65f58be83a4e7d87ff338aaf8956e9acb9fcc783f34f9edc29d1b40", + "peerID": "p2p_12D3KooWCbDiL7sP9BVby5KaZqPpaVP1RBokoa9ShzH5WhkYX46v", + "transmitAccount": " ", + "configEncryptionPublicKey": "a38dbe521643479d78ab5477cae78161a5de0030c95098e3fbb09add6aca9508", + "keyBundleID": "4b6418b8ab88ea1244c3c48eb5f4c86f9f0301aebffcac4fcfac5cdfb7cf6933" + }, + "ethereum-testnet-sepolia": { + "offchainPublicKey": "255096a3b7ade10e29c648e0b407fc486180464f713446b1da04f013df6179c8", + "onchainPublicKey": "8258f4c4761cc445333017608044a204fd0c006a", + "peerID": "p2p_12D3KooWCbDiL7sP9BVby5KaZqPpaVP1RBokoa9ShzH5WhkYX46v", + "transmitAccount": "0x415aa1E9a1bcB3929ed92bFa1F9735Dc0D45AD31", + "configEncryptionPublicKey": "2c45fec2320f6bcd36444529a86d9f8b4439499a5d8272dec9bcbbebb5e1bf01", + "keyBundleID": "7a9b75510b8d09932b98142419bef52436ff725dd9395469473b487ef87fdfb0" + } + }, + "payeeAddress": "", + "csaKey": "28b91143ec9111796a7d63e14c1cf6bb01b4ed59667ab54f5bc72ebe49c881be", + "isConnected": true, + "isEnabled": true + }, + "cl-keystone-one-2": { + "nodeID": "node_MgvchYopDVSBv3BCpNgEk", + "isBootstrap": false, + "ocrKeys": { + "aptos-testnet": { + "offchainPublicKey": "450aa794c87198a595761a8c18f0f1590046c8092960036638d002256af95254", + "onchainPublicKey": "ba20d3da9b07663f1e8039081a514649fd61a48be2d241bc63537ee47d028fcd", + "peerID": "p2p_12D3KooWGDmBKZ7B3PynGrvfHTJMEecpjfHts9YK5NWk8oJuxcAo", + "transmitAccount": " ", + "configEncryptionPublicKey": "412a4bed6b064c17168871d28dbb965cc0a898f7b19eb3fa7cd01d3e3d10b66c", + "keyBundleID": "e57c608a899d80e510913d2c7ef55758ee81e9eb73eb531003af1564307fd133" + }, + "ethereum-testnet-sepolia": { + "offchainPublicKey": "dba3c61e5f8bec594be481bcaf67ecea0d1c2950edb15b158ce3dbc77877def3", + "onchainPublicKey": "d4dcc573e9d24a8b27a07bba670ba3a2ab36e5bb", + "peerID": "p2p_12D3KooWGDmBKZ7B3PynGrvfHTJMEecpjfHts9YK5NWk8oJuxcAo", + "transmitAccount": "0xCea84bC1881F3cE14BA13Dc3a00DC1Ff3D553fF0", + "configEncryptionPublicKey": "ee466234b3b2f65b13c848b17aa6a8d4e0aa0311d3bf8e77a64f20b04ed48d39", + "keyBundleID": "1d20490fe469dd6af3d418cc310a6e835181fa13e8dc80156bcbe302b7afcd34" + } + }, + "payeeAddress": "", + "csaKey": "7a166fbc816eb4a4dcb620d11c3ccac5c085d56b1972374100116f87619debb8", + "isConnected": true, + "isEnabled": true + }, + "cl-keystone-one-3": { + "nodeID": "node_Q9CZrC45VZZkMnCbDNgzH", + "isBootstrap": false, + "ocrKeys": { + "aptos-testnet": { + "offchainPublicKey": "886044b333af681ab4bf3be663122524ece9725e110ac2a64cda8526cad6983e", + "onchainPublicKey": "046faf34ebfe42510251e6098bc34fa3dd5f2de38ac07e47f2d1b34ac770639f", + "peerID": "p2p_12D3KooWCcVLytqinD8xMn27NvomcQhj2mqMVzyGemz6oPwv1SMT", + "transmitAccount": " ", + "configEncryptionPublicKey": "a7f3435bfbaabebd1572142ff1aec9ed98758d9bb098f1fcc77262fcae7f4171", + "keyBundleID": "5811a96a0c3b5f5b52973eee10e5771cf5953d37d5616ea71f7ae76f09f6e332" + }, + "ethereum-testnet-sepolia": { + "offchainPublicKey": "b4c4993d6c15fee63800db901a8b35fa419057610962caab1c1d7bed55709127", + "onchainPublicKey": "6607c140e558631407f33bafbabd103863cee876", + "peerID": "p2p_12D3KooWCcVLytqinD8xMn27NvomcQhj2mqMVzyGemz6oPwv1SMT", + "transmitAccount": "0xA9eFB53c513E413762b2Be5299D161d8E6e7278e", + "configEncryptionPublicKey": "63375a3d175364bd299e7cecf352cb3e469dd30116cf1418f2b7571fb46c4a4b", + "keyBundleID": "8843b5db0608f92dac38ca56775766a08db9ee82224a19595d04bd6c58b38fbd" + } + }, + "payeeAddress": "", + "csaKey": "487901e0c0a9d3c66e7cfc50f3a9e3cdbfdf1b0107273d73d94a91d278545516", + "isConnected": true, + "isEnabled": true + }, + "cl-keystone-one-4": { + "nodeID": "node_q9CQHG4n4QozxAz5UCufa", + "isBootstrap": false, + "ocrKeys": { + "aptos-testnet": { + "offchainPublicKey": "b34bb49788541de8b6cfb321799a41927a391a4eb135c74f6cb14eec0531ee6f", + "onchainPublicKey": "1221e131ef21014a6a99ed22376eb869746a3b5e30fd202cf79e44efaeb8c5c2", + "peerID": "p2p_12D3KooWHqR1w26yHatTSZQW3xbRct9SxWzVj9X4SpU916Hy8jYg", + "transmitAccount": " ", + "configEncryptionPublicKey": "96ae354418e50dcd5b3dae62e8f0bc911bbce7f761220837aacdaa6f82bd0f29", + "keyBundleID": "b1ab478c1322bc4f8227be50898a8044efc70cf0156ec53cf132119db7e94dea" + }, + "ethereum-testnet-sepolia": { + "offchainPublicKey": "2a4c7dec127fdd8145e48c5edb9467225098bd8c8ad1dade868325b787affbde", + "onchainPublicKey": "a6f35436cb7bffd615cc47a0a04aa0a78696a144", + "peerID": "p2p_12D3KooWHqR1w26yHatTSZQW3xbRct9SxWzVj9X4SpU916Hy8jYg", + "transmitAccount": "0x6F5cAb24Fb7412bB516b3468b9F3a9c471d25fE5", + "configEncryptionPublicKey": "c812eab2415f45cc1d2afdb2be2e3ea419bb7851acfc30c07b4df42c856e8f74", + "keyBundleID": "8e563a16ec5a802345b162d0f31149e8d5055014a31847d7b20d6de500aa48bd" + } + }, + "payeeAddress": "", + "csaKey": "07e0ffc57b6263604df517b94bd986169451a3c90600a855bb19212dc575de54", + "isConnected": true, + "isEnabled": true + }, + "cl-keystone-one-5": { + "nodeID": "node_sS3MELvKpNQBMqh7t5QL5", + "isBootstrap": false, + "ocrKeys": { + "aptos-testnet": { + "offchainPublicKey": "11674b98849d8e070ac69d37c284b3091fcd374913f52b2b83ce2d9a4a4e0213", + "onchainPublicKey": "425d1354a7b8180252a221040c718cac0ba0251c7efe31a2acefbba578dc2153", + "peerID": "p2p_12D3KooWR8d5kbZb7YiQWKpT1J1PfMqNaGAmb4jBFx9DWag4hpSZ", + "transmitAccount": " ", + "configEncryptionPublicKey": "263bee0d09d90e0e618c4cdd630d1437f7377f2d544df57f39ddd47984970555", + "keyBundleID": "44b5f46bfbb04d0984469298ec43c350ec6b2cd4556b18265ebac1b6cc329c7c" + }, + "ethereum-testnet-sepolia": { + "offchainPublicKey": "283471ed66d61fbe11f64eff65d738b59a0301c9a4f846280db26c64c9fdd3f8", + "onchainPublicKey": "657587eb55cecd6f90b97297b611c3024e488cc0", + "peerID": "p2p_12D3KooWR8d5kbZb7YiQWKpT1J1PfMqNaGAmb4jBFx9DWag4hpSZ", + "transmitAccount": "0xdAd1F3F8ec690cf335D46c50EdA5547CeF875161", + "configEncryptionPublicKey": "36de4924cf11938b4461aea1ce99cb640e9603d9a7c294ab6c54acd51d575a49", + "keyBundleID": "99fad0362cc8dc8a57a8e616e68133a6d5a8834e08a1b4819710f0e912df5abc" + } + }, + "payeeAddress": "", + "csaKey": "4542f4fd2ed150c8c976b39802fe3d994aec3ac94fd11e7817f693b1c9a1dabb", + "isConnected": true, + "isEnabled": true + }, + "cl-keystone-one-6": { + "nodeID": "node_jb1oicMu3xxx2N2e4JWvE", + "isBootstrap": false, + "ocrKeys": { + "aptos-testnet": { + "offchainPublicKey": "6fc8c3fb55b39577abbab20028bee93d1d6d8a888dd298354b95d4af3ccb6009", + "onchainPublicKey": "4a94c75cb9fe8b1fba86fd4b71ad130943281fdefad10216c46eb2285d60950f", + "peerID": "p2p_12D3KooWNJ8de3PUURZ2oucrVTpnRTqNBTUYwHLQjK9LzN3E6Mfn", + "transmitAccount": " ", + "configEncryptionPublicKey": "3ae1a1c713e4ad63f67191fd93620c9eebe44e1d5f3264036ec0fbcd59cf9664", + "keyBundleID": "b419e9e3f1256aa2907a1a396bdf27ba5002a30eee440ab96cb60369429ce277" + }, + "ethereum-testnet-sepolia": { + "offchainPublicKey": "aa3419628ea3536783742d17d8adf05681aa6a6bd2b206fbde78c7e5aa38586d", + "onchainPublicKey": "4885973b2fcf061d5cdfb8f74c5139bd3056e9da", + "peerID": "p2p_12D3KooWNJ8de3PUURZ2oucrVTpnRTqNBTUYwHLQjK9LzN3E6Mfn", + "transmitAccount": "0x19e10B063a62B1574AE19020A64fDe6419892dA6", + "configEncryptionPublicKey": "8c6c7d889ac6cc9e663ae48073bbf130fae105d6a3689636db27752e3e3e6816", + "keyBundleID": "62d36269d916b4834b17dc6d637c1c39b0895396249a0845764c898e83f63525" + } + }, + "payeeAddress": "", + "csaKey": "75ac63fc97a31e31168084e0de8ccd2bea90059b609d962f3e43fc296cdba28d", + "isConnected": true, + "isEnabled": true + }, + "cl-keystone-one-7": { + "nodeID": "node_nokJcFtJ5hunVbgLXufKn", + "isBootstrap": false, + "ocrKeys": { + "aptos-testnet": { + "offchainPublicKey": "cf0684a0e59399fe9b92cfc740d9696f925e78ee7d0273947e5f7b830070eaaa", + "onchainPublicKey": "96dc85670c49caa986de4ad288e680e9afb0f5491160dcbb4868ca718e194fc8", + "peerID": "p2p_12D3KooWQMCj73V5xmCd6C5VsJr7rbFG2TF9LwVcLiiBqXps9MgC", + "transmitAccount": " ", + "configEncryptionPublicKey": "209eea27e73b0ecc1c49b3ea274e4a18a1f5ed62fd79f443f0b5b9cc6019356e", + "keyBundleID": "14082da0f33b4cec842bc1e1002e617a194ed4a81105603bd6c1edf784aa3743" + }, + "ethereum-testnet-sepolia": { + "offchainPublicKey": "01496edce35663071d74472e02119432ba059b3904d205e4358014410e4f2be3", + "onchainPublicKey": "213803bb9f9715379aaf11aadb0212369701dc0a", + "peerID": "p2p_12D3KooWQMCj73V5xmCd6C5VsJr7rbFG2TF9LwVcLiiBqXps9MgC", + "transmitAccount": "0x9ad9f3AD49e5aB0F28bD694d211a90297bD90D7f", + "configEncryptionPublicKey": "4d2f75f98b911c20fe7808384312d8b913e6b7a98c34d05c6e461434c92b4502", + "keyBundleID": "e6d6ffec6cff01ac20d57bc42626c8e955293f232d383bf468351d867a7b8213" + } + }, + "payeeAddress": "", + "csaKey": "b473091fe1d4dbbc26ad71c67b4432f8f4280e06bab5e2122a92f4ab8b6ff2f5", + "isConnected": true, + "isEnabled": true + }, + "cl-keystone-one-8": { + "nodeID": "node_7iTc33UzaAj6XmGa5hHC9", + "isBootstrap": false, + "ocrKeys": { + "aptos-testnet": { + "offchainPublicKey": "c791d2b9d3562f991af68ab7164a19734d551a9404d91c9571fdcdc5dcb237ca", + "onchainPublicKey": "bddafb20cc50d89e0ae2f244908c27b1d639615d8186b28c357669de3359f208", + "peerID": "p2p_12D3KooWAUagqMycsro27kFznSQRHbhfCBLx8nKD4ptTiUGDe38c", + "transmitAccount": " ", + "configEncryptionPublicKey": "0874e6cd5c8e651ab0ff564a474832ed9eaf2c5025b553f908d04921d9777d50", + "keyBundleID": "6726df46033038b724a4e6371807b6aa09efc829d0a3f7a5db4fd7df4b69fea7" + }, + "ethereum-testnet-sepolia": { + "offchainPublicKey": "ad08c2a5878cada53521f4e2bb449f191ccca7899246721a0deeea19f7b83f70", + "onchainPublicKey": "8c2aa1e6fad88a6006dfb116eb866cbad2910314", + "peerID": "p2p_12D3KooWAUagqMycsro27kFznSQRHbhfCBLx8nKD4ptTiUGDe38c", + "transmitAccount": "0x31B179dcF8f9036C30f04bE578793e51bF14A39E", + "configEncryptionPublicKey": "e3d4d7a7372a3b1110db0290ab3649eb5fbb0daf6cf3ae02cfe5f367700d9264", + "keyBundleID": "be0d639de3ae3cbeaa31ca369514f748ba1d271145cba6796bcc12aace2f64c3" + } + }, + "payeeAddress": "", + "csaKey": "1141dd1e46797ced9b0fbad49115f18507f6f6e6e3cc86e7e5ba169e58645adc", + "isConnected": true, + "isEnabled": true + }, + "cl-keystone-one-9": { + "nodeID": "node_V3xAXNftjt6sxkfRgmw9J", + "isBootstrap": false, + "ocrKeys": { + "aptos-testnet": { + "offchainPublicKey": "ff1144bbf648e6f76c58d0ce53a9a2cbe9a284d52db8691a714cac8e3a96b8b4", + "onchainPublicKey": "4fa557850e4d5c21b3963c97414c1f37792700c4d3b8abdb904b765fd47e39bf", + "peerID": "p2p_12D3KooWBCMCCZZ8x57AXvJvpCujqhZzTjWXbReaRE8TxNr5dM4U", + "transmitAccount": " ", + "configEncryptionPublicKey": "6a1f37f06833c55ecf46233439ea6179a835bac6f2b2dee725b747c121813149", + "keyBundleID": "d834cf7c830df7510228b33b138c018ff16b4eecf82273ed3bcd862bbbc046d4" + }, + "ethereum-testnet-sepolia": { + "offchainPublicKey": "c805572b813a072067eab2087ddbee8aa719090e12890b15c01094f0d3f74a5f", + "onchainPublicKey": "679296b7c1eb4948efcc87efc550940a182e610c", + "peerID": "p2p_12D3KooWBCMCCZZ8x57AXvJvpCujqhZzTjWXbReaRE8TxNr5dM4U", + "transmitAccount": "0x0b04cE574E80Da73191Ec141c0016a54A6404056", + "configEncryptionPublicKey": "09fced0207611ed618bf0759ab128d9797e15b18e46436be1a56a91e4043ec0e", + "keyBundleID": "1c28e76d180d1ed1524e61845fa58a384415de7e51017edf1f8c553e28357772" + } + }, + "payeeAddress": "", + "csaKey": "412dc6fe48ea4e34baaa77da2e3b032d39b938597b6f3d61fe7ed183a827a431", + "isConnected": true, + "isEnabled": true + } +} \ No newline at end of file diff --git a/deployment/keystone/types.go b/deployment/keystone/types.go index e5657657ed9..b7bf636c3e2 100644 --- a/deployment/keystone/types.go +++ b/deployment/keystone/types.go @@ -1,7 +1,6 @@ package keystone import ( - "encoding/hex" "errors" "fmt" "slices" @@ -15,11 +14,8 @@ import ( "github.com/smartcontractkit/chainlink/deployment" - v1 "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/node" - "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" ) @@ -55,147 +51,50 @@ type Nop struct { NodeIDs []string // nodes run by this operator } -// ocr2Node is a subset of the node configuration that is needed to register a node -// with the capabilities registry. Signer and P2PKey are chain agnostic. -// TODO: KS-466 when we migrate fully to the JD offchain client, we should be able remove this shim and use environment.Node directly -type ocr2Node struct { - ID string - Signer [32]byte // note that in capabilities registry we need a [32]byte, but in the forwarder we need a common.Address [20]byte - P2PKey p2pkey.PeerID - 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 - accountAddress string -} - -func (o *ocr2Node) signerAddress() common.Address { - // eth address is the first 20 bytes of the Signer - return common.BytesToAddress(o.Signer[:20]) -} - -func (o *ocr2Node) toNodeKeys() NodeKeys { +func toNodeKeys(o *deployment.Node, registryChainSel uint64) NodeKeys { var aptosOcr2KeyBundleId string var aptosOnchainPublicKey string - if o.aptosOcr2KeyBundle != nil { - aptosOcr2KeyBundleId = o.aptosOcr2KeyBundle.BundleId - aptosOnchainPublicKey = o.aptosOcr2KeyBundle.OnchainSigningAddress + var aptosCC *deployment.OCRConfig + for details, cfg := range o.SelToOCRConfig { + if family, err := chainsel.GetSelectorFamily(details.ChainSelector); err == nil && family == chainsel.FamilyAptos { + aptosCC = &cfg + break + } + } + if aptosCC != nil { + aptosOcr2KeyBundleId = aptosCC.KeyBundleID + aptosOnchainPublicKey = fmt.Sprintf("%x", aptosCC.OnchainPublicKey[:]) + } + registryChainID, err := chainsel.ChainIdFromSelector(registryChainSel) + if err != nil { + panic(err) } + registryChainDetails, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.Itoa(int(registryChainID)), chainsel.FamilyEVM) + if err != nil { + panic(err) + } + evmCC := o.SelToOCRConfig[registryChainDetails] return NodeKeys{ - EthAddress: o.accountAddress, - P2PPeerID: strings.TrimPrefix(o.p2pKeyBundle.PeerId, "p2p_"), - OCR2BundleID: o.ethOcr2KeyBundle.BundleId, - OCR2OnchainPublicKey: o.ethOcr2KeyBundle.OnchainSigningAddress, - OCR2OffchainPublicKey: o.ethOcr2KeyBundle.OffchainPublicKey, - OCR2ConfigPublicKey: o.ethOcr2KeyBundle.ConfigPublicKey, - CSAPublicKey: o.csaKey, + EthAddress: string(evmCC.TransmitAccount), + P2PPeerID: strings.TrimPrefix(o.PeerID.String(), "p2p_"), + OCR2BundleID: evmCC.KeyBundleID, + OCR2OffchainPublicKey: fmt.Sprintf("%x", evmCC.OffchainPublicKey[:]), + OCR2OnchainPublicKey: fmt.Sprintf("%x", evmCC.OnchainPublicKey[:]), + OCR2ConfigPublicKey: fmt.Sprintf("%x", evmCC.ConfigEncryptionPublicKey[:]), + CSAPublicKey: o.CSAKey, // default value of encryption public key is the CSA public key // TODO: DEVSVCS-760 - EncryptionPublicKey: strings.TrimPrefix(o.csaKey, "csa_"), + EncryptionPublicKey: strings.TrimPrefix(o.CSAKey, "csa_"), // TODO Aptos support. How will that be modeled in clo data? + // TODO: AptosAccount is unset but probably unused AptosBundleID: aptosOcr2KeyBundleId, 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 ExtractKeys(n *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 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") - } - evmCC, exists := ccfgs[chaintype.EVM] - if !exists { - return nil, errors.New("no evm chain config for node id " + id) - } - - if csaPubKey == "" { - return nil, errors.New("empty csa public key") - } - // parse csapublic key to - csaKey, err := hex.DecodeString(csaPubKey) - if err != nil { - return nil, fmt.Errorf("failed to decode csa public key %s: %w", csaPubKey, err) - } - if len(csaKey) != 32 { - return nil, fmt.Errorf("invalid csa public key '%s'. expected len 32 got %d", csaPubKey, 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) - } - 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) - - n := &ocr2Node{ - ID: id, - Signer: sigb, - P2PKey: p, - EncryptionPublicKey: csaKeyb, - IsBoostrap: ocfg.IsBootstrap, - p2pKeyBundle: ocfg.P2PKeyBundle, - ethOcr2KeyBundle: evmCC.Ocr2Config.OcrKeyBundle, - aptosOcr2KeyBundle: nil, - accountAddress: evmCC.AccountAddress, - csaKey: csaPubKey, - } - // aptos chain config is optional - if aptosCC, exists := ccfgs[chaintype.Aptos]; exists { - n.aptosOcr2KeyBundle = aptosCC.Ocr2Config.OcrKeyBundle - } - - return n, nil -} - -func makeNodeKeysSlice(nodes []*ocr2Node) []NodeKeys { +func makeNodeKeysSlice(nodes []deployment.Node, registryChainSel uint64) []NodeKeys { var out []NodeKeys for _, n := range nodes { - out = append(out, n.toNodeKeys()) + out = append(out, toNodeKeys(&n, registryChainSel)) } return out } @@ -255,21 +154,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 +165,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]) + 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) + 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) } } } @@ -321,70 +200,62 @@ func mapDonsToCaps(dons []DonInfo) map[string][]kcr.CapabilitiesRegistryCapabili // mapDonsToNodes returns a map of don name to simplified representation of their nodes // all nodes must have evm config and ocr3 capability nodes are must also have an aptos chain config -func mapDonsToNodes(dons []DonInfo, excludeBootstraps bool, registryChainSel uint64) (map[string][]*ocr2Node, error) { - donToOcr2Nodes := make(map[string][]*ocr2Node) +func mapDonsToNodes(dons []DonInfo, excludeBootstraps bool, registryChainSel uint64) (map[string][]deployment.Node, error) { + donToNodes := make(map[string][]deployment.Node) // get the nodes for each don from the offchain client, get ocr2 config from one of the chain configs for the node b/c - // they are equivalent, and transform to ocr2node representation + // they are equivalent for _, don := range dons { 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) - } - if excludeBootstraps && ocr2n.IsBoostrap { + if excludeBootstraps && node.IsBootstrap { continue } - if _, ok := donToOcr2Nodes[don.Name]; !ok { - donToOcr2Nodes[don.Name] = make([]*ocr2Node, 0) + if _, ok := donToNodes[don.Name]; !ok { + donToNodes[don.Name] = make([]deployment.Node, 0) } - donToOcr2Nodes[don.Name] = append(donToOcr2Nodes[don.Name], ocr2n) - } - } - - 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 + donToNodes[don.Name] = append(donToNodes[don.Name], node) } } - 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) + return donToNodes, nil } // 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 Info capabilities_registry.CapabilitiesRegistryDONInfo - Nodes []*ocr2Node + Nodes []deployment.Node } -func (d RegisteredDon) signers() []common.Address { +func (d RegisteredDon) signers(chainFamily string) []common.Address { sort.Slice(d.Nodes, func(i, j int) bool { - return d.Nodes[i].P2PKey.String() < d.Nodes[j].P2PKey.String() + return d.Nodes[i].PeerID.String() < d.Nodes[j].PeerID.String() }) var out []common.Address for _, n := range d.Nodes { - if n.IsBoostrap { + if n.IsBootstrap { continue } - out = append(out, n.signerAddress()) + var found bool + var registryChainDetails chainsel.ChainDetails + for details, _ := range n.SelToOCRConfig { + if family, err := chainsel.GetSelectorFamily(details.ChainSelector); err == nil && family == chainFamily { + found = true + registryChainDetails = details + + } + } + if !found { + panic(fmt.Sprintf("chainType not found: %v", chainFamily)) + } + // eth address is the first 20 bytes of the Signer + config, exists := n.SelToOCRConfig[registryChainDetails] + if !exists { + panic(fmt.Sprintf("chainID not found: %v", registryChainDetails)) + } + signer := config.OnchainPublicKey + signerAddress := common.BytesToAddress(signer) + out = append(out, signerAddress) } return out } diff --git a/deployment/keystone/types_test.go b/deployment/keystone/types_test.go index 925649bba0d..ea122837aa6 100644 --- a/deployment/keystone/types_test.go +++ b/deployment/keystone/types_test.go @@ -1,402 +1,78 @@ package keystone import ( + "encoding/hex" + "fmt" + "math/big" + "strconv" + "strings" "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" + "github.com/ethereum/go-ethereum/common" + chainsel "github.com/smartcontractkit/chain-selectors" + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" + "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/stretchr/testify/require" ) -func Test_newOcr2Node(t *testing.T) { - type args struct { - id string - ccfgs map[chaintype.ChainType]*v1.ChainConfig - csaPubKey string +func Test_toNodeKeys(t *testing.T) { + registryChainSel := chainsel.TEST_90000001 + registryChainID, err := chainsel.ChainIdFromSelector(registryChainSel.Selector) + if err != nil { + panic(err) + } + registryChainDetails, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.Itoa(int(registryChainID)), chainsel.FamilyEVM) + if err != nil { + panic(err) + } + aptosChainDetails, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.Itoa(int(1)), chainsel.FamilyAptos) + if err != nil { + panic(err) } - 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", + p2pID := p2pkey.MustNewV2XXXTestingOnly(big.NewInt(100)) + pubKey_1 := "11114981a6119ca3f932cdb8c402d71a72d672adae7849f581ecff8b8e1098e7" // valid csa key + signing_1 := common.Hex2Bytes("11117293a4Cc2621b61193135a95928735e4795f") // valid eth address + admin_1 := common.HexToAddress("0x1111567890123456789012345678901234567890") // valid eth address + signing_2 := common.Hex2Bytes("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee") // valid key + var encryptionpubkey [32]byte + if _, err := hex.Decode(encryptionpubkey[:], []byte(pubKey_1)); err != nil { + panic(fmt.Sprintf("failed to decode pubkey %s: %v", encryptionpubkey, err)) + } + keys := toNodeKeys(&deployment.Node{ + NodeID: "p2p_123", + Name: "node 1", + PeerID: p2pID.PeerID(), + CSAKey: pubKey_1, + AdminAddr: admin_1.String(), + SelToOCRConfig: map[chainsel.ChainDetails]deployment.OCRConfig{ + registryChainDetails: { + OffchainPublicKey: types.OffchainPublicKey(common.FromHex("1111111111111111111111111111111111111111111111111111111111111111")), + OnchainPublicKey: signing_1[:], + PeerID: p2pID.PeerID(), + TransmitAccount: types.Account(admin_1.String()), + ConfigEncryptionPublicKey: encryptionpubkey, + KeyBundleID: "abcd", }, - }, - { - 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", + aptosChainDetails: { + TransmitAccount: "ff", + OnchainPublicKey: signing_2, + KeyBundleID: "aptos", }, - wantAptos: true, }, - { - name: "bad csa key", - args: args{ - id: "1", - ccfgs: map[chaintype.ChainType]*v1.ChainConfig{ - chaintype.EVM: { + }, registryChainSel.Selector) - 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) - }) - } + require.Equal(t, NodeKeys{ + EthAddress: admin_1.String(), + AptosBundleID: "aptos", + AptosOnchainPublicKey: hex.EncodeToString(signing_2), + P2PPeerID: strings.TrimPrefix(p2pID.PeerID().String(), "p2p_"), + OCR2BundleID: "abcd", + OCR2OnchainPublicKey: hex.EncodeToString(signing_1), + OCR2OffchainPublicKey: "1111111111111111111111111111111111111111111111111111111111111111", + OCR2ConfigPublicKey: pubKey_1, + CSAPublicKey: pubKey_1, + EncryptionPublicKey: pubKey_1, + }, keys) } - -// 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 -// }