From 6275bd987324dfb9637a77294042695b1c7dc44e Mon Sep 17 00:00:00 2001 From: krehermann <16602512+krehermann@users.noreply.github.com> Date: Wed, 16 Oct 2024 12:36:44 -0600 Subject: [PATCH] ks-478 aptos key bundle support (#14803) * aptos key bundle support * try something else * debugging --- integration-tests/deployment/clo/env.go | 7 +- .../deployment/clo/models/models_gen.go | 5 +- .../clo/testdata/workflow_nodes.json | 222 +++++++++- .../deployment/keystone/deploy_test.go | 13 +- .../deployment/keystone/types.go | 90 +++- .../deployment/keystone/types_test.go | 402 ++++++++++++++++++ 6 files changed, 713 insertions(+), 26 deletions(-) create mode 100644 integration-tests/deployment/keystone/types_test.go diff --git a/integration-tests/deployment/clo/env.go b/integration-tests/deployment/clo/env.go index d2680f13e58..302f9794eaf 100644 --- a/integration-tests/deployment/clo/env.go +++ b/integration-tests/deployment/clo/env.go @@ -24,7 +24,7 @@ func NewDonEnv(t *testing.T, cfg DonEnvConfig) *deployment.Environment { for _, nop := range cfg.Nops { for _, node := range nop.Nodes { for _, chain := range node.ChainConfigs { - if chain.Ocr1Config.IsBootstrap { + if chain.Ocr2Config.IsBootstrap { t.Fatalf("Don nodes should not be bootstraps nop %s node %s chain %s", nop.ID, node.ID, chain.Network.ChainID) } } @@ -47,13 +47,16 @@ func NewDonEnv(t *testing.T, cfg DonEnvConfig) *deployment.Environment { return &out } -func NewDonEnvWithMemoryChains(t *testing.T, cfg DonEnvConfig) *deployment.Environment { +func NewDonEnvWithMemoryChains(t *testing.T, cfg DonEnvConfig, ignore func(*models.NodeChainConfig) bool) *deployment.Environment { e := NewDonEnv(t, cfg) // overwrite the chains with memory chains chains := make(map[uint64]struct{}) for _, nop := range cfg.Nops { for _, node := range nop.Nodes { for _, chain := range node.ChainConfigs { + if ignore(chain) { + continue + } id, err := strconv.ParseUint(chain.Network.ChainID, 10, 64) require.NoError(t, err, "failed to parse chain id to uint64") chains[id] = struct{}{} diff --git a/integration-tests/deployment/clo/models/models_gen.go b/integration-tests/deployment/clo/models/models_gen.go index 836eb7e7add..baea1dbcbed 100644 --- a/integration-tests/deployment/clo/models/models_gen.go +++ b/integration-tests/deployment/clo/models/models_gen.go @@ -2475,17 +2475,20 @@ const ( ChainTypeEvm ChainType = "EVM" ChainTypeSolana ChainType = "SOLANA" ChainTypeStarknet ChainType = "STARKNET" + ChainTypeAptos ChainType = "APTOS" + ) var AllChainType = []ChainType{ ChainTypeEvm, ChainTypeSolana, ChainTypeStarknet, + ChainTypeAptos, } func (e ChainType) IsValid() bool { switch e { - case ChainTypeEvm, ChainTypeSolana, ChainTypeStarknet: + case ChainTypeEvm, ChainTypeSolana, ChainTypeStarknet, ChainTypeAptos: return true } return false diff --git a/integration-tests/deployment/clo/testdata/workflow_nodes.json b/integration-tests/deployment/clo/testdata/workflow_nodes.json index ade92a8d9f8..78a8f706ae8 100644 --- a/integration-tests/deployment/clo/testdata/workflow_nodes.json +++ b/integration-tests/deployment/clo/testdata/workflow_nodes.json @@ -15,6 +15,28 @@ "name": "Chainlink Sepolia Prod Keystone One 9", "publicKey": "412dc6fe48ea4e34baaa77da2e3b032d39b938597b6f3d61fe7ed183a827a431", "chainConfigs": [ + { + "network": { + "id": "1401", + "chainID": "2", + "chainType": "APTOS", + "name": "APTOS TEST" + }, + "ocr2Config": { + "enabled": true, + "p2pKeyBundle": { + "peerID": "p2p_12D3KooWBCMCCZZ8x57AXvJvpCujqhZzTjWXbReaRE8TxNr5dM4U", + "publicKey": "147d5cc651819b093cd2fdff9760f0f0f77b7ef7798d9e24fc6a350b7300e5d9" + }, + "ocrKeyBundle": { + "bundleID": "d834cf7c830df7510228b33b138c018ff16b4eecf82273ed3bcd862bbbc046d4", + "configPublicKey": "6a1f37f06833c55ecf46233439ea6179a835bac6f2b2dee725b747c121813149", + "offchainPublicKey": "ff1144bbf648e6f76c58d0ce53a9a2cbe9a284d52db8691a714cac8e3a96b8b4", + "onchainSigningAddress": "4fa557850e4d5c21b3963c97414c1f37792700c4d3b8abdb904b765fd47e39bf" + }, + "plugins": {} + } + }, { "network": { "id": "140", @@ -103,6 +125,28 @@ "name": "Chainlink Sepolia Prod Keystone One 8", "publicKey": "1141dd1e46797ced9b0fbad49115f18507f6f6e6e3cc86e7e5ba169e58645adc", "chainConfigs": [ + { + "network": { + "id": "1402", + "chainID": "2", + "chainType": "APTOS", + "name": "APTOS TEST" + }, + "ocr2Config": { + "enabled": true, + "p2pKeyBundle": { + "peerID": "p2p_12D3KooWAUagqMycsro27kFznSQRHbhfCBLx8nKD4ptTiUGDe38c", + "publicKey": "09ca39cd924653c72fbb0e458b629c3efebdad3e29e7cd0b5760754d919ed829" + }, + "ocrKeyBundle": { + "bundleID": "6726df46033038b724a4e6371807b6aa09efc829d0a3f7a5db4fd7df4b69fea7", + "configPublicKey": "0874e6cd5c8e651ab0ff564a474832ed9eaf2c5025b553f908d04921d9777d50", + "offchainPublicKey": "c791d2b9d3562f991af68ab7164a19734d551a9404d91c9571fdcdc5dcb237ca", + "onchainSigningAddress": "bddafb20cc50d89e0ae2f244908c27b1d639615d8186b28c357669de3359f208" + }, + "plugins": {} + } + }, { "network": { "id": "140", @@ -191,6 +235,28 @@ "name": "Chainlink Sepolia Prod Keystone One 7", "publicKey": "b473091fe1d4dbbc26ad71c67b4432f8f4280e06bab5e2122a92f4ab8b6ff2f5", "chainConfigs": [ + { + "network": { + "id": "1403", + "chainID": "2", + "chainType": "APTOS", + "name": "APTOS TEST" + }, + "ocr2Config": { + "enabled": true, + "p2pKeyBundle": { + "peerID": "p2p_12D3KooWQMCj73V5xmCd6C5VsJr7rbFG2TF9LwVcLiiBqXps9MgC", + "publicKey": "d7e9f2252b09edf0802a65b60bc9956691747894cb3ab9fefd072adf742eb9f1" + }, + "ocrKeyBundle": { + "bundleID": "14082da0f33b4cec842bc1e1002e617a194ed4a81105603bd6c1edf784aa3743", + "configPublicKey": "209eea27e73b0ecc1c49b3ea274e4a18a1f5ed62fd79f443f0b5b9cc6019356e", + "offchainPublicKey": "cf0684a0e59399fe9b92cfc740d9696f925e78ee7d0273947e5f7b830070eaaa", + "onchainSigningAddress": "96dc85670c49caa986de4ad288e680e9afb0f5491160dcbb4868ca718e194fc8" + }, + "plugins": {} + } + }, { "network": { "id": "140", @@ -279,6 +345,28 @@ "name": "Chainlink Sepolia Prod Keystone One 6", "publicKey": "75ac63fc97a31e31168084e0de8ccd2bea90059b609d962f3e43fc296cdba28d", "chainConfigs": [ + { + "network": { + "id": "1404", + "chainID": "2", + "chainType": "APTOS", + "name": "APTOS TEST" + }, + "ocr2Config": { + "enabled": true, + "p2pKeyBundle": { + "peerID": "p2p_12D3KooWNJ8de3PUURZ2oucrVTpnRTqNBTUYwHLQjK9LzN3E6Mfn", + "publicKey": "b96933429b1a81c811e1195389d7733e936b03e8086e75ea1fa92c61564b6c31" + }, + "ocrKeyBundle": { + "bundleID": "b419e9e3f1256aa2907a1a396bdf27ba5002a30eee440ab96cb60369429ce277", + "configPublicKey": "3ae1a1c713e4ad63f67191fd93620c9eebe44e1d5f3264036ec0fbcd59cf9664", + "offchainPublicKey": "6fc8c3fb55b39577abbab20028bee93d1d6d8a888dd298354b95d4af3ccb6009", + "onchainSigningAddress": "4a94c75cb9fe8b1fba86fd4b71ad130943281fdefad10216c46eb2285d60950f" + }, + "plugins": {} + } + }, { "network": { "id": "140", @@ -367,6 +455,28 @@ "name": "Chainlink Sepolia Prod Keystone One 5", "publicKey": "4542f4fd2ed150c8c976b39802fe3d994aec3ac94fd11e7817f693b1c9a1dabb", "chainConfigs": [ + { + "network": { + "id": "14005", + "chainID": "2", + "chainType": "APTOS", + "name": "APTOS TEST" + }, + "ocr2Config": { + "enabled": true, + "p2pKeyBundle": { + "peerID": "p2p_12D3KooWR8d5kbZb7YiQWKpT1J1PfMqNaGAmb4jBFx9DWag4hpSZ", + "publicKey": "e38c9f2760db006f070e9cc1bc1c2269ad033751adaa85d022fb760cbc5b5ef6" + }, + "ocrKeyBundle": { + "bundleID": "44b5f46bfbb04d0984469298ec43c350ec6b2cd4556b18265ebac1b6cc329c7c", + "configPublicKey": "263bee0d09d90e0e618c4cdd630d1437f7377f2d544df57f39ddd47984970555", + "offchainPublicKey": "11674b98849d8e070ac69d37c284b3091fcd374913f52b2b83ce2d9a4a4e0213", + "onchainSigningAddress": "425d1354a7b8180252a221040c718cac0ba0251c7efe31a2acefbba578dc2153" + }, + "plugins": {} + } + }, { "network": { "id": "140", @@ -455,6 +565,28 @@ "name": "Chainlink Sepolia Prod Keystone One 4", "publicKey": "07e0ffc57b6263604df517b94bd986169451a3c90600a855bb19212dc575de54", "chainConfigs": [ + { + "network": { + "id": "1406", + "chainID": "2", + "chainType": "APTOS", + "name": "APTOS TEST" + }, + "ocr2Config": { + "enabled": true, + "p2pKeyBundle": { + "peerID": "p2p_12D3KooWHqR1w26yHatTSZQW3xbRct9SxWzVj9X4SpU916Hy8jYg", + "publicKey": "77224be9d052343b1d17156a1e463625c0d746468d4f5a44cddd452365b1d4ed" + }, + "ocrKeyBundle": { + "bundleID": "b1ab478c1322bc4f8227be50898a8044efc70cf0156ec53cf132119db7e94dea", + "configPublicKey": "96ae354418e50dcd5b3dae62e8f0bc911bbce7f761220837aacdaa6f82bd0f29", + "offchainPublicKey": "b34bb49788541de8b6cfb321799a41927a391a4eb135c74f6cb14eec0531ee6f", + "onchainSigningAddress": "1221e131ef21014a6a99ed22376eb869746a3b5e30fd202cf79e44efaeb8c5c2" + }, + "plugins": {} + } + }, { "network": { "id": "140", @@ -541,9 +673,31 @@ "nodes": [ { "id": "786", - "name": "\tChainlink Sepolia Prod Keystone One 3", + "name": "Chainlink Sepolia Prod Keystone One 3", "publicKey": "487901e0c0a9d3c66e7cfc50f3a9e3cdbfdf1b0107273d73d94a91d278545516", "chainConfigs": [ + { + "network": { + "id": "1417", + "chainID": "2", + "chainType": "APTOS", + "name": "APTOS TEST" + }, + "ocr2Config": { + "enabled": true, + "p2pKeyBundle": { + "peerID": "p2p_12D3KooWCcVLytqinD8xMn27NvomcQhj2mqMVzyGemz6oPwv1SMT", + "publicKey": "298834a041a056df58c839cb53d99b78558693042e54dff238f504f16d18d4b6" + }, + "ocrKeyBundle": { + "bundleID": "5811a96a0c3b5f5b52973eee10e5771cf5953d37d5616ea71f7ae76f09f6e332", + "configPublicKey": "a7f3435bfbaabebd1572142ff1aec9ed98758d9bb098f1fcc77262fcae7f4171", + "offchainPublicKey": "886044b333af681ab4bf3be663122524ece9725e110ac2a64cda8526cad6983e", + "onchainSigningAddress": "046faf34ebfe42510251e6098bc34fa3dd5f2de38ac07e47f2d1b34ac770639f" + }, + "plugins": {} + } + }, { "network": { "id": "140", @@ -661,6 +815,28 @@ "plugins": {} } }, + { + "network": { + "id": "1408", + "chainID": "2", + "chainType": "APTOS", + "name": "APTOS TEST" + }, + "ocr2Config": { + "enabled": true, + "p2pKeyBundle": { + "peerID": "p2p_12D3KooWGDmBKZ7B3PynGrvfHTJMEecpjfHts9YK5NWk8oJuxcAo", + "publicKey": "5f247f61a6d5bfdd1d5064db0bd25fe443648133c6131975edb23481424e3d9c" + }, + "ocrKeyBundle": { + "bundleID": "e57c608a899d80e510913d2c7ef55758ee81e9eb73eb531003af1564307fd133", + "configPublicKey": "412a4bed6b064c17168871d28dbb965cc0a898f7b19eb3fa7cd01d3e3d10b66c", + "offchainPublicKey": "450aa794c87198a595761a8c18f0f1590046c8092960036638d002256af95254", + "onchainSigningAddress": "ba20d3da9b07663f1e8039081a514649fd61a48be2d241bc63537ee47d028fcd" + }, + "plugins": {} + } + }, { "network": { "id": "129", @@ -722,6 +898,28 @@ "name": "Chainlink Sepolia Prod Keystone One 1", "publicKey": "28b91143ec9111796a7d63e14c1cf6bb01b4ed59667ab54f5bc72ebe49c881be", "chainConfigs": [ + { + "network": { + "id": "1409", + "chainID": "2", + "chainType": "APTOS", + "name": "APTOS TEST" + }, + "ocr2Config": { + "enabled": true, + "p2pKeyBundle": { + "peerID": "p2p_12D3KooWCbDiL7sP9BVby5KaZqPpaVP1RBokoa9ShzH5WhkYX46v", + "publicKey": "2934f31f278e5c60618f85861bd6add54a4525d79a642019bdc87d75d26372c3" + }, + "ocrKeyBundle": { + "bundleID": "4b6418b8ab88ea1244c3c48eb5f4c86f9f0301aebffcac4fcfac5cdfb7cf6933", + "configPublicKey": "a38dbe521643479d78ab5477cae78161a5de0030c95098e3fbb09add6aca9508", + "offchainPublicKey": "7718dcbf40173dbd876720aa64028a6b18bf9a87543fc83a549515c4937962e3", + "onchainSigningAddress": "247d0189f65f58be83a4e7d87ff338aaf8956e9acb9fcc783f34f9edc29d1b40" + }, + "plugins": {} + } + }, { "network": { "id": "140", @@ -811,6 +1009,28 @@ "name": "Chainlink Sepolia Prod Keystone One 0", "publicKey": "403b72f0b1b3b5f5a91bcfedb7f28599767502a04b5b7e067fcf3782e23eeb9c", "chainConfigs": [ + { + "network": { + "id": "1411", + "chainID": "2", + "chainType": "APTOS", + "name": "APTOS TEST" + }, + "ocr2Config": { + "enabled": true, + "p2pKeyBundle": { + "peerID": "p2p_12D3KooWMWUKdoAc2ruZf9f55p7NVFj7AFiPm67xjQ8BZBwkqyYv", + "publicKey": "adb6bf005cdb23f21e11b82d66b9f62628c2939640ed93028bf0dad3923c5a8b" + }, + "ocrKeyBundle": { + "bundleID": "b4504e84ea307cc2afffca0206bd4bf8e98acc5a03c9bd47b2456e3845a5d1fa", + "configPublicKey": "559ea4ee5774a31d97914a4220d6a47094ae8e2cf0806e80e1eacd851f3e6757", + "offchainPublicKey": "4ec55bbe76a6b1fdc885c59da85a8fe44cf06afe1e4719f0824a731937526c52", + "onchainSigningAddress": "b8834eaa062f0df4ccfe7832253920071ec14dc4f78b13ecdda10b824e2dd3b6" + }, + "plugins": {} + } + }, { "network": { "id": "140", diff --git a/integration-tests/deployment/keystone/deploy_test.go b/integration-tests/deployment/keystone/deploy_test.go index 13adfc09740..7331155ecc6 100644 --- a/integration-tests/deployment/keystone/deploy_test.go +++ b/integration-tests/deployment/keystone/deploy_test.go @@ -1,7 +1,6 @@ package keystone_test import ( - "context" "encoding/json" "fmt" "os" @@ -13,6 +12,8 @@ import ( chainsel "github.com/smartcontractkit/chain-selectors" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + "github.com/smartcontractkit/chainlink/integration-tests/deployment" "github.com/smartcontractkit/chainlink/integration-tests/deployment/clo" "github.com/smartcontractkit/chainlink/integration-tests/deployment/clo/models" @@ -57,8 +58,7 @@ func TestDeploy(t *testing.T) { MaxFaultyOracles: len(wfNops) / 3, } - ctx := context.Background() - + ctx := tests.Context(t) // explicitly deploy the contracts cs, err := keystone.DeployContracts(lggr, env, registryChainSel) require.NoError(t, err) @@ -157,12 +157,17 @@ func TestDeploy(t *testing.T) { func makeMultiDonTestEnv(t *testing.T, lggr logger.Logger, dons []keystone.DonCapabilities) *deployment.Environment { var donToEnv = make(map[string]*deployment.Environment) + // chain selector lib doesn't support chain id 2 and we don't use it in tests + // because it's not an evm chain + ignoreAptos := func(c *models.NodeChainConfig) bool { + return c.Network.ChainID == "2" // aptos chain + } for _, don := range dons { env := clo.NewDonEnvWithMemoryChains(t, clo.DonEnvConfig{ DonName: don.Name, Nops: don.Nops, Logger: lggr, - }) + }, ignoreAptos) donToEnv[don.Name] = env } menv := clo.NewTestEnv(t, lggr, donToEnv) diff --git a/integration-tests/deployment/keystone/types.go b/integration-tests/deployment/keystone/types.go index d49d2180436..18c0e1dd75f 100644 --- a/integration-tests/deployment/keystone/types.go +++ b/integration-tests/deployment/keystone/types.go @@ -2,6 +2,7 @@ package keystone import ( "encoding/hex" + "encoding/json" "errors" "fmt" "sort" @@ -17,6 +18,7 @@ import ( v1 "github.com/smartcontractkit/chainlink/integration-tests/deployment/jd/node/v1" "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" ) @@ -61,10 +63,11 @@ type ocr2Node struct { EncryptionPublicKey [32]byte IsBoostrap bool // useful when have to register the ocr3 contract config - p2pKeyBundle *v1.OCR2Config_P2PKeyBundle - ocrKeyBundle *v1.OCR2Config_OCRKeyBundle - csaKey string // *v1.Node.PublicKey - accountAddress string + 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 { @@ -75,10 +78,10 @@ func (o *ocr2Node) toNodeKeys() NodeKeys { return NodeKeys{ EthAddress: o.accountAddress, P2PPeerID: o.p2pKeyBundle.PeerId, - OCR2BundleID: o.ocrKeyBundle.BundleId, - OCR2OnchainPublicKey: o.ocrKeyBundle.OnchainSigningAddress, - OCR2OffchainPublicKey: o.ocrKeyBundle.OffchainPublicKey, - OCR2ConfigPublicKey: o.ocrKeyBundle.ConfigPublicKey, + 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 @@ -87,10 +90,15 @@ func (o *ocr2Node) toNodeKeys() NodeKeys { } } -func newOcr2Node(id string, ccfg *v1.ChainConfig, csaPubKey string) (*ocr2Node, error) { - if ccfg == 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") } @@ -105,7 +113,7 @@ func newOcr2Node(id string, ccfg *v1.ChainConfig, csaPubKey string) (*ocr2Node, var csaKeyb [32]byte copy(csaKeyb[:], csaKey) - ocfg := ccfg.Ocr2Config + 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) @@ -123,17 +131,24 @@ func newOcr2Node(id string, ccfg *v1.ChainConfig, csaPubKey string) (*ocr2Node, var sigb [32]byte copy(sigb[:], signerB) - return &ocr2Node{ + n := &ocr2Node{ ID: id, Signer: sigb, P2PKey: p, EncryptionPublicKey: csaKeyb, IsBoostrap: ocfg.IsBootstrap, p2pKeyBundle: ocfg.P2PKeyBundle, - ocrKeyBundle: ocfg.OcrKeyBundle, - accountAddress: ccfg.AccountAddress, + ethOcr2KeyBundle: evmCC.Ocr2Config.OcrKeyBundle, + aptosOcr2KeyBundle: nil, + accountAddress: evmCC.AccountAddress, csaKey: csaPubKey, - }, nil + } + // 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 { @@ -210,12 +225,14 @@ func mapDonsToCaps(dons []DonCapabilities) map[string][]kcr.CapabilitiesRegistry } // 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 []DonCapabilities, excludeBootstraps bool) (map[string][]*ocr2Node, error) { donToOcr2Nodes := make(map[string][]*ocr2Node) // 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 for _, don := range dons { + isOCR3 := hasOCR3Capability(don.Capabilities) for _, nop := range don.Nops { for _, node := range nop.Nodes { csaPubKey := node.PublicKey @@ -226,9 +243,27 @@ func mapDonsToNodes(dons []DonCapabilities, excludeBootstraps bool) (map[string] if len(node.ChainConfigs) == 0 { return nil, fmt.Errorf("no chain configs for node %s. cannot obtain keys", node.ID) } - chain := node.ChainConfigs[0] - ccfg := chainConfigFromClo(chain) - ocr2n, err := newOcr2Node(node.ID, ccfg, *csaPubKey) + // all nodes should have an evm chain config, specifically the registry chain + evmCC, exists := firstChainConfigByType(node.ChainConfigs, chaintype.EVM) + if !exists { + return nil, fmt.Errorf("no evm chain config for node %s", node.ID) + } + cfgs := map[chaintype.ChainType]*v1.ChainConfig{ + chaintype.EVM: evmCC, + } + // wf nodes need to have aptos chain config + if isOCR3 { + aptosCC, exists := firstChainConfigByType(node.ChainConfigs, chaintype.Aptos) + if !exists { + debug, err := json.Marshal(node.ChainConfigs) + if err != nil { + debug = []byte("unmarshallable chain configs") + } + return nil, fmt.Errorf("ocr3 capability don no aptos chain config for node %s, configs %s", node.ID, debug) + } + cfgs[chaintype.Aptos] = aptosCC + } + ocr2n, err := newOcr2Node(node.ID, cfgs, *csaPubKey) if err != nil { return nil, fmt.Errorf("failed to create ocr2 node for node %s: %w", node.ID, err) } @@ -247,6 +282,25 @@ func mapDonsToNodes(dons []DonCapabilities, excludeBootstraps bool) (map[string] return donToOcr2Nodes, nil } +func firstChainConfigByType(ccfgs []*models.NodeChainConfig, t chaintype.ChainType) (*v1.ChainConfig, bool) { + for _, c := range ccfgs { + //nolint:staticcheck //ignore EqualFold it broke ci for some reason (go version skew btw local and ci?) + if strings.ToLower(c.Network.ChainType.String()) == strings.ToLower(string(t)) { + return chainConfigFromClo(c), true + } + } + return nil, false +} + +func hasOCR3Capability(caps []kcr.CapabilitiesRegistryCapability) bool { + for _, c := range caps { + if c.CapabilityType == 2 { + return true + } + } + return false +} + // RegisteredDon is a representation of a don that exists in the in the capabilities registry all with the enriched node data type RegisteredDon struct { Name string diff --git a/integration-tests/deployment/keystone/types_test.go b/integration-tests/deployment/keystone/types_test.go new file mode 100644 index 00000000000..8e1ff279df7 --- /dev/null +++ b/integration-tests/deployment/keystone/types_test.go @@ -0,0 +1,402 @@ +package keystone + +import ( + "encoding/json" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/test-go/testify/require" + + "github.com/smartcontractkit/chainlink/integration-tests/deployment/clo/models" + v1 "github.com/smartcontractkit/chainlink/integration-tests/deployment/jd/node/v1" + kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" + "github.com/smartcontractkit/chainlink/v2/core/services/keystore/chaintype" +) + +func Test_newOcr2Node(t *testing.T) { + type args struct { + id string + ccfgs map[chaintype.ChainType]*v1.ChainConfig + csaPubKey string + } + tests := []struct { + name string + args args + wantAptos bool + wantErr bool + }{ + { + name: "no aptos", + args: args{ + id: "1", + ccfgs: map[chaintype.ChainType]*v1.ChainConfig{ + chaintype.EVM: { + + Ocr2Config: &v1.OCR2Config{ + P2PKeyBundle: &v1.OCR2Config_P2PKeyBundle{ + PeerId: "p2p_12D3KooWMWUKdoAc2ruZf9f55p7NVFj7AFiPm67xjQ8BZBwkqyYv", + PublicKey: "pubKey", + }, + OcrKeyBundle: &v1.OCR2Config_OCRKeyBundle{ + BundleId: "bundleId", + ConfigPublicKey: "03dacd15fc96c965c648e3623180de002b71a97cf6eeca9affb91f461dcd6ce1", + OffchainPublicKey: "03dacd15fc96c965c648e3623180de002b71a97cf6eeca9affb91f461dcd6ce1", + OnchainSigningAddress: "b35409a8d4f9a18da55c5b2bb08a3f5f68d44442", + }, + }, + }, + }, + csaPubKey: "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", + }, + }, + { + name: "with aptos", + args: args{ + id: "1", + ccfgs: map[chaintype.ChainType]*v1.ChainConfig{ + chaintype.EVM: { + + Ocr2Config: &v1.OCR2Config{ + P2PKeyBundle: &v1.OCR2Config_P2PKeyBundle{ + PeerId: "p2p_12D3KooWMWUKdoAc2ruZf9f55p7NVFj7AFiPm67xjQ8BZBwkqyYv", + PublicKey: "pubKey", + }, + OcrKeyBundle: &v1.OCR2Config_OCRKeyBundle{ + BundleId: "bundleId", + ConfigPublicKey: "03dacd15fc96c965c648e3623180de002b71a97cf6eeca9affb91f461dcd6ce1", + OffchainPublicKey: "03dacd15fc96c965c648e3623180de002b71a97cf6eeca9affb91f461dcd6ce1", + OnchainSigningAddress: "b35409a8d4f9a18da55c5b2bb08a3f5f68d44442", + }, + }, + }, + chaintype.Aptos: { + + Ocr2Config: &v1.OCR2Config{ + P2PKeyBundle: &v1.OCR2Config_P2PKeyBundle{ + PeerId: "p2p_12D3KooWMWUKdoAc2ruZf9f55p7NVFj7AFiPm67xjQ8BZB11111", + PublicKey: "pubKey", + }, + OcrKeyBundle: &v1.OCR2Config_OCRKeyBundle{ + BundleId: "bundleId2", + ConfigPublicKey: "0000015fc96c965c648e3623180de002b71a97cf6eeca9affb91f461dcd6ce1", + OffchainPublicKey: "03dacd15fc96c965c648e3623180de002b71a97cf6eeca9affb91f461dcd6ce1", + OnchainSigningAddress: "111409a8d4f9a18da55c5b2bb08a3f5f68d44777", + }, + }, + }, + }, + csaPubKey: "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", + }, + wantAptos: true, + }, + { + name: "bad csa key", + args: args{ + id: "1", + ccfgs: map[chaintype.ChainType]*v1.ChainConfig{ + chaintype.EVM: { + + Ocr2Config: &v1.OCR2Config{ + P2PKeyBundle: &v1.OCR2Config_P2PKeyBundle{ + PeerId: "p2p_12D3KooWMWUKdoAc2ruZf9f55p7NVFj7AFiPm67xjQ8BZBwkqyYv", + PublicKey: "pubKey", + }, + OcrKeyBundle: &v1.OCR2Config_OCRKeyBundle{ + BundleId: "bundleId", + ConfigPublicKey: "03dacd15fc96c965c648e3623180de002b71a97cf6eeca9affb91f461dcd6ce1", + OffchainPublicKey: "03dacd15fc96c965c648e3623180de002b71a97cf6eeca9affb91f461dcd6ce1", + OnchainSigningAddress: "b35409a8d4f9a18da55c5b2bb08a3f5f68d44442", + }, + }, + }, + }, + csaPubKey: "not hex", + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := newOcr2Node(tt.args.id, tt.args.ccfgs, tt.args.csaPubKey) + if (err != nil) != tt.wantErr { + t.Errorf("newOcr2Node() error = %v, wantErr %v", err, tt.wantErr) + return + } + if tt.wantErr { + return + } + assert.NotNil(t, got.ethOcr2KeyBundle) + assert.NotNil(t, got.p2pKeyBundle) + assert.NotNil(t, got.Signer) + assert.NotNil(t, got.EncryptionPublicKey) + assert.NotEmpty(t, got.csaKey) + assert.NotEmpty(t, got.P2PKey) + assert.Equal(t, tt.wantAptos, got.aptosOcr2KeyBundle != nil) + }) + } +} + +func Test_mapDonsToNodes(t *testing.T) { + var ( + pubKey = "03dacd15fc96c965c648e3623180de002b71a97cf6eeca9affb91f461dcd6ce1" + evmSig = "b35409a8d4f9a18da55c5b2bb08a3f5f68d44442" + aptosSig = "b35409a8d4f9a18da55c5b2bb08a3f5f68d44442b35409a8d4f9a18da55c5b2bb08a3f5f68d44442" + peerID = "p2p_12D3KooWMWUKdoAc2ruZf9f55p7NVFj7AFiPm67xjQ8BZBwkqyYv" + // todo: these should be defined in common + writerCap = 3 + ocr3Cap = 2 + ) + 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, + }, + 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 err if no aptos 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.ChainTypeEvm, + }, + 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: true, + }, + { + name: "ocr3 cap evm & aptos", + 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, + }, + 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) + 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, "../clo/testdata/workflow_nodes.json") + cwNops := loadTestNops(t, "../clo/testdata/chain_writer_nodes.json") + assetNops := loadTestNops(t, "../clo/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) + require.NoError(t, err, "failed to map wf don") + _, err = mapDonsToNodes([]DonCapabilities{cwDon}, false) + require.NoError(t, err, "failed to map cw don") + _, err = mapDonsToNodes([]DonCapabilities{assetDon}, false) + 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 +}