diff --git a/deployment/keystone/changeset/update_node_capabilities.go b/deployment/keystone/changeset/update_node_capabilities.go index a51706723cc..d690fe3f41d 100644 --- a/deployment/keystone/changeset/update_node_capabilities.go +++ b/deployment/keystone/changeset/update_node_capabilities.go @@ -19,14 +19,17 @@ var _ deployment.ChangeSet[*MutateNodeCapabilitiesRequest] = UpdateNodeCapabilit type P2PSignerEnc = internal.P2PSignerEnc 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) - } + evmCC := n.SelToOCRConfig[registryChainSel] + var signer [32]byte + copy(signer[:], evmCC.OnchainPublicKey) + var csakey [32]byte + copy(csakey[:], evmCC.ConfigEncryptionPublicKey[:]) + + // TODO: return error return value 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 32286db0aad..be984fd2d37 100644 --- a/deployment/keystone/deploy.go +++ b/deployment/keystone/deploy.go @@ -206,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) } @@ -247,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, }) @@ -264,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) @@ -376,19 +376,11 @@ func ConfigureOCR3ContractFromJD(env *deployment.Environment, chainSel uint64, n if err != nil { return err } - var ocr2nodes []*ocr2Node - for _, node := range nodes { - n, err := newOcr2NodeFromJD(&node, chainSel) - if err != nil { - return fmt.Errorf("failed to create ocr2 node from clo node: %w", err) - } - ocr2nodes = append(ocr2nodes, n) - } _, err = configureOCR3contract(configureOCR3Request{ cfg: cfg, chain: registryChain, contract: contract, - nodes: ocr2nodes, + nodes: nodes, }) return err } @@ -588,7 +580,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 } @@ -621,7 +613,7 @@ func registerNodes(lggr logger.Logger, req *registerNodesRequest) (*registerNode } 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) @@ -632,23 +624,27 @@ 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 } - fmt.Printf("nodeToRegisterNop %+v\n", nodeToRegisterNop) - 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 := n.SelToOCRConfig[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 { @@ -668,7 +664,7 @@ func registerNodes(lggr logger.Logger, req *registerNodesRequest) (*registerNode } params.HashedCapabilityIds = append(params.HashedCapabilityIds, newCapIds...) } - nodeIDToParams[n.ID] = params + nodeIDToParams[n.NodeID] = params } } @@ -721,7 +717,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 { @@ -740,7 +736,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) @@ -757,15 +753,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) } @@ -875,7 +871,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(chain.Selector) + 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) @@ -885,7 +882,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 } @@ -894,7 +891,7 @@ type configureOCR3Request struct { cfg *OracleConfigWithSecrets chain deployment.Chain contract *kocr3.OCR3Capability - nodes []*ocr2Node + nodes []deployment.Node } type configureOCR3Response struct { ocrConfig Orc2drOracleConfig @@ -904,7 +901,7 @@ func configureOCR3contract(req configureOCR3Request) (*configureOCR3Response, er if req.contract == nil { return nil, fmt.Errorf("OCR3 contract is nil") } - nks := makeNodeKeysSlice(req.nodes) + nks := makeNodeKeysSlice(req.nodes, req.chain.Selector) ocrConfig, err := GenerateOCR3Config(*req.cfg, nks) if err != nil { return nil, fmt.Errorf("failed to generate OCR3 config: %w", err) diff --git a/deployment/keystone/types.go b/deployment/keystone/types.go index 801e80cd930..29db5e986f0 100644 --- a/deployment/keystone/types.go +++ b/deployment/keystone/types.go @@ -1,7 +1,6 @@ package keystone import ( - "encoding/hex" "errors" "fmt" "slices" @@ -14,8 +13,6 @@ 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/keys/p2pkey" @@ -56,128 +53,54 @@ type Nop struct { // 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 - 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 { +// 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 +// ethOcr2KeyBundle *v1.OCR2Config_OCRKeyBundle +// aptosOcr2KeyBundle *v1.OCR2Config_OCRKeyBundle +// csaKey string // *v1.Node.PublicKey +// accountAddress string +// } + +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 - } - return NodeKeys{ - EthAddress: o.accountAddress, - P2PPeerID: strings.TrimPrefix(o.P2PKey.String(), "p2p_"), - OCR2BundleID: o.ethOcr2KeyBundle.BundleId, - OCR2OnchainPublicKey: o.ethOcr2KeyBundle.OnchainSigningAddress, - OCR2OffchainPublicKey: o.ethOcr2KeyBundle.OffchainPublicKey, - OCR2ConfigPublicKey: o.ethOcr2KeyBundle.ConfigPublicKey, - CSAPublicKey: o.csaKey, - // default value of encryption public key is the CSA public key - // TODO: DEVSVCS-760 - EncryptionPublicKey: strings.TrimPrefix(o.csaKey, "csa_"), - // TODO Aptos support. How will that be modeled in clo data? - AptosBundleID: aptosOcr2KeyBundleId, - AptosOnchainPublicKey: aptosOnchainPublicKey, - } -} -func newOcr2NodeFromJD(n *deployment.Node, registryChainSel uint64) (*ocr2Node, error) { - return newOcr2Node(n, registryChainSel) -} - -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.NodeID, err) - } - return orc2n.P2PKey, orc2n.Signer, orc2n.EncryptionPublicKey, nil -} - -func newOcr2Node(node *deployment.Node, registryChainSel uint64) (*ocr2Node, error) { - if node.CSAKey == "" { - 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(node.SelToOCRConfig) == 0 { - return nil, errors.New("no chain configs") - } - evmCC, ok := node.SelToOCRConfig[registryChainSel] - if !ok { - return nil, fmt.Errorf("failed to get registry chain config for sel %d", registryChainSel) - } - // parse csapublic key - csaKey, err := hex.DecodeString(node.CSAKey) - if err != nil { - 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", node.CSAKey, len(csaKey)) - } - var csaKeyb [32]byte - copy(csaKeyb[:], csaKey) - - signer := evmCC.OnchainPublicKey - if len(signer) != 20 { - return nil, fmt.Errorf("invalid onchain signing address %s", signer) - } - var sigb [32]byte - copy(sigb[:], signer) - - n := &ocr2Node{ - ID: node.NodeID, - Signer: sigb, - P2PKey: evmCC.PeerID, - EncryptionPublicKey: csaKeyb, - 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 { + for sel, cfg := range o.SelToOCRConfig { if family, err := chainsel.GetSelectorFamily(sel); err == nil && family == chainsel.FamilyAptos { aptosCC = &cfg break } } - // aptos chain config is optional if aptosCC != nil { - n.aptosOcr2KeyBundle = &v1.OCR2Config_OCRKeyBundle{ - BundleId: aptosCC.KeyBundleID, - OnchainSigningAddress: fmt.Sprintf("%x", evmCC.OnchainPublicKey[:]), - } + aptosOcr2KeyBundleId = aptosCC.KeyBundleID + aptosOnchainPublicKey = fmt.Sprintf("%x", aptosCC.OnchainPublicKey[:]) + } + evmCC := o.SelToOCRConfig[registryChainSel] + return NodeKeys{ + 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_"), + // TODO Aptos support. How will that be modeled in clo data? + AptosBundleID: aptosOcr2KeyBundleId, + AptosOnchainPublicKey: aptosOnchainPublicKey, } - - 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 } @@ -252,7 +175,7 @@ func nopsToNodes(donInfos []DonInfo, dons []DonCapabilities, chainSelector uint6 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 := node.AdminAddr @@ -262,7 +185,7 @@ func nopsToNodes(donInfos []DonInfo, dons []DonCapabilities, chainSelector uint6 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].NodeID) } @@ -283,47 +206,46 @@ 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.NodeID, 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) + donToNodes[don.Name] = append(donToNodes[don.Name], node) } } - return donToOcr2Nodes, nil + 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(registryChainSel uint64) []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()) + // eth address is the first 20 bytes of the Signer + signer := n.SelToOCRConfig[registryChainSel].OnchainPublicKey + signerAddress := common.BytesToAddress(signer) + out = append(out, signerAddress) } return out }