diff --git a/core/scripts/go.mod b/core/scripts/go.mod index 176e3780bbf..9f0f60cb931 100644 --- a/core/scripts/go.mod +++ b/core/scripts/go.mod @@ -50,7 +50,6 @@ require ( filippo.io/edwards25519 v1.1.0 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect github.com/99designs/keyring v1.2.1 // indirect - github.com/AlekSi/pointer v1.1.0 // indirect github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect github.com/ChainSafe/go-schnorrkel v1.0.0 // indirect github.com/CosmWasm/wasmd v0.40.1 // indirect diff --git a/deployment/environment/clo/env.go b/deployment/environment/clo/env.go new file mode 100644 index 00000000000..d1683ad4e1e --- /dev/null +++ b/deployment/environment/clo/env.go @@ -0,0 +1,137 @@ +package clo + +import ( + "strconv" + "testing" + + "github.com/test-go/testify/require" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/environment/clo/models" + "github.com/smartcontractkit/chainlink/deployment/environment/memory" +) + +type DonEnvConfig struct { + DonName string + Chains map[uint64]deployment.Chain + Logger logger.Logger + Nops []*models.NodeOperator +} + +func NewDonEnv(t *testing.T, cfg DonEnvConfig) *deployment.Environment { + // no bootstraps in the don as far as capabilities registry is concerned + for _, nop := range cfg.Nops { + for _, node := range nop.Nodes { + for _, chain := range node.ChainConfigs { + 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) + } + } + } + } + out := deployment.NewEnvironment( + cfg.DonName, + cfg.Logger, + deployment.NewMemoryAddressBook(), + cfg.Chains, + make([]string, 0), + NewJobClient(cfg.Logger, cfg.Nops), + ) + // assume that all the nodes in the provided input nops are part of the don + for _, nop := range cfg.Nops { + for _, node := range nop.Nodes { + out.NodeIDs = append(out.NodeIDs, node.ID) + } + } + + return out +} + +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{}{} + } + } + } + var cs []uint64 + for c := range chains { + cs = append(cs, c) + } + memoryChains := memory.NewMemoryChainsWithChainIDs(t, cs) + e.Chains = memoryChains + return e +} + +// MultiDonEnvironment is a single logical deployment environment (like dev, testnet, prod,...). +// It represents the idea that different nodesets host different capabilities. +// Each element in the DonEnv is a logical set of nodes that host the same capabilities. +// This model allows us to reuse the existing Environment abstraction while supporting multiple nodesets at +// expense of slightly abusing the original abstraction. Specifically, the abuse is that +// each Environment in the DonToEnv map is a subset of the target deployment environment. +// One element cannot represent dev and other testnet for example. +type MultiDonEnvironment struct { + donToEnv map[string]*deployment.Environment + Logger logger.Logger + // hacky but temporary to transition to Environment abstraction. set by New + Chains map[uint64]deployment.Chain +} + +func (mde MultiDonEnvironment) Flatten(name string) *deployment.Environment { + // TODO: KS-460 integrate with the clo offchain client impl + // may need to extend the Environment abstraction use maps rather than slices for Nodes + // somehow we need to capture the fact that each nodes belong to nodesets which have different capabilities + // purposely nil to catch misuse until we do that work + return deployment.NewEnvironment( + name, + mde.Logger, + deployment.NewMemoryAddressBook(), + mde.Chains, + nil, + nil, + ) +} + +func newMultiDonEnvironment(logger logger.Logger, donToEnv map[string]*deployment.Environment) *MultiDonEnvironment { + chains := make(map[uint64]deployment.Chain) + for _, env := range donToEnv { + for sel, chain := range env.Chains { + if _, exists := chains[sel]; !exists { + chains[sel] = chain + } + } + } + return &MultiDonEnvironment{ + donToEnv: donToEnv, + Logger: logger, + Chains: chains, + } +} + +func NewTestEnv(t *testing.T, lggr logger.Logger, dons map[string]*deployment.Environment) *MultiDonEnvironment { + for _, don := range dons { + //don := don + seen := make(map[uint64]deployment.Chain) + // ensure that generated chains are the same for all environments. this ensures that he in memory representation + // points to a common object for all dons given the same selector. + for sel, chain := range don.Chains { + c, exists := seen[sel] + if exists { + don.Chains[sel] = c + } else { + seen[sel] = chain + } + } + } + return newMultiDonEnvironment(lggr, dons) +} diff --git a/deployment/environment/clo/offchain_client_impl.go b/deployment/environment/clo/offchain_client_impl.go index f8962e1440d..e670663b925 100644 --- a/deployment/environment/clo/offchain_client_impl.go +++ b/deployment/environment/clo/offchain_client_impl.go @@ -2,19 +2,14 @@ package clo import ( "context" - "fmt" - "slices" - "strings" "go.uber.org/zap" "google.golang.org/grpc" - "github.com/AlekSi/pointer" "github.com/smartcontractkit/chainlink-common/pkg/logger" 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/deployment/environment/clo/models" ) @@ -65,64 +60,39 @@ func (j JobClient) GetNode(ctx context.Context, in *nodev1.GetNodeRequest, opts } func (j JobClient) ListNodes(ctx context.Context, in *nodev1.ListNodesRequest, opts ...grpc.CallOption) (*nodev1.ListNodesResponse, error) { - include := func(node *nodev1.Node) bool { - if in.Filter == nil { + //TODO CCIP-3108 + var fiterIds map[string]struct{} + include := func(id string) bool { + if in.Filter == nil || len(in.Filter.Ids) == 0 { return true } - if len(in.Filter.Ids) > 0 { - idx := slices.IndexFunc(in.Filter.Ids, func(id string) bool { - return node.Id == id - }) - if idx < 0 { - return false + // lazy init + if len(fiterIds) == 0 { + for _, id := range in.Filter.Ids { + fiterIds[id] = struct{}{} } } - for _, selector := range in.Filter.Selectors { - idx := slices.IndexFunc(node.Labels, func(label *ptypes.Label) bool { - return label.Key == selector.Key - }) - if idx < 0 { - return false - } - label := node.Labels[idx] - - switch selector.Op { - case ptypes.SelectorOp_IN: - values := strings.Split(*selector.Value, ",") - found := slices.Contains(values, *label.Value) - if !found { - return false - } - default: - panic("unimplemented selector") - } - } - return true + _, ok := fiterIds[id] + return ok } var nodes []*nodev1.Node for _, nop := range j.NodeOperators { for _, n := range nop.Nodes { - node := &nodev1.Node{ - Id: n.ID, - Name: n.Name, - PublicKey: *n.PublicKey, - IsEnabled: n.Enabled, - IsConnected: n.Connected, - Labels: []*ptypes.Label{ - { - Key: "p2p_id", - Value: pointer.ToString(n.ID), // here n.ID is also peer ID - }, - }, - } - if include(node) { - nodes = append(nodes, node) + if include(n.ID) { + nodes = append(nodes, &nodev1.Node{ + Id: n.ID, + Name: n.Name, + PublicKey: *n.PublicKey, // is this the correct val? + IsEnabled: n.Enabled, + IsConnected: n.Connected, + }) } } } return &nodev1.ListNodesResponse{ Nodes: nodes, }, nil + } func (j JobClient) ListNodeChainConfigs(ctx context.Context, in *nodev1.ListNodeChainConfigsRequest, opts ...grpc.CallOption) (*nodev1.ListNodeChainConfigsResponse, error) { @@ -214,24 +184,10 @@ func cloNodeToChainConfigs(n *models.Node) []*nodev1.ChainConfig { } func cloChainCfgToJDChainCfg(ccfg *models.NodeChainConfig) *nodev1.ChainConfig { - var ctype nodev1.ChainType - switch ccfg.Network.ChainType { - case models.ChainTypeEvm: - ctype = nodev1.ChainType_CHAIN_TYPE_EVM - case models.ChainTypeSolana: - ctype = nodev1.ChainType_CHAIN_TYPE_SOLANA - case models.ChainTypeStarknet: - ctype = nodev1.ChainType_CHAIN_TYPE_STARKNET - case models.ChainTypeAptos: - ctype = nodev1.ChainType_CHAIN_TYPE_APTOS - default: - panic(fmt.Sprintf("Unsupported chain family %v", ccfg.Network.ChainType)) - } - return &nodev1.ChainConfig{ Chain: &nodev1.Chain{ Id: ccfg.Network.ChainID, - Type: ctype, + Type: nodev1.ChainType_CHAIN_TYPE_EVM, // TODO: write conversion func from clo to jd tyes }, AccountAddress: ccfg.AccountAddress, AdminAddress: ccfg.AdminAddress, diff --git a/deployment/environment/clo/offchain_client_impl_test.go b/deployment/environment/clo/offchain_client_impl_test.go index bc6f30a7db1..3c9277d9fb0 100644 --- a/deployment/environment/clo/offchain_client_impl_test.go +++ b/deployment/environment/clo/offchain_client_impl_test.go @@ -6,12 +6,10 @@ import ( "reflect" "testing" - "github.com/AlekSi/pointer" "github.com/test-go/testify/require" "google.golang.org/grpc" 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/environment/clo" "github.com/smartcontractkit/chainlink/deployment/environment/clo/models" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -137,12 +135,6 @@ func TestJobClient_ListNodes(t *testing.T) { Name: "Chainlink Sepolia Prod Keystone One 9", PublicKey: "412dc6fe48ea4e34baaa77da2e3b032d39b938597b6f3d61fe7ed183a827a431", IsConnected: true, - Labels: []*ptypes.Label{ - { - Key: "p2p_id", - Value: pointer.ToString("780"), - }, - }, }, }, }, @@ -163,24 +155,12 @@ func TestJobClient_ListNodes(t *testing.T) { Name: "Chainlink Sepolia Prod Keystone One 9", PublicKey: "412dc6fe48ea4e34baaa77da2e3b032d39b938597b6f3d61fe7ed183a827a431", IsConnected: true, - Labels: []*ptypes.Label{ - { - Key: "p2p_id", - Value: pointer.ToString("780"), - }, - }, }, { Id: "781", Name: "Chainlink Sepolia Prod Keystone One 8", PublicKey: "1141dd1e46797ced9b0fbad49115f18507f6f6e6e3cc86e7e5ba169e58645adc", IsConnected: true, - Labels: []*ptypes.Label{ - { - Key: "p2p_id", - Value: pointer.ToString("781"), - }, - }, }, }, }, @@ -201,24 +181,12 @@ func TestJobClient_ListNodes(t *testing.T) { Name: "Chainlink Sepolia Prod Keystone One 999", PublicKey: "9991dd1e46797ced9b0fbad49115f18507f6f6e6e3cc86e7e5ba169e58999999", IsConnected: true, - Labels: []*ptypes.Label{ - { - Key: "p2p_id", - Value: pointer.ToString("999"), - }, - }, }, { Id: "1000", Name: "Chainlink Sepolia Prod Keystone One 1000", PublicKey: "1000101e46797ced9b0fbad49115f18507f6f6e6e3cc86e7e5ba169e58641000", IsConnected: true, - Labels: []*ptypes.Label{ - { - Key: "p2p_id", - Value: pointer.ToString("1000"), - }, - }, }, }, }, diff --git a/deployment/environment/devenv/don.go b/deployment/environment/devenv/don.go index f9b449e208b..c14216f3894 100644 --- a/deployment/environment/devenv/don.go +++ b/deployment/environment/devenv/don.go @@ -34,7 +34,6 @@ type NodeInfo struct { Name string // name of the node, used to identify the node, helpful in logs AdminAddr string // admin address to send payments to, applicable only for non-bootstrap nodes MultiAddr string // multi address denoting node's FQN (needed for deriving P2PBootstrappers in OCR), applicable only for bootstrap nodes - Labels map[string]string // labels to use when registering the node with job distributor } type DON struct { @@ -105,12 +104,6 @@ func NewRegisteredDON(ctx context.Context, nodeInfo []NodeInfo, jd JobDistributo return nil, fmt.Errorf("failed to create node %d: %w", i, err) } // node Labels so that it's easier to query them - for key, value := range info.Labels { - node.labels = append(node.labels, &ptypes.Label{ - Key: key, - Value: pointer.ToString(value), - }) - } if info.IsBootstrap { // create multi address for OCR2, applicable only for bootstrap nodes if info.MultiAddr == "" { @@ -188,35 +181,17 @@ type JDChainConfigInput struct { func (n *Node) CreateCCIPOCRSupportedChains(ctx context.Context, chains []JDChainConfigInput, jd JobDistributor) error { for i, chain := range chains { chainId := strconv.FormatUint(chain.ChainID, 10) - var account string - switch chain.ChainType { - case "EVM": - accountAddr, err := n.gqlClient.FetchAccountAddress(ctx, chainId) - if err != nil { - return fmt.Errorf("failed to fetch account address for node %s: %w", n.Name, err) - } - if accountAddr == nil { - return fmt.Errorf("no account address found for node %s", n.Name) - } - if n.AccountAddr == nil { - n.AccountAddr = make(map[uint64]string) - } - n.AccountAddr[chain.ChainID] = *accountAddr - account = *accountAddr - case "APTOS", "SOLANA": - accounts, err := n.gqlClient.FetchKeys(ctx, chain.ChainType) - if err != nil { - return fmt.Errorf("failed to fetch account address for node %s: %w", n.Name, err) - } - if len(accounts) == 0 { - return fmt.Errorf("no account address found for node %s", n.Name) - } - - account = accounts[0] - default: - return fmt.Errorf("unsupported chainType %v", chain.ChainType) + accountAddr, err := n.gqlClient.FetchAccountAddress(ctx, chainId) + if err != nil { + return fmt.Errorf("failed to fetch account address for node %s: %w", n.Name, err) } - + if accountAddr == nil { + return fmt.Errorf("no account address found for node %s", n.Name) + } + if n.AccountAddr == nil { + n.AccountAddr = make(map[uint64]string) + } + n.AccountAddr[chain.ChainID] = *accountAddr peerID, err := n.gqlClient.FetchP2PPeerID(ctx) if err != nil { return fmt.Errorf("failed to fetch peer id for node %s: %w", n.Name, err) @@ -246,7 +221,7 @@ func (n *Node) CreateCCIPOCRSupportedChains(ctx context.Context, chains []JDChai JobDistributorID: n.JDId, ChainID: chainId, ChainType: chain.ChainType, - AccountAddr: account, + AccountAddr: pointer.GetString(accountAddr), AdminAddr: n.adminAddr, Ocr2Enabled: true, Ocr2IsBootstrap: isBootstrap, @@ -316,20 +291,6 @@ func (n *Node) RegisterNodeToJobDistributor(ctx context.Context, jd JobDistribut return fmt.Errorf("no csa key found for node %s", n.Name) } csaKey := strings.TrimPrefix(*csaKeyRes, "csa_") - - // tag nodes with p2p_id for easy lookup - peerID, err := n.gqlClient.FetchP2PPeerID(ctx) - if err != nil { - return fmt.Errorf("failed to fetch peer id for node %s: %w", n.Name, err) - } - if peerID == nil { - return fmt.Errorf("no peer id found for node %s", n.Name) - } - n.labels = append(n.labels, &ptypes.Label{ - Key: "p2p_id", - Value: pointer.ToString(*peerID), - }) - // register the node in the job distributor registerResponse, err := jd.RegisterNode(ctx, &nodev1.RegisterNodeRequest{ PublicKey: csaKey, diff --git a/deployment/environment/memory/chain.go b/deployment/environment/memory/chain.go index d66200685ea..0f3badc7dca 100644 --- a/deployment/environment/memory/chain.go +++ b/deployment/environment/memory/chain.go @@ -85,7 +85,7 @@ func GenerateChainsWithIds(t *testing.T, chainIDs []uint64) map[uint64]EVMChain owner, err := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) require.NoError(t, err) backend := backends.NewSimulatedBackend(core.GenesisAlloc{ - owner.From: {Balance: big.NewInt(0).Mul(big.NewInt(700000), big.NewInt(params.Ether))}}, 50000000) + owner.From: {Balance: big.NewInt(0).Mul(big.NewInt(100), big.NewInt(params.Ether))}}, 10000000) tweakChainTimestamp(t, backend, time.Hour*8) chains[chainID] = EVMChain{ Backend: backend, diff --git a/deployment/environment/memory/environment.go b/deployment/environment/memory/environment.go index ae2ba54c015..22733571038 100644 --- a/deployment/environment/memory/environment.go +++ b/deployment/environment/memory/environment.go @@ -5,7 +5,7 @@ import ( "fmt" "testing" - "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" "github.com/ethereum/go-ethereum/core/types" "github.com/hashicorp/consul/sdk/freeport" "github.com/stretchr/testify/require" @@ -29,18 +29,6 @@ type MemoryEnvironmentConfig struct { RegistryConfig deployment.CapabilityRegistryConfig } -// For placeholders like aptos -func NewMemoryChain(t *testing.T, selector uint64) deployment.Chain { - return deployment.Chain{ - Selector: selector, - Client: nil, - DeployerKey: &bind.TransactOpts{}, - Confirm: func(tx *types.Transaction) (uint64, error) { - return 0, nil - }, - } -} - // Needed for environment variables on the node which point to prexisitng addresses. // i.e. CapReg. func NewMemoryChains(t *testing.T, numChains int) map[uint64]deployment.Chain { @@ -90,19 +78,30 @@ func generateMemoryChain(t *testing.T, inputs map[uint64]EVMChain) map[uint64]de } func NewNodes(t *testing.T, logLevel zapcore.Level, chains map[uint64]deployment.Chain, numNodes, numBootstraps int, registryConfig deployment.CapabilityRegistryConfig) map[string]Node { + mchains := make(map[uint64]EVMChain) + for _, chain := range chains { + evmChainID, err := chainsel.ChainIdFromSelector(chain.Selector) + if err != nil { + t.Fatal(err) + } + mchains[evmChainID] = EVMChain{ + Backend: chain.Client.(*backends.SimulatedBackend), + DeployerKey: chain.DeployerKey, + } + } nodesByPeerID := make(map[string]Node) ports := freeport.GetN(t, numBootstraps+numNodes) // bootstrap nodes must be separate nodes from plugin nodes, // since we won't run a bootstrapper and a plugin oracle on the same // chainlink node in production. for i := 0; i < numBootstraps; i++ { - node := NewNode(t, ports[i], chains, logLevel, true /* bootstrap */, registryConfig) + node := NewNode(t, ports[i], mchains, logLevel, true /* bootstrap */, registryConfig) nodesByPeerID[node.Keys.PeerID.String()] = *node // Note in real env, this ID is allocated by JD. } for i := 0; i < numNodes; i++ { // grab port offset by numBootstraps, since above loop also takes some ports. - node := NewNode(t, ports[numBootstraps+i], chains, logLevel, false /* bootstrap */, registryConfig) + node := NewNode(t, ports[numBootstraps+i], mchains, logLevel, false /* bootstrap */, registryConfig) nodesByPeerID[node.Keys.PeerID.String()] = *node // Note in real env, this ID is allocated by JD. } diff --git a/deployment/environment/memory/job_client.go b/deployment/environment/memory/job_client.go index f243ef63818..d572f5f92f5 100644 --- a/deployment/environment/memory/job_client.go +++ b/deployment/environment/memory/job_client.go @@ -4,22 +4,15 @@ import ( "context" "errors" "fmt" - "slices" "strconv" - "strings" - "github.com/AlekSi/pointer" "github.com/ethereum/go-ethereum/common" "google.golang.org/grpc" - chainsel "github.com/smartcontractkit/chain-selectors" - 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/capabilities/ccip/validate" - "github.com/smartcontractkit/chainlink/v2/core/services/keystore/chaintype" ) type JobClient struct { @@ -69,7 +62,7 @@ func (j JobClient) GetNode(ctx context.Context, in *nodev1.GetNodeRequest, opts return &nodev1.GetNodeResponse{ Node: &nodev1.Node{ Id: in.Id, - PublicKey: n.Keys.CSA.PublicKeyString(), + PublicKey: n.Keys.OCRKeyBundle.ID(), // is this the correct val? IsEnabled: true, IsConnected: true, }, @@ -78,61 +71,35 @@ func (j JobClient) GetNode(ctx context.Context, in *nodev1.GetNodeRequest, opts func (j JobClient) ListNodes(ctx context.Context, in *nodev1.ListNodesRequest, opts ...grpc.CallOption) (*nodev1.ListNodesResponse, error) { //TODO CCIP-3108 - include := func(node *nodev1.Node) bool { - if in.Filter == nil { + var fiterIds map[string]struct{} + include := func(id string) bool { + if in.Filter == nil || len(in.Filter.Ids) == 0 { return true } - if len(in.Filter.Ids) > 0 { - idx := slices.IndexFunc(in.Filter.Ids, func(id string) bool { - return node.Id == id - }) - if idx < 0 { - return false - } - } - for _, selector := range in.Filter.Selectors { - idx := slices.IndexFunc(node.Labels, func(label *ptypes.Label) bool { - return label.Key == selector.Key - }) - if idx < 0 { - return false - } - label := node.Labels[idx] - - switch selector.Op { - case ptypes.SelectorOp_IN: - values := strings.Split(*selector.Value, ",") - found := slices.Contains(values, *label.Value) - if !found { - return false - } - default: - panic("unimplemented selector") + // lazy init + if len(fiterIds) == 0 { + for _, id := range in.Filter.Ids { + fiterIds[id] = struct{}{} } } - return true + _, ok := fiterIds[id] + return ok } var nodes []*nodev1.Node for id, n := range j.Nodes { - node := &nodev1.Node{ - Id: id, - PublicKey: n.Keys.CSA.ID(), - IsEnabled: true, - IsConnected: true, - Labels: []*ptypes.Label{ - { - Key: "p2p_id", - Value: pointer.ToString(n.Keys.PeerID.String()), - }, - }, - } - if include(node) { - nodes = append(nodes, node) + if include(id) { + nodes = append(nodes, &nodev1.Node{ + Id: id, + PublicKey: n.Keys.OCRKeyBundle.ID(), // is this the correct val? + IsEnabled: true, + IsConnected: true, + }) } } return &nodev1.ListNodesResponse{ Nodes: nodes, }, nil + } func (j JobClient) ListNodeChainConfigs(ctx context.Context, in *nodev1.ListNodeChainConfigsRequest, opts ...grpc.CallOption) (*nodev1.ListNodeChainConfigsResponse, error) { @@ -146,17 +113,8 @@ func (j JobClient) ListNodeChainConfigs(ctx context.Context, in *nodev1.ListNode if !ok { return nil, fmt.Errorf("node id not found: %s", in.Filter.NodeIds[0]) } - evmBundle := n.Keys.OCRKeyBundles[chaintype.EVM] - offpk := evmBundle.OffchainPublicKey() - cpk := evmBundle.ConfigEncryptionPublicKey() - - evmKeyBundle := &nodev1.OCR2Config_OCRKeyBundle{ - BundleId: evmBundle.ID(), - ConfigPublicKey: common.Bytes2Hex(cpk[:]), - OffchainPublicKey: common.Bytes2Hex(offpk[:]), - OnchainSigningAddress: evmBundle.OnChainPublicKey(), - } - + offpk := n.Keys.OCRKeyBundle.OffchainPublicKey() + cpk := n.Keys.OCRKeyBundle.ConfigEncryptionPublicKey() var chainConfigs []*nodev1.ChainConfig for evmChainID, transmitter := range n.Keys.TransmittersByEVMChainID { chainConfigs = append(chainConfigs, &nodev1.ChainConfig{ @@ -165,84 +123,6 @@ func (j JobClient) ListNodeChainConfigs(ctx context.Context, in *nodev1.ListNode Type: nodev1.ChainType_CHAIN_TYPE_EVM, }, AccountAddress: transmitter.String(), - AdminAddress: transmitter.String(), // TODO: custom address - Ocr1Config: nil, - Ocr2Config: &nodev1.OCR2Config{ - Enabled: true, - IsBootstrap: n.IsBoostrap, - P2PKeyBundle: &nodev1.OCR2Config_P2PKeyBundle{ - PeerId: n.Keys.PeerID.String(), - }, - OcrKeyBundle: evmKeyBundle, - Multiaddr: n.Addr.String(), - Plugins: nil, - ForwarderAddress: ptr(""), - }, - }) - } - for _, selector := range n.Chains { - family, err := chainsel.GetSelectorFamily(selector) - 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 - } - - var ocrtype chaintype.ChainType - switch family { - case chainsel.FamilyEVM: - ocrtype = chaintype.EVM - case chainsel.FamilySolana: - ocrtype = chaintype.Solana - case chainsel.FamilyStarknet: - ocrtype = chaintype.StarkNet - case chainsel.FamilyCosmos: - ocrtype = chaintype.Cosmos - case chainsel.FamilyAptos: - ocrtype = chaintype.Aptos - default: - panic(fmt.Sprintf("Unsupported chain family %v", family)) - } - - bundle := n.Keys.OCRKeyBundles[ocrtype] - - offpk := bundle.OffchainPublicKey() - cpk := bundle.ConfigEncryptionPublicKey() - - keyBundle := &nodev1.OCR2Config_OCRKeyBundle{ - BundleId: bundle.ID(), - ConfigPublicKey: common.Bytes2Hex(cpk[:]), - OffchainPublicKey: common.Bytes2Hex(offpk[:]), - OnchainSigningAddress: bundle.OnChainPublicKey(), - } - - var ctype nodev1.ChainType - switch family { - case chainsel.FamilyEVM: - ctype = nodev1.ChainType_CHAIN_TYPE_EVM - case chainsel.FamilySolana: - ctype = nodev1.ChainType_CHAIN_TYPE_SOLANA - case chainsel.FamilyStarknet: - ctype = nodev1.ChainType_CHAIN_TYPE_STARKNET - case chainsel.FamilyAptos: - ctype = nodev1.ChainType_CHAIN_TYPE_APTOS - default: - panic(fmt.Sprintf("Unsupported chain family %v", family)) - } - - chainConfigs = append(chainConfigs, &nodev1.ChainConfig{ - Chain: &nodev1.Chain{ - Id: strconv.Itoa(int(chainID)), - Type: ctype, - }, - AccountAddress: "", // TODO: support AccountAddress AdminAddress: "", Ocr1Config: nil, Ocr2Config: &nodev1.OCR2Config{ @@ -251,13 +131,19 @@ func (j JobClient) ListNodeChainConfigs(ctx context.Context, in *nodev1.ListNode P2PKeyBundle: &nodev1.OCR2Config_P2PKeyBundle{ PeerId: n.Keys.PeerID.String(), }, - OcrKeyBundle: keyBundle, + OcrKeyBundle: &nodev1.OCR2Config_OCRKeyBundle{ + BundleId: n.Keys.OCRKeyBundle.ID(), + ConfigPublicKey: common.Bytes2Hex(cpk[:]), + OffchainPublicKey: common.Bytes2Hex(offpk[:]), + OnchainSigningAddress: n.Keys.OCRKeyBundle.OnChainPublicKey(), + }, Multiaddr: n.Addr.String(), Plugins: nil, ForwarderAddress: ptr(""), }, }) } + // TODO: I think we can pull it from the feeds manager. return &nodev1.ListNodeChainConfigsResponse{ ChainConfigs: chainConfigs, diff --git a/deployment/environment/memory/node.go b/deployment/environment/memory/node.go index 133c70b8b29..a2a690cbae5 100644 --- a/deployment/environment/memory/node.go +++ b/deployment/environment/memory/node.go @@ -10,13 +10,11 @@ import ( "testing" "time" - "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" "github.com/ethereum/go-ethereum/common" gethtypes "github.com/ethereum/go-ethereum/core/types" chainsel "github.com/smartcontractkit/chain-selectors" "github.com/stretchr/testify/require" "go.uber.org/zap/zapcore" - "golang.org/x/exp/maps" "github.com/smartcontractkit/chainlink-common/pkg/config" "github.com/smartcontractkit/chainlink-common/pkg/loop" @@ -37,7 +35,6 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/services/keystore" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/chaintype" - "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/csakey" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ocr2key" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" "github.com/smartcontractkit/chainlink/v2/core/services/relay" @@ -49,7 +46,6 @@ import ( type Node struct { App chainlink.Application // Transmitter key/OCR keys for this node - Chains []uint64 // chain selectors Keys Keys Addr net.TCPAddr IsBoostrap bool @@ -72,23 +68,11 @@ func (n Node) ReplayLogs(chains map[uint64]uint64) error { func NewNode( t *testing.T, port int, // Port for the P2P V2 listener. - chains map[uint64]deployment.Chain, + chains map[uint64]EVMChain, logLevel zapcore.Level, bootstrap bool, registryConfig deployment.CapabilityRegistryConfig, ) *Node { - evmchains := make(map[uint64]EVMChain) - for _, chain := range chains { - evmChainID, err := chainsel.ChainIdFromSelector(chain.Selector) - if err != nil { - t.Fatal(err) - } - evmchains[evmChainID] = EVMChain{ - Backend: chain.Client.(*backends.SimulatedBackend), - DeployerKey: chain.DeployerKey, - } - } - // Do not want to load fixtures as they contain a dummy chainID. // Create database and initial configuration. cfg, db := heavyweight.FullTestDBNoFixturesV2(t, func(c *chainlink.Config, s *chainlink.Secrets) { @@ -118,7 +102,7 @@ func NewNode( c.Log.Level = ptr(configv2.LogLevel(logLevel)) var chainConfigs v2toml.EVMConfigs - for chainID := range evmchains { + for chainID := range chains { chainConfigs = append(chainConfigs, createConfigV2Chain(chainID)) } c.EVM = chainConfigs @@ -130,7 +114,7 @@ func NewNode( // Create clients for the core node backed by sim. clients := make(map[uint64]client.Client) - for chainID, chain := range evmchains { + for chainID, chain := range chains { clients[chainID] = client.NewSimulatedBackendClient(t, chain.Backend, big.NewInt(int64(chainID))) } @@ -194,7 +178,6 @@ func NewNode( return &Node{ App: app, - Chains: maps.Keys(chains), Keys: keys, Addr: net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: port}, IsBoostrap: bootstrap, @@ -203,84 +186,50 @@ func NewNode( type Keys struct { PeerID p2pkey.PeerID - CSA csakey.KeyV2 TransmittersByEVMChainID map[uint64]common.Address - OCRKeyBundles map[chaintype.ChainType]ocr2key.KeyBundle + OCRKeyBundle ocr2key.KeyBundle } func CreateKeys(t *testing.T, - app chainlink.Application, chains map[uint64]deployment.Chain) Keys { + app chainlink.Application, chains map[uint64]EVMChain) Keys { ctx := tests.Context(t) require.NoError(t, app.GetKeyStore().Unlock(ctx, "password")) _, err := app.GetKeyStore().P2P().Create(ctx) require.NoError(t, err) - csaKey, err := app.GetKeyStore().CSA().Create(ctx) - require.NoError(t, err) - p2pIDs, err := app.GetKeyStore().P2P().GetAll() require.NoError(t, err) require.Len(t, p2pIDs, 1) peerID := p2pIDs[0].PeerID() // create a transmitter for each chain transmitters := make(map[uint64]common.Address) - keybundles := make(map[chaintype.ChainType]ocr2key.KeyBundle) - for _, chain := range chains { - family, err := chainsel.GetSelectorFamily(chain.Selector) - require.NoError(t, err) - - var ctype chaintype.ChainType - switch family { - case chainsel.FamilyEVM: - ctype = chaintype.EVM - case chainsel.FamilySolana: - ctype = chaintype.Solana - case chainsel.FamilyStarknet: - ctype = chaintype.StarkNet - case chainsel.FamilyCosmos: - ctype = chaintype.Cosmos - case chainsel.FamilyAptos: - ctype = chaintype.Aptos - default: - panic(fmt.Sprintf("Unsupported chain family %v", family)) - } - - keybundle, err := app.GetKeyStore().OCR2().Create(ctx, ctype) - require.NoError(t, err) - keybundles[ctype] = keybundle - - if family != chainsel.FamilyEVM { - // TODO: only support EVM transmission keys for now - continue - } - - evmChainID, err := chainsel.ChainIdFromSelector(chain.Selector) - require.NoError(t, err) - - cid := big.NewInt(int64(evmChainID)) + for chainID, chain := range chains { + cid := big.NewInt(int64(chainID)) addrs, err2 := app.GetKeyStore().Eth().EnabledAddressesForChain(ctx, cid) require.NoError(t, err2) if len(addrs) == 1 { // just fund the address - transmitters[evmChainID] = addrs[0] + fundAddress(t, chain.DeployerKey, addrs[0], assets.Ether(10).ToInt(), chain.Backend) + transmitters[chainID] = addrs[0] } else { // create key and fund it _, err3 := app.GetKeyStore().Eth().Create(ctx, cid) - require.NoError(t, err3, "failed to create key for chain", evmChainID) + require.NoError(t, err3, "failed to create key for chain", chainID) sendingKeys, err3 := app.GetKeyStore().Eth().EnabledAddressesForChain(ctx, cid) require.NoError(t, err3) require.Len(t, sendingKeys, 1) - transmitters[evmChainID] = sendingKeys[0] + fundAddress(t, chain.DeployerKey, sendingKeys[0], assets.Ether(10).ToInt(), chain.Backend) + transmitters[chainID] = sendingKeys[0] } - backend := chain.Client.(*backends.SimulatedBackend) - fundAddress(t, chain.DeployerKey, transmitters[evmChainID], assets.Ether(1000).ToInt(), backend) } + require.Len(t, transmitters, len(chains)) + keybundle, err := app.GetKeyStore().OCR2().Create(ctx, chaintype.EVM) + require.NoError(t, err) return Keys{ PeerID: peerID, - CSA: csaKey, TransmittersByEVMChainID: transmitters, - OCRKeyBundles: keybundles, + OCRKeyBundle: keybundle, } } diff --git a/deployment/environment/memory/node_test.go b/deployment/environment/memory/node_test.go index 7cbcb66d04a..9142f48bbfe 100644 --- a/deployment/environment/memory/node_test.go +++ b/deployment/environment/memory/node_test.go @@ -12,7 +12,7 @@ import ( ) func TestNode(t *testing.T) { - chains := NewMemoryChains(t, 3) + chains := GenerateChains(t, 3) ports := freeport.GetN(t, 1) node := NewNode(t, ports[0], chains, zapcore.DebugLevel, false, deployment.CapabilityRegistryConfig{}) // We expect 3 transmitter keys diff --git a/deployment/environment/web/sdk/client/client.go b/deployment/environment/web/sdk/client/client.go index 162c6159004..b22f52f3af4 100644 --- a/deployment/environment/web/sdk/client/client.go +++ b/deployment/environment/web/sdk/client/client.go @@ -18,7 +18,6 @@ type Client interface { FetchCSAPublicKey(ctx context.Context) (*string, error) FetchP2PPeerID(ctx context.Context) (*string, error) FetchAccountAddress(ctx context.Context, chainID string) (*string, error) - FetchKeys(ctx context.Context, chainType string) ([]string, error) FetchOCR2KeyBundleID(ctx context.Context, chainType string) (string, error) GetJob(ctx context.Context, id string) (*generated.GetJobResponse, error) ListJobs(ctx context.Context, offset, limit int) (*generated.ListJobsResponse, error) @@ -128,32 +127,6 @@ func (c *client) FetchAccountAddress(ctx context.Context, chainID string) (*stri return nil, fmt.Errorf("no account found for chain %s", chainID) } -func (c *client) FetchKeys(ctx context.Context, chainType string) ([]string, error) { - keys, err := generated.FetchKeys(ctx, c.gqlClient) - if err != nil { - return nil, err - } - if keys == nil { - return nil, fmt.Errorf("no accounts found") - } - switch generated.OCR2ChainType(chainType) { - case generated.OCR2ChainTypeAptos: - var accounts []string - for _, key := range keys.AptosKeys.GetResults() { - accounts = append(accounts, key.Account) - } - return accounts, nil - case generated.OCR2ChainTypeSolana: - var accounts []string - for _, key := range keys.SolanaKeys.GetResults() { - accounts = append(accounts, key.Id) - } - return accounts, nil - default: - return nil, fmt.Errorf("unsupported chainType %v", chainType) - } -} - func (c *client) GetJob(ctx context.Context, id string) (*generated.GetJobResponse, error) { return generated.GetJob(ctx, c.gqlClient, id) } diff --git a/deployment/environment/web/sdk/internal/generated/generated.go b/deployment/environment/web/sdk/internal/generated/generated.go index 7b16e4a1e3f..68ab3e48e4f 100644 --- a/deployment/environment/web/sdk/internal/generated/generated.go +++ b/deployment/environment/web/sdk/internal/generated/generated.go @@ -1887,58 +1887,6 @@ type FetchCSAKeysResponse struct { // GetCsaKeys returns FetchCSAKeysResponse.CsaKeys, and is useful for accessing the field via an interface. func (v *FetchCSAKeysResponse) GetCsaKeys() FetchCSAKeysCsaKeysCSAKeysPayload { return v.CsaKeys } -// FetchKeysAptosKeysAptosKeysPayload includes the requested fields of the GraphQL type AptosKeysPayload. -type FetchKeysAptosKeysAptosKeysPayload struct { - Results []FetchKeysAptosKeysAptosKeysPayloadResultsAptosKey `json:"results"` -} - -// GetResults returns FetchKeysAptosKeysAptosKeysPayload.Results, and is useful for accessing the field via an interface. -func (v *FetchKeysAptosKeysAptosKeysPayload) GetResults() []FetchKeysAptosKeysAptosKeysPayloadResultsAptosKey { - return v.Results -} - -// FetchKeysAptosKeysAptosKeysPayloadResultsAptosKey includes the requested fields of the GraphQL type AptosKey. -type FetchKeysAptosKeysAptosKeysPayloadResultsAptosKey struct { - Id string `json:"id"` - Account string `json:"account"` -} - -// GetId returns FetchKeysAptosKeysAptosKeysPayloadResultsAptosKey.Id, and is useful for accessing the field via an interface. -func (v *FetchKeysAptosKeysAptosKeysPayloadResultsAptosKey) GetId() string { return v.Id } - -// GetAccount returns FetchKeysAptosKeysAptosKeysPayloadResultsAptosKey.Account, and is useful for accessing the field via an interface. -func (v *FetchKeysAptosKeysAptosKeysPayloadResultsAptosKey) GetAccount() string { return v.Account } - -// FetchKeysResponse is returned by FetchKeys on success. -type FetchKeysResponse struct { - SolanaKeys FetchKeysSolanaKeysSolanaKeysPayload `json:"solanaKeys"` - AptosKeys FetchKeysAptosKeysAptosKeysPayload `json:"aptosKeys"` -} - -// GetSolanaKeys returns FetchKeysResponse.SolanaKeys, and is useful for accessing the field via an interface. -func (v *FetchKeysResponse) GetSolanaKeys() FetchKeysSolanaKeysSolanaKeysPayload { return v.SolanaKeys } - -// GetAptosKeys returns FetchKeysResponse.AptosKeys, and is useful for accessing the field via an interface. -func (v *FetchKeysResponse) GetAptosKeys() FetchKeysAptosKeysAptosKeysPayload { return v.AptosKeys } - -// FetchKeysSolanaKeysSolanaKeysPayload includes the requested fields of the GraphQL type SolanaKeysPayload. -type FetchKeysSolanaKeysSolanaKeysPayload struct { - Results []FetchKeysSolanaKeysSolanaKeysPayloadResultsSolanaKey `json:"results"` -} - -// GetResults returns FetchKeysSolanaKeysSolanaKeysPayload.Results, and is useful for accessing the field via an interface. -func (v *FetchKeysSolanaKeysSolanaKeysPayload) GetResults() []FetchKeysSolanaKeysSolanaKeysPayloadResultsSolanaKey { - return v.Results -} - -// FetchKeysSolanaKeysSolanaKeysPayloadResultsSolanaKey includes the requested fields of the GraphQL type SolanaKey. -type FetchKeysSolanaKeysSolanaKeysPayloadResultsSolanaKey struct { - Id string `json:"id"` -} - -// GetId returns FetchKeysSolanaKeysSolanaKeysPayloadResultsSolanaKey.Id, and is useful for accessing the field via an interface. -func (v *FetchKeysSolanaKeysSolanaKeysPayloadResultsSolanaKey) GetId() string { return v.Id } - // FetchOCR2KeyBundlesOcr2KeyBundlesOCR2KeyBundlesPayload includes the requested fields of the GraphQL type OCR2KeyBundlesPayload. type FetchOCR2KeyBundlesOcr2KeyBundlesOCR2KeyBundlesPayload struct { Results []FetchOCR2KeyBundlesOcr2KeyBundlesOCR2KeyBundlesPayloadResultsOCR2KeyBundle `json:"results"` @@ -5712,45 +5660,6 @@ func FetchCSAKeys( return &data_, err_ } -// The query or mutation executed by FetchKeys. -const FetchKeys_Operation = ` -query FetchKeys { - solanaKeys { - results { - id - } - } - aptosKeys { - results { - id - account - } - } -} -` - -func FetchKeys( - ctx_ context.Context, - client_ graphql.Client, -) (*FetchKeysResponse, error) { - req_ := &graphql.Request{ - OpName: "FetchKeys", - Query: FetchKeys_Operation, - } - var err_ error - - var data_ FetchKeysResponse - resp_ := &graphql.Response{Data: &data_} - - err_ = client_.MakeRequest( - ctx_, - req_, - resp_, - ) - - return &data_, err_ -} - // The query or mutation executed by FetchOCR2KeyBundles. const FetchOCR2KeyBundles_Operation = ` query FetchOCR2KeyBundles { diff --git a/deployment/environment/web/sdk/internal/genqlient.graphql b/deployment/environment/web/sdk/internal/genqlient.graphql index 4c998a4f6a6..06baf4f7913 100644 --- a/deployment/environment/web/sdk/internal/genqlient.graphql +++ b/deployment/environment/web/sdk/internal/genqlient.graphql @@ -45,20 +45,6 @@ query FetchAccounts { } } -query FetchKeys { - solanaKeys { - results { - id - } - } - aptosKeys { - results { - id - account - } - } -} - ##################### # ocr2KeyBundles ##################### @@ -470,4 +456,4 @@ mutation UpdateJobProposalSpecDefinition( code } } -} +} \ No newline at end of file diff --git a/deployment/go.mod b/deployment/go.mod index 594c0043006..26342d19ca2 100644 --- a/deployment/go.mod +++ b/deployment/go.mod @@ -31,7 +31,6 @@ require ( github.com/testcontainers/testcontainers-go v0.34.0 go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 - golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c golang.org/x/sync v0.8.0 google.golang.org/grpc v1.67.1 google.golang.org/protobuf v1.35.1 @@ -476,6 +475,7 @@ require ( go4.org/netipx v0.0.0-20230125063823-8449b0a6169f // indirect golang.org/x/arch v0.11.0 // indirect golang.org/x/crypto v0.28.0 // indirect + golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect golang.org/x/mod v0.21.0 // indirect golang.org/x/net v0.30.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect diff --git a/deployment/keystone/changeset/deploy_ocr3.go b/deployment/keystone/changeset/deploy_ocr3.go index e0edf4a4440..016eaa97d1f 100644 --- a/deployment/keystone/changeset/deploy_ocr3.go +++ b/deployment/keystone/changeset/deploy_ocr3.go @@ -5,6 +5,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/environment/clo/models" kslib "github.com/smartcontractkit/chainlink/deployment/keystone" ) @@ -26,8 +27,9 @@ func DeployOCR3(env deployment.Environment, config interface{}) (deployment.Chan return deployment.ChangesetOutput{AddressBook: ab}, nil } -func ConfigureOCR3Contract(lggr logger.Logger, env deployment.Environment, ab deployment.AddressBook, registryChainSel uint64, nodes []string, cfg kslib.OracleConfigWithSecrets) (deployment.ChangesetOutput, error) { - err := kslib.ConfigureOCR3ContractFromJD(&env, registryChainSel, nodes, ab, &cfg) +func ConfigureOCR3Contract(lggr logger.Logger, env deployment.Environment, ab deployment.AddressBook, registryChainSel uint64, nodes []*models.Node, cfg kslib.OracleConfigWithSecrets) (deployment.ChangesetOutput, error) { + + err := kslib.ConfigureOCR3ContractFromCLO(&env, registryChainSel, nodes, ab, &cfg) if err != nil { return deployment.ChangesetOutput{}, fmt.Errorf("failed to configure OCR3Capability: %w", err) } diff --git a/deployment/keystone/changeset/internal/update_don_test.go b/deployment/keystone/changeset/internal/update_don_test.go index 12ccfe290b1..baedda5e93d 100644 --- a/deployment/keystone/changeset/internal/update_don_test.go +++ b/deployment/keystone/changeset/internal/update_don_test.go @@ -4,14 +4,14 @@ import ( "bytes" "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" + "github.com/smartcontractkit/chainlink/deployment/environment/clo/models" kslib "github.com/smartcontractkit/chainlink/deployment/keystone" kscs "github.com/smartcontractkit/chainlink/deployment/keystone/changeset" "github.com/smartcontractkit/chainlink/deployment/keystone/changeset/internal" @@ -95,19 +95,18 @@ func TestUpdateDon(t *testing.T) { t.Run("empty", func(t *testing.T) { cfg := setupUpdateDonTestConfig{ - dons: []kslib.DonInfo{ + dons: []kslib.DonCapabilities{ { - Name: "don 1", - Nodes: []keystone.Node{node_1, node_2, node_3, node_4}, + Name: "don 1", + Nops: []*models.NodeOperator{ + { + Name: "nop 1", + Nodes: []*models.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}, - }, - }, } testCfg := setupUpdateDonTest(t, lggr, cfg) @@ -170,24 +169,26 @@ type minimalNodeCfg struct { admin common.Address } -func newNode(t *testing.T, cfg minimalNodeCfg) keystone.Node { +func newNode(t *testing.T, cfg minimalNodeCfg) *models.Node { t.Helper() - return keystone.Node{ + return &models.Node{ ID: cfg.id, PublicKey: &cfg.pubKey, - ChainConfigs: []*nodev1.ChainConfig{ + ChainConfigs: []*models.NodeChainConfig{ { - Chain: &nodev1.Chain{ - Id: "test chain", - Type: nodev1.ChainType_CHAIN_TYPE_EVM, + ID: "test chain", + Network: &models.Network{ + ID: "test network 1", + ChainID: strconv.FormatUint(cfg.registryChain.EvmChainID, 10), + ChainType: models.ChainTypeEvm, }, AdminAddress: cfg.admin.String(), - Ocr2Config: &nodev1.OCR2Config{ - P2PKeyBundle: &nodev1.OCR2Config_P2PKeyBundle{ - PeerId: cfg.p2p.PeerID().String(), + Ocr2Config: &models.NodeOCR2Config{ + P2pKeyBundle: &models.NodeOCR2ConfigP2PKeyBundle{ + PeerID: cfg.p2p.PeerID().String(), }, - OcrKeyBundle: &nodev1.OCR2Config_OCRKeyBundle{ + OcrKeyBundle: &models.NodeOCR2ConfigOCRKeyBundle{ OnchainSigningAddress: cfg.signingAddr, }, }, @@ -197,8 +198,7 @@ func newNode(t *testing.T, cfg minimalNodeCfg) keystone.Node { } type setupUpdateDonTestConfig struct { - dons []kslib.DonInfo - nops []keystone.NOP + dons []kslib.DonCapabilities } type setupUpdateDonTestResult struct { @@ -208,19 +208,28 @@ type setupUpdateDonTestResult struct { func setupUpdateDonTest(t *testing.T, lggr logger.Logger, cfg setupUpdateDonTestConfig) *kstest.SetupTestRegistryResponse { t.Helper() - req := newSetupTestRegistryRequest(t, cfg.dons, cfg.nops) + req := newSetupTestRegistryRequest(t, cfg.dons) return kstest.SetupTestRegistry(t, lggr, req) } -func newSetupTestRegistryRequest(t *testing.T, dons []kslib.DonInfo, nops []keystone.NOP) *kstest.SetupTestRegistryRequest { +func newSetupTestRegistryRequest(t *testing.T, dons []kslib.DonCapabilities) *kstest.SetupTestRegistryRequest { t.Helper() - nodes := make(map[string]keystone.Node) + allNops := make(map[string]*models.NodeOperator) for _, don := range dons { - for _, node := range don.Nodes { - nodes[node.ID] = node + for _, nop := range don.Nops { + nop := nop + n, exists := allNops[nop.ID] + if exists { + nop.Nodes = append(n.Nodes, nop.Nodes...) + } + allNops[nop.ID] = nop } } - nopsToNodes := makeNopToNodes(t, nops, nodes) + var nops []*models.NodeOperator + for _, nop := range allNops { + nops = append(nops, nop) + } + nopsToNodes := makeNopToNodes(t, nops) testDons := makeTestDon(t, dons) p2pToCapabilities := makeP2PToCapabilities(t, dons) req := &kstest.SetupTestRegistryRequest{ @@ -231,45 +240,46 @@ 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, cloNops []*models.NodeOperator) map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc { nopToNodes := make(map[kcr.CapabilitiesRegistryNodeOperator][]*internal.P2PSignerEnc) - for _, nop := range nops { + for _, nop := range cloNops { // all chain configs are the same wrt admin address & node keys // so we can just use the first one crnop := kcr.CapabilitiesRegistryNodeOperator{ Name: nop.Name, - Admin: common.HexToAddress(nodes[nop.Nodes[0]].ChainConfigs[0].AdminAddress), + Admin: common.HexToAddress(nop.Nodes[0].ChainConfigs[0].AdminAddress), } - var signers []*internal.P2PSignerEnc - for _, nodeID := range nop.Nodes { - node := nodes[nodeID] + var nodes []*internal.P2PSignerEnc + for _, node := range nop.Nodes { require.NotNil(t, node.PublicKey, "public key is nil %s", node.ID) // all chain configs are the same wrt admin address & node keys - p, err := kscs.NewP2PSignerEncFromJD(node.ChainConfigs[0], *node.PublicKey) + p, err := kscs.NewP2PSignerEncFromCLO(node.ChainConfigs[0], *node.PublicKey) require.NoError(t, err, "failed to make p2p signer enc from clo nod %s", node.ID) - signers = append(signers, p) + nodes = append(nodes, p) } - nopToNodes[crnop] = signers + nopToNodes[crnop] = nodes } return nopToNodes } -func makeP2PToCapabilities(t *testing.T, dons []kslib.DonInfo) map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability { +func makeP2PToCapabilities(t *testing.T, dons []kslib.DonCapabilities) map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability { p2pToCapabilities := make(map[p2pkey.PeerID][]kcr.CapabilitiesRegistryCapability) 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) - p2pToCapabilities[p.P2PKey] = append(p2pToCapabilities[p.P2PKey], cap) + for _, nop := range don.Nops { + for _, node := range nop.Nodes { + for _, cap := range don.Capabilities { + p, err := kscs.NewP2PSignerEncFromCLO(node.ChainConfigs[0], *node.PublicKey) + require.NoError(t, err, "failed to make p2p signer enc from clo nod %s", node.ID) + p2pToCapabilities[p.P2PKey] = append(p2pToCapabilities[p.P2PKey], cap) + } } } } return p2pToCapabilities } -func makeTestDon(t *testing.T, dons []kslib.DonInfo) []kstest.Don { +func makeTestDon(t *testing.T, dons []kslib.DonCapabilities) []kstest.Don { out := make([]kstest.Don, len(dons)) for i, don := range dons { out[i] = testDon(t, don) @@ -277,14 +287,16 @@ func makeTestDon(t *testing.T, dons []kslib.DonInfo) []kstest.Don { return out } -func testDon(t *testing.T, don kslib.DonInfo) kstest.Don { +func testDon(t *testing.T, don kslib.DonCapabilities) kstest.Don { var p2pids []p2pkey.PeerID - 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) - p2pids = append(p2pids, p.P2PKey) + for _, nop := range don.Nops { + for _, node := range nop.Nodes { + // all chain configs are the same wrt admin address & node keys + // so we can just use the first one + p, err := kscs.NewP2PSignerEncFromCLO(node.ChainConfigs[0], *node.PublicKey) + require.NoError(t, err, "failed to make p2p signer enc from clo nod %s", node.ID) + p2pids = append(p2pids, p.P2PKey) + } } var capabilityConfigs []internal.CapabilityConfig diff --git a/deployment/keystone/changeset/types.go b/deployment/keystone/changeset/types.go index fb609041792..e8a86fa4272 100644 --- a/deployment/keystone/changeset/types.go +++ b/deployment/keystone/changeset/types.go @@ -6,17 +6,24 @@ import ( "fmt" v1 "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/node" + "github.com/smartcontractkit/chainlink/deployment/environment/clo" + "github.com/smartcontractkit/chainlink/deployment/environment/clo/models" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey" ) -func NewP2PSignerEncFromJD(ccfg *v1.ChainConfig, pubkeyStr string) (*P2PSignerEnc, error) { +func NewP2PSignerEncFromCLO(cc *models.NodeChainConfig, pubkey string) (*P2PSignerEnc, error) { + ccfg := clo.NewChainConfig(cc) + var pubkeyB [32]byte + if _, err := hex.Decode(pubkeyB[:], []byte(pubkey)); err != nil { + return nil, fmt.Errorf("failed to decode pubkey %s: %w", pubkey, err) + } + return newP2PSignerEncFromJD(ccfg, pubkeyB) +} + +func newP2PSignerEncFromJD(ccfg *v1.ChainConfig, pubkey [32]byte) (*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 { diff --git a/deployment/keystone/changeset/update_node_capabilities.go b/deployment/keystone/changeset/update_node_capabilities.go index a2f52be989c..09cf351cc85 100644 --- a/deployment/keystone/changeset/update_node_capabilities.go +++ b/deployment/keystone/changeset/update_node_capabilities.go @@ -6,7 +6,7 @@ import ( chainsel "github.com/smartcontractkit/chain-selectors" "github.com/smartcontractkit/chainlink/deployment" - "github.com/smartcontractkit/chainlink/deployment/keystone" + "github.com/smartcontractkit/chainlink/deployment/environment/clo/models" kslib "github.com/smartcontractkit/chainlink/deployment/keystone" "github.com/smartcontractkit/chainlink/deployment/keystone/changeset/internal" @@ -18,7 +18,7 @@ var _ deployment.ChangeSet = UpdateNodeCapabilities type P2PSignerEnc = internal.P2PSignerEnc -func NewP2PSignerEnc(n *keystone.Node, registryChainSel uint64) (*P2PSignerEnc, error) { +func NewP2PSignerEnc(n *models.Node, registryChainSel uint64) (*P2PSignerEnc, error) { p2p, signer, enc, err := kslib.ExtractKeys(n, registryChainSel) if err != nil { return nil, fmt.Errorf("failed to extract keys: %w", err) diff --git a/deployment/keystone/deploy.go b/deployment/keystone/deploy.go index 737850cc0fa..8838312121a 100644 --- a/deployment/keystone/deploy.go +++ b/deployment/keystone/deploy.go @@ -7,18 +7,15 @@ import ( "encoding/hex" "errors" "fmt" - "slices" "sort" "strings" "time" - "github.com/AlekSi/pointer" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/rpc" - 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" + "github.com/smartcontractkit/chainlink/deployment/environment/clo/models" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/durationpb" @@ -93,13 +90,8 @@ func ConfigureContracts(ctx context.Context, lggr logger.Logger, req ConfigureCo return nil, fmt.Errorf("failed to configure registry: %w", err) } - donInfos, err := DonInfos(req.Dons, req.Env.Offchain) - if err != nil { - return nil, fmt.Errorf("failed to get don infos: %w", err) - } - // now we have the capability registry set up we need to configure the forwarder contracts and the OCR3 contract - dons, err := joinInfoAndNodes(cfgRegistryResp.DonInfos, donInfos, req.RegistryChainSel) + dons, err := joinInfoAndNodes(cfgRegistryResp.DonInfos, req.Dons, req.RegistryChainSel) if err != nil { return nil, fmt.Errorf("failed to assimilate registry to Dons: %w", err) } @@ -145,91 +137,6 @@ func DeployContracts(lggr logger.Logger, e *deployment.Environment, chainSel uin }, nil } -// DonInfo is DonCapabilities, but expanded to contain node information -type DonInfo struct { - Name string - Nodes []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: pointer.ToString(selector), - }, - }, - }, - }) - if err != nil { - return nil, err - } - for _, nodeP2PID := range nodeIDs { - // TODO: Filter should accept multiple nodes - nodeChainConfigs, err := jd.ListNodeChainConfigs(context.Background(), &nodev1.ListNodeChainConfigsRequest{Filter: &nodev1.ListNodeChainConfigsRequest_Filter{ - NodeIds: []string{nodeP2PID}, - }}) - if err != nil { - return nil, err - } - 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 == nodeP2PID - }) - }) - if idx < 0 { - return nil, fmt.Errorf("node %v not found", nodeP2PID) - } - jdNode := nodesFromJD.Nodes[idx] - - nodes = append(nodes, Node{ - ID: jdNode.Id, - P2PID: nodeP2PID, - 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 { - var nodeIDs []string - for _, nop := range don.Nops { - nodeIDs = append(nodeIDs, nop.Nodes...) - } - nodes, err := NodesFromJD(don.Name, nodeIDs, jd) - if err != nil { - return nil, err - } - donInfos = append(donInfos, DonInfo{ - Name: don.Name, - Nodes: nodes, - Capabilities: don.Capabilities, - }) - } - return donInfos, nil -} - // ConfigureRegistry configures the registry contract with the given DONS and their capabilities // the address book is required to contain the addresses of the deployed registry contract func ConfigureRegistry(ctx context.Context, lggr logger.Logger, req ConfigureContractsRequest, addrBook deployment.AddressBook) (*ConfigureContractsResponse, error) { @@ -246,11 +153,6 @@ func ConfigureRegistry(ctx context.Context, lggr logger.Logger, req ConfigureCon return nil, fmt.Errorf("failed to get contract sets: %w", err) } - donInfos, err := DonInfos(req.Dons, req.Env.Offchain) - if err != nil { - return nil, fmt.Errorf("failed to get don infos: %w", err) - } - // ensure registry is deployed and get the registry contract and chain var registry *kcr.CapabilitiesRegistry registryChainContracts, ok := contractSetsResp.ContractSets[req.RegistryChainSel] @@ -265,15 +167,15 @@ 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) + donToOcr2Nodes, err := mapDonsToNodes(req.Dons, true, req.RegistryChainSel) if err != nil { return nil, fmt.Errorf("failed to map dons to nodes: %w", err) } // TODO: we can remove this abstractions and refactor the functions that accept them to accept []DonCapabilities // they are unnecessary indirection - donToCapabilities := mapDonsToCaps(donInfos) - nodeIdToNop, err := nodesToNops(donInfos, req.RegistryChainSel) + donToCapabilities := mapDonsToCaps(req.Dons) + nodeIdToNop, err := nodesToNops(req.Dons, req.RegistryChainSel) if err != nil { return nil, fmt.Errorf("failed to map nodes to nops: %w", err) } @@ -318,8 +220,6 @@ func ConfigureRegistry(ctx context.Context, lggr logger.Logger, req ConfigureCon } lggr.Infow("registered nodes", "nodes", nodesResp.nodeIDToParams) - // TODO: annotate nodes with node_operator_id in JD? - // register DONS donsResp, err := registerDons(lggr, registerDonsRequest{ registry: registry, @@ -414,7 +314,7 @@ func ConfigureOCR3Contract(env *deployment.Environment, chainSel uint64, dons [] return nil } -func ConfigureOCR3ContractFromJD(env *deployment.Environment, chainSel uint64, nodeIDs []string, addrBook deployment.AddressBook, cfg *OracleConfigWithSecrets) error { +func ConfigureOCR3ContractFromCLO(env *deployment.Environment, chainSel uint64, nodes []*models.Node, addrBook deployment.AddressBook, cfg *OracleConfigWithSecrets) error { registryChain, ok := env.Chains[chainSel] if !ok { return fmt.Errorf("chain %d not found in environment", chainSel) @@ -434,13 +334,9 @@ func ConfigureOCR3ContractFromJD(env *deployment.Environment, chainSel uint64, n if contract == nil { return fmt.Errorf("no ocr3 contract found for chain %d", chainSel) } - nodes, err := NodesFromJD("nodes", nodeIDs, env.Offchain) - if err != nil { - return err - } var ocr2nodes []*ocr2Node for _, node := range nodes { - n, err := newOcr2NodeFromClo(&node, chainSel) + n, err := newOcr2NodeFromClo(node, chainSel) if err != nil { return fmt.Errorf("failed to create ocr2 node from clo node: %w", err) } diff --git a/deployment/keystone/deploy_test.go b/deployment/keystone/deploy_test.go index e362270c110..211e273c38e 100644 --- a/deployment/keystone/deploy_test.go +++ b/deployment/keystone/deploy_test.go @@ -4,14 +4,11 @@ import ( "encoding/json" "fmt" "os" - "strconv" "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" @@ -20,7 +17,6 @@ import ( "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" @@ -29,201 +25,6 @@ import ( func TestDeploy(t *testing.T) { lggr := logger.TestLogger(t) - // sepolia; all nodes are on the this chain - sepoliaChainId := uint64(11155111) - sepoliaArbitrumChainId := uint64(421614) - - sepoliaChainSel, err := chainsel.SelectorFromChainId(sepoliaChainId) - require.NoError(t, err) - // sepoliaArbitrumChainSel, err := chainsel.SelectorFromChainId(sepoliaArbitrumChainId) - // require.NoError(t, err) - // aptosChainSel := uint64(999) // TODO: - - crConfig := deployment.CapabilityRegistryConfig{ - EVMChainID: sepoliaChainId, - Contract: [20]byte{}, - } - - evmChains := memory.NewMemoryChainsWithChainIDs(t, []uint64{sepoliaChainId, sepoliaArbitrumChainId}) - // aptosChain := memory.NewMemoryChain(t, aptosChainSel) - - wfChains := map[uint64]deployment.Chain{} - wfChains[sepoliaChainSel] = evmChains[sepoliaChainSel] - // wfChains[aptosChainSel] = aptosChain - wfNodes := memory.NewNodes(t, zapcore.InfoLevel, wfChains, 4, 0, crConfig) - require.Len(t, wfNodes, 4) - - cwNodes := memory.NewNodes(t, zapcore.InfoLevel, evmChains, 4, 0, crConfig) - - assetChains := map[uint64]deployment.Chain{} - assetChains[sepoliaChainSel] = evmChains[sepoliaChainSel] - assetNodes := memory.NewNodes(t, zapcore.InfoLevel, assetChains, 4, 0, crConfig) - require.Len(t, assetNodes, 4) - - // TODO: partition nodes into multiple nops - - wfDon := keystone.DonCapabilities{ - Name: keystone.WFDonName, - Nops: []keystone.NOP{ - { - Name: "nop 1", - Nodes: maps.Keys(wfNodes), - }, - }, - Capabilities: []kcr.CapabilitiesRegistryCapability{keystone.OCR3Cap}, - } - cwDon := keystone.DonCapabilities{ - Name: keystone.TargetDonName, - Nops: []keystone.NOP{ - { - Name: "nop 2", - Nodes: maps.Keys(cwNodes), - }, - }, - Capabilities: []kcr.CapabilitiesRegistryCapability{keystone.WriteChainCap}, - } - assetDon := keystone.DonCapabilities{ - Name: keystone.StreamDonName, - Nops: []keystone.NOP{ - { - Name: "nop 3", - Nodes: maps.Keys(assetNodes), - }, - }, - Capabilities: []kcr.CapabilitiesRegistryCapability{keystone.StreamTriggerCap}, - } - - allChains := make(map[uint64]deployment.Chain) - maps.Copy(allChains, evmChains) - // allChains[aptosChainSel] = aptosChain - - allNodes := make(map[string]memory.Node) - maps.Copy(allNodes, wfNodes) - maps.Copy(allNodes, cwNodes) - maps.Copy(allNodes, assetNodes) - env := memory.NewMemoryEnvironmentFromChainsNodes(t, lggr, allChains, allNodes) - - var ocr3Config = keystone.OracleConfigWithSecrets{ - OracleConfig: keystone.OracleConfig{ - MaxFaultyOracles: len(wfNodes) / 3, - }, - OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), - } - - ctx := tests.Context(t) - // explicitly deploy the contracts - cs, err := keystone.DeployContracts(lggr, &env, sepoliaChainSel) - require.NoError(t, err) - env.ExistingAddresses = cs.AddressBook - deployReq := keystone.ConfigureContractsRequest{ - RegistryChainSel: sepoliaChainSel, - Env: &env, - OCR3Config: &ocr3Config, - Dons: []keystone.DonCapabilities{wfDon, cwDon, assetDon}, - DoContractDeploy: false, - } - deployResp, err := keystone.ConfigureContracts(ctx, lggr, deployReq) - require.NoError(t, err) - ad := deployResp.Changeset.AddressBook - addrs, err := ad.Addresses() - require.NoError(t, err) - lggr.Infow("Deployed Keystone contracts", "address book", addrs) - - // all contracts on home chain - homeChainAddrs, err := ad.AddressesForChain(sepoliaChainSel) - require.NoError(t, err) - require.Len(t, homeChainAddrs, 3) - // only forwarder on non-home chain - for sel := range env.Chains { - chainAddrs, err := ad.AddressesForChain(sel) - require.NoError(t, err) - if sel != sepoliaChainSel { - require.Len(t, chainAddrs, 1) - } else { - require.Len(t, chainAddrs, 3) - } - containsForwarder := false - for _, tv := range chainAddrs { - if tv.Type == keystone.KeystoneForwarder { - containsForwarder = true - break - } - } - require.True(t, containsForwarder, "no forwarder found in %v on chain %d for target don", chainAddrs, sel) - } - req := &keystone.GetContractSetsRequest{ - Chains: env.Chains, - AddressBook: ad, - } - - contractSetsResp, err := keystone.GetContractSets(lggr, req) - require.NoError(t, err) - require.Len(t, contractSetsResp.ContractSets, len(env.Chains)) - // check the registry - regChainContracts, ok := contractSetsResp.ContractSets[sepoliaChainSel] - require.True(t, ok) - gotRegistry := regChainContracts.CapabilitiesRegistry - require.NotNil(t, gotRegistry) - // contract reads - gotDons, err := gotRegistry.GetDONs(&bind.CallOpts{}) - if err != nil { - err = keystone.DecodeErr(kcr.CapabilitiesRegistryABI, err) - require.Fail(t, fmt.Sprintf("failed to get Dons from registry at %s: %s", gotRegistry.Address().String(), err)) - } - require.NoError(t, err) - assert.Len(t, gotDons, len(deployReq.Dons)) - - for n, info := range deployResp.DonInfos { - found := false - for _, gdon := range gotDons { - if gdon.Id == info.Id { - found = true - assert.EqualValues(t, info, gdon) - break - } - } - require.True(t, found, "don %s not found in registry", n) - } - // check the forwarder - for _, cs := range contractSetsResp.ContractSets { - forwarder := cs.Forwarder - require.NotNil(t, forwarder) - // any read to ensure that the contract is deployed correctly - _, err := forwarder.Owner(&bind.CallOpts{}) - require.NoError(t, err) - // TODO expand this test; there is no get method on the forwarder so unclear how to test it - } - // check the ocr3 contract - for chainSel, cs := range contractSetsResp.ContractSets { - if chainSel != sepoliaChainSel { - require.Nil(t, cs.OCR3) - continue - } - require.NotNil(t, cs.OCR3) - // any read to ensure that the contract is deployed correctly - _, err := cs.OCR3.LatestConfigDetails(&bind.CallOpts{}) - require.NoError(t, err) - } -} - -// TODO: Deprecated, remove everything below that leverages CLO - -func nodeOperatorsToIDs(nops []*models.NodeOperator) (nodeIDs []keystone.NOP) { - for _, nop := range nops { - nodeOperator := keystone.NOP{ - Name: nop.Name, - } - for _, node := range nop.Nodes { - nodeOperator.Nodes = append(nodeOperator.Nodes, node.ID) - } - nodeIDs = append(nodeIDs, nodeOperator) - } - return nodeIDs -} - -func TestDeployCLO(t *testing.T) { - lggr := logger.TestLogger(t) - wfNops := loadTestNops(t, "testdata/workflow_nodes.json") cwNops := loadTestNops(t, "testdata/chain_writer_nodes.json") assetNops := loadTestNops(t, "testdata/asset_nodes.json") @@ -234,65 +35,23 @@ func TestDeployCLO(t *testing.T) { require.Len(t, assetNops, 16) requireChains(t, assetNops, []models.ChainType{models.ChainTypeEvm}) - wfNodes := nodeOperatorsToIDs(wfNops) - cwNodes := nodeOperatorsToIDs(cwNops) - assetNodes := nodeOperatorsToIDs(assetNops) - wfDon := keystone.DonCapabilities{ Name: keystone.WFDonName, - Nops: wfNodes, + Nops: wfNops, Capabilities: []kcr.CapabilitiesRegistryCapability{keystone.OCR3Cap}, } cwDon := keystone.DonCapabilities{ Name: keystone.TargetDonName, - Nops: cwNodes, + Nops: cwNops, Capabilities: []kcr.CapabilitiesRegistryCapability{keystone.WriteChainCap}, } assetDon := keystone.DonCapabilities{ Name: keystone.StreamDonName, - Nops: assetNodes, + Nops: assetNops, Capabilities: []kcr.CapabilitiesRegistryCapability{keystone.StreamTriggerCap}, } - var allNops []*models.NodeOperator - allNops = append(allNops, wfNops...) - allNops = append(allNops, cwNops...) - allNops = append(allNops, assetNops...) - - chains := make(map[uint64]struct{}) - for _, nop := range allNops { - for _, node := range nop.Nodes { - for _, chain := range node.ChainConfigs { - // chain selector lib doesn't support chain id 2 and we don't use it in tests - // because it's not an evm chain - if chain.Network.ChainID == "2" { // aptos 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{}{} - } - } - } - var chainIDs []uint64 - for c := range chains { - chainIDs = append(chainIDs, c) - } - allChains := memory.NewMemoryChainsWithChainIDs(t, chainIDs) - - env := &deployment.Environment{ - Name: "CLO", - ExistingAddresses: deployment.NewMemoryAddressBook(), - Offchain: clo.NewJobClient(lggr, allNops), - Chains: allChains, - Logger: lggr, - } - // assume that all the nodes in the provided input nops are part of the don - for _, nop := range allNops { - for _, node := range nop.Nodes { - env.NodeIDs = append(env.NodeIDs, node.ID) - } - } + env := makeMultiDonTestEnv(t, lggr, []keystone.DonCapabilities{wfDon, cwDon, assetDon}) // sepolia; all nodes are on the this chain registryChainSel, err := chainsel.SelectorFromChainId(11155111) @@ -419,6 +178,25 @@ func requireChains(t *testing.T, donNops []*models.NodeOperator, cs []models.Cha } } +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) + return menv.Flatten("testing-env") +} + func loadTestNops(t *testing.T, pth string) []*models.NodeOperator { f, err := os.ReadFile(pth) require.NoError(t, err) diff --git a/deployment/keystone/types.go b/deployment/keystone/types.go index dd93c1889ec..18967ccf445 100644 --- a/deployment/keystone/types.go +++ b/deployment/keystone/types.go @@ -13,6 +13,7 @@ import ( chainsel "github.com/smartcontractkit/chain-selectors" "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/environment/clo/models" v1 "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/node" @@ -99,7 +100,8 @@ func (o *ocr2Node) toNodeKeys() NodeKeys { AptosOnchainPublicKey: aptosOnchainPublicKey, } } -func newOcr2NodeFromClo(n *Node, registryChainSel uint64) (*ocr2Node, error) { + +func newOcr2NodeFromClo(n *models.Node, registryChainSel uint64) (*ocr2Node, error) { if n.PublicKey == nil { return nil, errors.New("no public key") } @@ -108,21 +110,21 @@ func newOcr2NodeFromClo(n *Node, registryChainSel uint64) (*ocr2Node, error) { 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) + evmCC, err := registryChainConfig(n.ChainConfigs, chaintype.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) + aptosCC, exists := firstChainConfigByType(n.ChainConfigs, chaintype.Aptos) if exists { cfgs[chaintype.Aptos] = aptosCC } - return newOcr2Node(n.P2PID, cfgs, *n.PublicKey) + return newOcr2Node(n.ID, cfgs, *n.PublicKey) } -func ExtractKeys(n *Node, registerChainSel uint64) (p2p p2pkey.PeerID, signer [32]byte, encPubKey [32]byte, err error) { +func ExtractKeys(n *models.Node, registerChainSel uint64) (p2p p2pkey.PeerID, signer [32]byte, encPubKey [32]byte, err error) { orc2n, err := newOcr2NodeFromClo(n, registerChainSel) if err != nil { return p2p, signer, encPubKey, fmt.Errorf("failed to create ocr2 node for node %s: %w", n.ID, err) @@ -199,30 +201,26 @@ func makeNodeKeysSlice(nodes []*ocr2Node) []NodeKeys { return out } -type NOP struct { - Name string - Nodes []string // peerID -} - // DonCapabilities is a set of capabilities hosted by a set of node operators // in is in a convenient form to handle the CLO representation of the nop data type DonCapabilities struct { Name string - Nops []NOP + Nops []*models.NodeOperator // each nop is a node operator and may have multiple nodes Capabilities []kcr.CapabilitiesRegistryCapability // every capability is hosted on each nop } // map the node id to the NOP -func (dc DonInfo) nodeIdToNop(cs uint64) (map[string]capabilities_registry.CapabilitiesRegistryNodeOperator, error) { +func (dc DonCapabilities) nodeIdToNop(cs uint64) (map[string]capabilities_registry.CapabilitiesRegistryNodeOperator, error) { out := make(map[string]capabilities_registry.CapabilitiesRegistryNodeOperator) - for _, node := range dc.Nodes { - a, err := AdminAddress(&node, cs) - if err != nil { - return nil, fmt.Errorf("failed to get admin address for node %s: %w", node.ID, err) - } - // TODO: this never mapped to nop name, but don name - out[node.ID] = NodeOperator(dc.Name, a) + for _, nop := range dc.Nops { + for _, node := range nop.Nodes { + a, err := AdminAddress(node, cs) + if err != nil { + return nil, fmt.Errorf("failed to get admin address for node %s: %w", node.ID, err) + } + out[node.ID] = NodeOperator(dc.Name, a) + } } return out, nil } @@ -234,15 +232,14 @@ func NodeOperator(name string, adminAddress string) capabilities_registry.Capabi } } -func AdminAddress(n *Node, chainSel uint64) (string, error) { +func AdminAddress(n *models.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 { + if chain.Network.ChainID == cidStr { return chain.AdminAddress, nil } } @@ -251,7 +248,7 @@ func AdminAddress(n *Node, chainSel uint64) (string, error) { // helpers to maintain compatibility with the existing registration functions // nodesToNops converts a list of DonCapabilities to a map of node id to NOP -func nodesToNops(dons []DonInfo, chainSel uint64) (map[string]capabilities_registry.CapabilitiesRegistryNodeOperator, error) { +func nodesToNops(dons []DonCapabilities, chainSel uint64) (map[string]capabilities_registry.CapabilitiesRegistryNodeOperator, error) { out := make(map[string]capabilities_registry.CapabilitiesRegistryNodeOperator) for _, don := range dons { nops, err := don.nodeIdToNop(chainSel) @@ -270,7 +267,7 @@ func nodesToNops(dons []DonInfo, chainSel uint64) (map[string]capabilities_regis } // mapDonsToCaps converts a list of DonCapabilities to a map of don name to capabilities -func mapDonsToCaps(dons []DonInfo) map[string][]kcr.CapabilitiesRegistryCapability { +func mapDonsToCaps(dons []DonCapabilities) map[string][]kcr.CapabilitiesRegistryCapability { out := make(map[string][]kcr.CapabilitiesRegistryCapability) for _, don := range dons { out[don.Name] = don.Capabilities @@ -280,48 +277,53 @@ 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) { +func mapDonsToNodes(dons []DonCapabilities, excludeBootstraps bool, registryChainSel uint64) (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 { - for _, node := range don.Nodes { - ocr2n, err := newOcr2NodeFromClo(&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 { - continue - } - if _, ok := donToOcr2Nodes[don.Name]; !ok { - donToOcr2Nodes[don.Name] = make([]*ocr2Node, 0) + for _, nop := range don.Nops { + for _, node := range nop.Nodes { + ocr2n, err := newOcr2NodeFromClo(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 { + continue + } + if _, ok := donToOcr2Nodes[don.Name]; !ok { + donToOcr2Nodes[don.Name] = make([]*ocr2Node, 0) + } + donToOcr2Nodes[don.Name] = append(donToOcr2Nodes[don.Name], ocr2n) + } - donToOcr2Nodes[don.Name] = append(donToOcr2Nodes[don.Name], ocr2n) } } return donToOcr2Nodes, nil } -func firstChainConfigByType(ccfgs []*v1.ChainConfig, t v1.ChainType) (*v1.ChainConfig, bool) { +func firstChainConfigByType(ccfgs []*models.NodeChainConfig, t chaintype.ChainType) (*v1.ChainConfig, bool) { for _, c := range ccfgs { - if c.Chain.Type == t { - return c, true + //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 registryChainConfig(ccfgs []*v1.ChainConfig, t v1.ChainType, sel uint64) (*v1.ChainConfig, error) { +func registryChainConfig(ccfgs []*models.NodeChainConfig, t chaintype.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 + //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)) && c.Network.ChainID == chainIdStr { + return chainConfigFromClo(c), nil } } return nil, fmt.Errorf("no chain config for chain %d", chainId) @@ -348,7 +350,7 @@ func (d RegisteredDon) signers() []common.Address { return out } -func joinInfoAndNodes(donInfos map[string]kcr.CapabilitiesRegistryDONInfo, dons []DonInfo, registryChainSel uint64) ([]RegisteredDon, error) { +func joinInfoAndNodes(donInfos map[string]kcr.CapabilitiesRegistryDONInfo, dons []DonCapabilities, registryChainSel uint64) ([]RegisteredDon, error) { // all maps should have the same keys nodes, err := mapDonsToNodes(dons, true, registryChainSel) if err != nil { @@ -374,6 +376,31 @@ func joinInfoAndNodes(donInfos map[string]kcr.CapabilitiesRegistryDONInfo, dons return out, nil } +func chainConfigFromClo(chain *models.NodeChainConfig) *v1.ChainConfig { + return &v1.ChainConfig{ + Chain: &v1.Chain{ + Id: chain.Network.ChainID, + Type: v1.ChainType_CHAIN_TYPE_EVM, // TODO: support other chain types + }, + + AccountAddress: chain.AccountAddress, + AdminAddress: chain.AdminAddress, + Ocr2Config: &v1.OCR2Config{ + Enabled: chain.Ocr2Config.Enabled, + P2PKeyBundle: &v1.OCR2Config_P2PKeyBundle{ + PeerId: chain.Ocr2Config.P2pKeyBundle.PeerID, + PublicKey: chain.Ocr2Config.P2pKeyBundle.PublicKey, + }, + OcrKeyBundle: &v1.OCR2Config_OCRKeyBundle{ + BundleId: chain.Ocr2Config.OcrKeyBundle.BundleID, + OnchainSigningAddress: chain.Ocr2Config.OcrKeyBundle.OnchainSigningAddress, + OffchainPublicKey: chain.Ocr2Config.OcrKeyBundle.OffchainPublicKey, + ConfigPublicKey: chain.Ocr2Config.OcrKeyBundle.ConfigPublicKey, + }, + }, + } +} + var emptyAddr = "0x0000000000000000000000000000000000000000" // compute the admin address from the string. If the address is empty, replaces the 0s with fs diff --git a/deployment/keystone/types_test.go b/deployment/keystone/types_test.go index 925649bba0d..69b2e39a8f1 100644 --- a/deployment/keystone/types_test.go +++ b/deployment/keystone/types_test.go @@ -1,11 +1,19 @@ package keystone import ( + "encoding/json" + "os" + "strconv" "testing" "github.com/stretchr/testify/assert" + "github.com/test-go/testify/require" + + chainsel "github.com/smartcontractkit/chain-selectors" v1 "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/node" + "github.com/smartcontractkit/chainlink/deployment/environment/clo/models" + kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/chaintype" ) @@ -132,271 +140,271 @@ func Test_newOcr2Node(t *testing.T) { } } -// 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) +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") -// } + 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 -// } +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 +} diff --git a/shell.nix b/shell.nix index 8d5b4351b25..e3b187dcd96 100644 --- a/shell.nix +++ b/shell.nix @@ -1,7 +1,7 @@ {pkgs, isCrib}: with pkgs; let go = go_1_21; - postgresql = postgresql_15; + postgresql = postgresql_14; nodejs = nodejs-18_x; nodePackages = pkgs.nodePackages.override {inherit nodejs;}; pnpm = pnpm_9;