From 1e53cdc9b5ca1d4bebf64ac76635b2e85c0e5694 Mon Sep 17 00:00:00 2001 From: Ross Chadwick Date: Thu, 12 Sep 2019 12:54:42 +0200 Subject: [PATCH 1/3] p2p/simulations/adapters: Add bootnode & lightnode flags to NodeConfig, to allow for configuration of their respective services during simulations --- p2p/simulations/adapters/types.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/p2p/simulations/adapters/types.go b/p2p/simulations/adapters/types.go index f65ce7b6050f..3aabe2c1bdb0 100644 --- a/p2p/simulations/adapters/types.go +++ b/p2p/simulations/adapters/types.go @@ -89,6 +89,12 @@ type NodeConfig struct { // Enable peer events for Msgs EnableMsgEvents bool + // Node will run services as a bootnode + BootNode bool + + // Node will run services as a lightnode + LightNode bool + // Name is a human friendly name for the node like "node01" Name string @@ -121,6 +127,8 @@ type nodeConfigJSON struct { Name string `json:"name"` Services []string `json:"services"` EnableMsgEvents bool `json:"enable_msg_events"` + BootNode bool `json:"bootnode"` + LightNode bool `json:"lightnode"` Port uint16 `json:"port"` } @@ -133,6 +141,8 @@ func (n *NodeConfig) MarshalJSON() ([]byte, error) { Services: n.Services, Port: n.Port, EnableMsgEvents: n.EnableMsgEvents, + BootNode: n.BootNode, + LightNode: n.LightNode, } if n.PrivateKey != nil { confJSON.PrivateKey = hex.EncodeToString(crypto.FromECDSA(n.PrivateKey)) @@ -170,6 +180,8 @@ func (n *NodeConfig) UnmarshalJSON(data []byte) error { n.Services = confJSON.Services n.Port = confJSON.Port n.EnableMsgEvents = confJSON.EnableMsgEvents + n.BootNode = confJSON.BootNode + n.LightNode = confJSON.LightNode return nil } From cf8d752c419061fddc2ac2e24242fb65a50092c5 Mon Sep 17 00:00:00 2001 From: Ross Chadwick Date: Mon, 16 Sep 2019 20:08:43 +0200 Subject: [PATCH 2/3] p2p/simulations: Add simulation network functionality for bootnodes & lightnodes --- p2p/simulations/network.go | 175 ++++++++++++++++++++++++++++-- p2p/simulations/network_test.go | 186 ++++++++++++++++++++++++++++++++ 2 files changed, 354 insertions(+), 7 deletions(-) diff --git a/p2p/simulations/network.go b/p2p/simulations/network.go index f03c953e8953..3118f59a3608 100644 --- a/p2p/simulations/network.go +++ b/p2p/simulations/network.go @@ -56,6 +56,10 @@ type Network struct { Nodes []*Node `json:"nodes"` nodeMap map[enode.ID]int + // Node subtypes are also mapped separately, so they can be distinguished quickly + bootNodeMap map[enode.ID]int + lightNodeMap map[enode.ID]int + Conns []*Conn `json:"conns"` connMap map[string]int @@ -71,6 +75,8 @@ func NewNetwork(nodeAdapter adapters.NodeAdapter, conf *NetworkConfig) *Network NetworkConfig: *conf, nodeAdapter: nodeAdapter, nodeMap: make(map[enode.ID]int), + bootNodeMap: make(map[enode.ID]int), + lightNodeMap: make(map[enode.ID]int), connMap: make(map[string]int), quitc: make(chan struct{}), } @@ -120,7 +126,15 @@ func (net *Network) NewNodeWithConfig(conf *adapters.NodeConfig) (*Node, error) Config: conf, } log.Trace("Node created", "id", conf.ID) - net.nodeMap[conf.ID] = len(net.Nodes) + + nodeIndex := len(net.Nodes) + if conf.BootNode { + net.bootNodeMap[conf.ID] = nodeIndex + } else if conf.LightNode { + net.lightNodeMap[conf.ID] = nodeIndex + } + + net.nodeMap[conf.ID] = nodeIndex net.Nodes = append(net.Nodes, node) // emit a "control" event @@ -427,19 +441,164 @@ func (net *Network) getNodeByName(name string) *Node { return nil } -// GetNodes returns the existing nodes -func (net *Network) GetNodes() (nodes []*Node) { +// GetNodes returns the existing nodes. +// Nodes can optionally be excluded by specifying their enode.ID. +func (net *Network) GetNodes(excludeIDs ...enode.ID) []*Node { + net.lock.RLock() + defer net.lock.RUnlock() + + return net.getNodes(excludeIDs) +} + +func (net *Network) getNodes(excludeIDs []enode.ID) []*Node { + if len(excludeIDs) > 0 { + // Get all curent nodeIDs + nodeIDs := make([]enode.ID, 0, len(net.nodeMap)) + for id := range net.nodeMap { + nodeIDs = append(nodeIDs, id) + } + + // Return the difference of nodeIDs and excludeIDs + filteredIDs := filterIDs(nodeIDs, excludeIDs) + return net.getNodesByID(filteredIDs) + } else { + return net.Nodes + } +} + +// GetNodesByID returns existing nodes with the given enode.IDs. +// If a node doesn't exist with a given enode.ID, it is ignored. +func (net *Network) GetNodesByID(nodeIDs []enode.ID) []*Node { net.lock.RLock() defer net.lock.RUnlock() - return net.getNodes() + return net.getNodesByID(nodeIDs) } -func (net *Network) getNodes() (nodes []*Node) { - nodes = append(nodes, net.Nodes...) +func (net *Network) getNodesByID(nodeIDs []enode.ID) []*Node { + nodes := make([]*Node, 0, len(nodeIDs)) + for _, id := range nodeIDs { + node := net.getNode(id) + if node != nil { + nodes = append(nodes, node) + } + } + return nodes } +// GetBootNodes returns all configured bootnodes in the network. +func (net *Network) GetBootNodes() []*Node { + net.lock.RLock() + defer net.lock.RUnlock() + + return net.getBootNodes() +} + +func (net *Network) getBootNodes() []*Node { + bootNodes := make([]*Node, 0, len(net.bootNodeMap)) + for _, i := range net.bootNodeMap { + bootNodes = append(bootNodes, net.Nodes[i]) + } + + return bootNodes +} + +// GetBootNodeIDs returns a slice of all bootnode enode.ID +func (net *Network) GetBootNodeIDs() []enode.ID { + net.lock.RLock() + defer net.lock.RUnlock() + + return net.getBootNodeIDs() +} + +func (net *Network) getBootNodeIDs() []enode.ID { + bootNodeIDs := make([]enode.ID, 0, len(net.bootNodeMap)) + for id := range net.bootNodeMap { + bootNodeIDs = append(bootNodeIDs, id) + } + + return bootNodeIDs +} + +// GetLightNodes returns all configured light nodes in the network. +func (net *Network) GetLightNodes() []*Node { + net.lock.RLock() + defer net.lock.RUnlock() + + return net.getLightNodes() +} + +func (net *Network) getLightNodes() []*Node { + lightNodes := make([]*Node, 0, len(net.lightNodeMap)) + for _, i := range net.lightNodeMap { + lightNodes = append(lightNodes, net.Nodes[i]) + } + + return lightNodes +} + +// GetLightNodeIDs returns a slice of all light node enode.ID +func (net *Network) GetLightNodeIDs() []enode.ID { + net.lock.RLock() + defer net.lock.RUnlock() + + return net.getLightNodeIDs() +} + +func (net *Network) getLightNodeIDs() []enode.ID { + lightNodeIDs := make([]enode.ID, 0, len(net.lightNodeMap)) + for id := range net.lightNodeMap { + lightNodeIDs = append(lightNodeIDs, id) + } + + return lightNodeIDs +} + +// GetFullNodes returns all configured full nodes in the network. +// This excludes bootnodes and lightnodes. +func (net *Network) GetFullNodes() []*Node { + net.lock.RLock() + defer net.lock.RUnlock() + + return net.getFullNodes() +} + +// Collect the enode.IDs of all nodes types that are not full nodes and provide them to getNodes for exclusion +func (net *Network) getFullNodes() []*Node { + excludeNodeCount := len(net.lightNodeMap) + len(net.bootNodeMap) + excludeIDs := make([]enode.ID, 0, excludeNodeCount) + for ID := range net.lightNodeMap { + excludeIDs = append(excludeIDs, ID) + } + + for ID := range net.bootNodeMap { + excludeIDs = append(excludeIDs, ID) + } + + return net.getNodes(excludeIDs) +} + +// GetFullNodeIDs returns a slice of all full node enode.ID +func (net *Network) GetFullNodeIDs() []enode.ID { + net.lock.RLock() + defer net.lock.RUnlock() + + return net.getFullNodeIDs() +} + +func (net *Network) getFullNodeIDs() []enode.ID { + // The number of full nodes is the total number minus all sub mapping counts + fullNodeCount := len(net.nodeMap) - len(net.lightNodeMap) - len(net.bootNodeMap) + fullNodeIDs := make([]enode.ID, 0, fullNodeCount) + + for _, node := range net.getFullNodes() { + fullNodeIDs = append(fullNodeIDs, node.ID()) + } + + return fullNodeIDs +} + // GetRandomUpNode returns a random node on the network, which is running. func (net *Network) GetRandomUpNode(excludeIDs ...enode.ID) *Node { net.lock.RLock() @@ -469,7 +628,7 @@ func (net *Network) GetRandomDownNode(excludeIDs ...enode.ID) *Node { } func (net *Network) getDownNodeIDs() (ids []enode.ID) { - for _, node := range net.getNodes() { + for _, node := range net.Nodes { if !node.Up() { ids = append(ids, node.ID()) } @@ -616,6 +775,8 @@ func (net *Network) Reset() { //re-initialize the maps net.connMap = make(map[string]int) net.nodeMap = make(map[enode.ID]int) + net.bootNodeMap = make(map[enode.ID]int) + net.lightNodeMap = make(map[enode.ID]int) net.Nodes = nil net.Conns = nil diff --git a/p2p/simulations/network_test.go b/p2p/simulations/network_test.go index 01cd1000de47..b4ee356698f5 100644 --- a/p2p/simulations/network_test.go +++ b/p2p/simulations/network_test.go @@ -17,6 +17,7 @@ package simulations import ( + "bytes" "context" "encoding/json" "fmt" @@ -393,6 +394,191 @@ func TestNetworkSimulation(t *testing.T) { } } +// TestMultiNodeRetrieval creates a multi-node simulation network. +// Full nodes, bootnodes and lightnodes are created. +// Functions for retrieving specific subgroups of nodes are then tested for correctness. +func TestMultiNodeRetrieval(t *testing.T) { + adapter := adapters.NewSimAdapter(adapters.Services{ + "test": newTestService, + }) + network := NewNetwork(adapter, &NetworkConfig{ + DefaultService: "test", + }) + defer network.Shutdown() + + // Create a bootnode + bootNodeConf := adapters.RandomNodeConfig() + bootNodeConf.BootNode = true + bootNode, err := network.NewNodeWithConfig(bootNodeConf) + if err != nil { + t.Fatalf("error creating bootnode: %s", err) + } + if err := network.Start(bootNode.ID()); err != nil { + t.Fatalf("error starting bootnode: %s", err) + } + + // Create 20 light nodes + lightNodeCount := 20 + lightNodes := make(map[enode.ID]*Node, lightNodeCount) + for i := 0; i < lightNodeCount; i++ { + conf := adapters.RandomNodeConfig() + conf.LightNode = true + node, err := network.NewNodeWithConfig(conf) + if err != nil { + t.Fatalf("error creating light node: %s", err) + } + if err := network.Start(node.ID()); err != nil { + t.Fatalf("error starting light node: %s", err) + } + lightNodes[node.ID()] = node + } + + // Create 20 full nodes + fullNodeCount := 20 + fullNodes := make(map[enode.ID]*Node, fullNodeCount) + for i := 0; i < fullNodeCount; i++ { + conf := adapters.RandomNodeConfig() + node, err := network.NewNodeWithConfig(conf) + if err != nil { + t.Fatalf("error creating node: %s", err) + } + if err := network.Start(node.ID()); err != nil { + t.Fatalf("error starting node: %s", err) + } + fullNodes[node.ID()] = node + } + + // Check that network.GetBootNodes returns the boot node we created and only that bootnode + bootNodes := network.GetBootNodes() + if len(bootNodes) == 0 { + t.Fatal("GetBootNodes returned empty when size of one was expected") + } + + for _, bn := range bootNodes { + if !bytes.Equal(bn.ID().Bytes(), bootNode.ID().Bytes()) { + t.Fatalf("Found an unexpected node in GetBootNodes: %s", bn.String()) + } + } + + // Check that the boot node's ID is the only one returned by GetBoodNodeIDs() + // If a non-matching ID is found, the test fails + bootNodeIDs := network.GetBootNodeIDs() + if len(bootNodeIDs) == 0 { + t.Fatal("GetBootNodeIDs returned empty when one ID was expected") + } + + for _, id := range bootNodeIDs { + if !bytes.Equal(id.Bytes(), bootNode.ID().Bytes()) { + t.Fatalf("Found an unexpected enode.ID in GetBootNodeIDs: %s", id.String()) + } + } + + // Check that each of lightNodes (the light nodes we just created) are available from the GetLightNodes method. + // If a light node isn't found in GetLightNodes, the test fails. + for _, ln1 := range lightNodes { + match := false + lightNode1IDBytes := ln1.ID().Bytes() + + for _, ln2 := range network.GetLightNodes() { + lightNode2IDBytes := ln2.ID().Bytes() + if bytes.Equal(lightNode1IDBytes, lightNode2IDBytes) { + match = true + break + } + } + + if !match { + t.Fatalf("A created light node was not returned by GetLightNodes(), ID: %s", ln1.ID().String()) + } + } + + // Check that the IDs of each of fullNodes are returned by GetFullNodeIDs() + // If a full not isn't found in GetFullNodeIDs(), the test fails + lightNodeIDs := network.GetLightNodeIDs() + for id1 := range lightNodes { + match := false + for _, id2 := range lightNodeIDs { + if bytes.Equal(id1.Bytes(), id2.Bytes()) { + match = true + break + } + } + + if !match { + t.Fatalf("Not all light nodes were returned by GetLightNodeIDs(), ID: %s", id1.String()) + } + } + + // Check that each of fullNodes (the full nodes we just created) are available from the GetFullNodes method. + // If a full node isn't found in GetFullNodes, the test fails. + for _, fn1 := range fullNodes { + match := false + fullNode1IDBytes := fn1.ID().Bytes() + + for _, fn2 := range network.GetFullNodes() { + fullNode2IDBytes := fn2.ID().Bytes() + if bytes.Equal(fullNode1IDBytes, fullNode2IDBytes) { + match = true + break + } + } + + if !match { + t.Fatalf("A created full node was not returned by GetFullNodes(), ID: %s", fn1.ID().String()) + } + } + + // Check that the IDs of each of fullNodes are returned by GetFullNodeIDs() + // If a full not isn't found in GetFullNodeIDs(), the test fails + fullNodeIDs := network.GetFullNodeIDs() + for id1 := range fullNodes { + match := false + for _, id2 := range fullNodeIDs { + if bytes.Equal(id1.Bytes(), id2.Bytes()) { + match = true + break + } + } + + if !match { + t.Fatalf("Not all full nodes were returned by GetFullNodeIDs(), ID: %s", id1.String()) + } + } + + // Get all nodes, excluding the bootnode by passing the bootnode ID. + // Checks that the bootnode is excluded as expected and fails the test if not. + nodesExclBootNode := network.GetNodes(bootNode.ID()) + for _, node := range nodesExclBootNode { + if bytes.Equal(node.ID().Bytes(), bootNode.ID().Bytes()) { + t.Fatalf("Bootnode still found in GetNodes when it has been explicitly excluded.") + } + } + + // Get all node IDs and call GetNodesByID using them. + // The test then confirms that the nodes returned from GetNodes() match those returned from GetNodesByID(allIDs) + var nodeIDs []enode.ID + for _, node := range network.GetNodes() { + nodeIDs = append(nodeIDs, node.ID()) + } + + nodesByID := network.GetNodesByID(nodeIDs) + for _, node1 := range network.GetNodes() { + match := false + node1IDBytes := node1.ID().Bytes() + for _, node2 := range nodesByID { + node2IDBytes := node2.ID().Bytes() + if bytes.Equal(node1IDBytes, node2IDBytes) { + match = true + break + } + } + + if !match { + t.Fatalf("A node was found in GetNodes() that was not returned by GetNodesByID() for all node IDs") + } + } +} + func triggerChecks(ctx context.Context, ids []enode.ID, trigger chan enode.ID, interval time.Duration) { tick := time.NewTicker(interval) defer tick.Stop() From d7bc2a8ad1d9c7108ba6a5431fde2e195cd49740 Mon Sep 17 00:00:00 2001 From: Ross Chadwick Date: Tue, 15 Oct 2019 14:08:02 +0200 Subject: [PATCH 3/3] p2p/simulations: Add node property (e.g bootnode, etc) support to simulation networks + Expanded node getter utility functions --- p2p/simulations/adapters/types.go | 20 +- p2p/simulations/network.go | 179 ++++++----------- p2p/simulations/network_test.go | 306 +++++++++++++++++++----------- 3 files changed, 265 insertions(+), 240 deletions(-) diff --git a/p2p/simulations/adapters/types.go b/p2p/simulations/adapters/types.go index 3aabe2c1bdb0..850de96a155e 100644 --- a/p2p/simulations/adapters/types.go +++ b/p2p/simulations/adapters/types.go @@ -89,12 +89,6 @@ type NodeConfig struct { // Enable peer events for Msgs EnableMsgEvents bool - // Node will run services as a bootnode - BootNode bool - - // Node will run services as a lightnode - LightNode bool - // Name is a human friendly name for the node like "node01" Name string @@ -107,6 +101,11 @@ type NodeConfig struct { // services registered by calling the RegisterService function) Services []string + // Properties are the names of the properties this node should hold + // within running services (e.g. "bootnode", "lightnode" or any custom values) + // These values need to be checked and acted upon by node Services + Properties []string + // Enode node *enode.Node @@ -126,9 +125,8 @@ type nodeConfigJSON struct { PrivateKey string `json:"private_key"` Name string `json:"name"` Services []string `json:"services"` + Properties []string `json:"properties"` EnableMsgEvents bool `json:"enable_msg_events"` - BootNode bool `json:"bootnode"` - LightNode bool `json:"lightnode"` Port uint16 `json:"port"` } @@ -139,10 +137,9 @@ func (n *NodeConfig) MarshalJSON() ([]byte, error) { ID: n.ID.String(), Name: n.Name, Services: n.Services, + Properties: n.Properties, Port: n.Port, EnableMsgEvents: n.EnableMsgEvents, - BootNode: n.BootNode, - LightNode: n.LightNode, } if n.PrivateKey != nil { confJSON.PrivateKey = hex.EncodeToString(crypto.FromECDSA(n.PrivateKey)) @@ -178,10 +175,9 @@ func (n *NodeConfig) UnmarshalJSON(data []byte) error { n.Name = confJSON.Name n.Services = confJSON.Services + n.Properties = confJSON.Properties n.Port = confJSON.Port n.EnableMsgEvents = confJSON.EnableMsgEvents - n.BootNode = confJSON.BootNode - n.LightNode = confJSON.LightNode return nil } diff --git a/p2p/simulations/network.go b/p2p/simulations/network.go index 3118f59a3608..58fd9a28b0fe 100644 --- a/p2p/simulations/network.go +++ b/p2p/simulations/network.go @@ -56,9 +56,8 @@ type Network struct { Nodes []*Node `json:"nodes"` nodeMap map[enode.ID]int - // Node subtypes are also mapped separately, so they can be distinguished quickly - bootNodeMap map[enode.ID]int - lightNodeMap map[enode.ID]int + // Maps a node property string to node indexes of all nodes that hold this property + propertyMap map[string][]int Conns []*Conn `json:"conns"` connMap map[string]int @@ -75,8 +74,7 @@ func NewNetwork(nodeAdapter adapters.NodeAdapter, conf *NetworkConfig) *Network NetworkConfig: *conf, nodeAdapter: nodeAdapter, nodeMap: make(map[enode.ID]int), - bootNodeMap: make(map[enode.ID]int), - lightNodeMap: make(map[enode.ID]int), + propertyMap: make(map[string][]int), connMap: make(map[string]int), quitc: make(chan struct{}), } @@ -128,15 +126,14 @@ func (net *Network) NewNodeWithConfig(conf *adapters.NodeConfig) (*Node, error) log.Trace("Node created", "id", conf.ID) nodeIndex := len(net.Nodes) - if conf.BootNode { - net.bootNodeMap[conf.ID] = nodeIndex - } else if conf.LightNode { - net.lightNodeMap[conf.ID] = nodeIndex - } - net.nodeMap[conf.ID] = nodeIndex net.Nodes = append(net.Nodes, node) + // Register any node properties with the network-level propertyMap + for _, property := range conf.Properties { + net.propertyMap[property] = append(net.propertyMap[property], nodeIndex) + } + // emit a "control" event net.events.Send(ControlEvent(node)) @@ -424,7 +421,7 @@ func (net *Network) getNode(id enode.ID) *Node { return net.Nodes[i] } -// GetNode gets the node with the given name, returning nil if the node does +// GetNodeByName gets the node with the given name, returning nil if the node does // not exist func (net *Network) GetNodeByName(name string) *Node { net.lock.RLock() @@ -441,6 +438,30 @@ func (net *Network) getNodeByName(name string) *Node { return nil } +// GetNodeIDs returns the IDs of all existing nodes +// Nodes can optionally be excluded by specifying their enode.ID. +func (net *Network) GetNodeIDs(excludeIDs ...enode.ID) []enode.ID { + net.lock.RLock() + defer net.lock.RUnlock() + + return net.getNodeIDs(excludeIDs) +} + +func (net *Network) getNodeIDs(excludeIDs []enode.ID) []enode.ID { + // Get all curent nodeIDs + nodeIDs := make([]enode.ID, 0, len(net.nodeMap)) + for id := range net.nodeMap { + nodeIDs = append(nodeIDs, id) + } + + if len(excludeIDs) > 0 { + // Return the difference of nodeIDs and excludeIDs + return filterIDs(nodeIDs, excludeIDs) + } else { + return nodeIDs + } +} + // GetNodes returns the existing nodes. // Nodes can optionally be excluded by specifying their enode.ID. func (net *Network) GetNodes(excludeIDs ...enode.ID) []*Node { @@ -452,15 +473,8 @@ func (net *Network) GetNodes(excludeIDs ...enode.ID) []*Node { func (net *Network) getNodes(excludeIDs []enode.ID) []*Node { if len(excludeIDs) > 0 { - // Get all curent nodeIDs - nodeIDs := make([]enode.ID, 0, len(net.nodeMap)) - for id := range net.nodeMap { - nodeIDs = append(nodeIDs, id) - } - - // Return the difference of nodeIDs and excludeIDs - filteredIDs := filterIDs(nodeIDs, excludeIDs) - return net.getNodesByID(filteredIDs) + nodeIDs := net.getNodeIDs(excludeIDs) + return net.getNodesByID(nodeIDs) } else { return net.Nodes } @@ -487,116 +501,39 @@ func (net *Network) getNodesByID(nodeIDs []enode.ID) []*Node { return nodes } -// GetBootNodes returns all configured bootnodes in the network. -func (net *Network) GetBootNodes() []*Node { - net.lock.RLock() - defer net.lock.RUnlock() - - return net.getBootNodes() -} - -func (net *Network) getBootNodes() []*Node { - bootNodes := make([]*Node, 0, len(net.bootNodeMap)) - for _, i := range net.bootNodeMap { - bootNodes = append(bootNodes, net.Nodes[i]) - } - - return bootNodes -} - -// GetBootNodeIDs returns a slice of all bootnode enode.ID -func (net *Network) GetBootNodeIDs() []enode.ID { - net.lock.RLock() - defer net.lock.RUnlock() - - return net.getBootNodeIDs() -} - -func (net *Network) getBootNodeIDs() []enode.ID { - bootNodeIDs := make([]enode.ID, 0, len(net.bootNodeMap)) - for id := range net.bootNodeMap { - bootNodeIDs = append(bootNodeIDs, id) - } - - return bootNodeIDs -} - -// GetLightNodes returns all configured light nodes in the network. -func (net *Network) GetLightNodes() []*Node { +// GetNodesByProperty returns existing nodes that have the given property string registered in their NodeConfig +func (net *Network) GetNodesByProperty(property string) []*Node { net.lock.RLock() defer net.lock.RUnlock() - return net.getLightNodes() + return net.getNodesByProperty(property) } -func (net *Network) getLightNodes() []*Node { - lightNodes := make([]*Node, 0, len(net.lightNodeMap)) - for _, i := range net.lightNodeMap { - lightNodes = append(lightNodes, net.Nodes[i]) +func (net *Network) getNodesByProperty(property string) []*Node { + nodes := make([]*Node, 0, len(net.propertyMap[property])) + for _, nodeIndex := range net.propertyMap[property] { + nodes = append(nodes, net.Nodes[nodeIndex]) } - return lightNodes -} - -// GetLightNodeIDs returns a slice of all light node enode.ID -func (net *Network) GetLightNodeIDs() []enode.ID { - net.lock.RLock() - defer net.lock.RUnlock() - - return net.getLightNodeIDs() -} - -func (net *Network) getLightNodeIDs() []enode.ID { - lightNodeIDs := make([]enode.ID, 0, len(net.lightNodeMap)) - for id := range net.lightNodeMap { - lightNodeIDs = append(lightNodeIDs, id) - } - - return lightNodeIDs -} - -// GetFullNodes returns all configured full nodes in the network. -// This excludes bootnodes and lightnodes. -func (net *Network) GetFullNodes() []*Node { - net.lock.RLock() - defer net.lock.RUnlock() - - return net.getFullNodes() -} - -// Collect the enode.IDs of all nodes types that are not full nodes and provide them to getNodes for exclusion -func (net *Network) getFullNodes() []*Node { - excludeNodeCount := len(net.lightNodeMap) + len(net.bootNodeMap) - excludeIDs := make([]enode.ID, 0, excludeNodeCount) - for ID := range net.lightNodeMap { - excludeIDs = append(excludeIDs, ID) - } - - for ID := range net.bootNodeMap { - excludeIDs = append(excludeIDs, ID) - } - - return net.getNodes(excludeIDs) + return nodes } -// GetFullNodeIDs returns a slice of all full node enode.ID -func (net *Network) GetFullNodeIDs() []enode.ID { +// GetNodeIDsByProperty returns existing node's enode IDs that have the given property string registered in the NodeConfig +func (net *Network) GetNodeIDsByProperty(property string) []enode.ID { net.lock.RLock() defer net.lock.RUnlock() - return net.getFullNodeIDs() + return net.getNodeIDsByProperty(property) } -func (net *Network) getFullNodeIDs() []enode.ID { - // The number of full nodes is the total number minus all sub mapping counts - fullNodeCount := len(net.nodeMap) - len(net.lightNodeMap) - len(net.bootNodeMap) - fullNodeIDs := make([]enode.ID, 0, fullNodeCount) - - for _, node := range net.getFullNodes() { - fullNodeIDs = append(fullNodeIDs, node.ID()) +func (net *Network) getNodeIDsByProperty(property string) []enode.ID { + nodeIDs := make([]enode.ID, 0, len(net.propertyMap[property])) + for _, nodeIndex := range net.propertyMap[property] { + node := net.Nodes[nodeIndex] + nodeIDs = append(nodeIDs, node.ID()) } - return fullNodeIDs + return nodeIDs } // GetRandomUpNode returns a random node on the network, which is running. @@ -636,6 +573,13 @@ func (net *Network) getDownNodeIDs() (ids []enode.ID) { return ids } +// GetRandomNode returns a random node on the network, regardless of whether it is running or not +func (net *Network) GetRandomNode(excludeIDs ...enode.ID) *Node { + net.lock.RLock() + defer net.lock.RUnlock() + return net.getRandomNode(net.getNodeIDs(nil), excludeIDs) // no need to exclude twice +} + func (net *Network) getRandomNode(ids []enode.ID, excludeIDs []enode.ID) *Node { filtered := filterIDs(ids, excludeIDs) @@ -775,8 +719,7 @@ func (net *Network) Reset() { //re-initialize the maps net.connMap = make(map[string]int) net.nodeMap = make(map[enode.ID]int) - net.bootNodeMap = make(map[enode.ID]int) - net.lightNodeMap = make(map[enode.ID]int) + net.propertyMap = make(map[string][]int) net.Nodes = nil net.Conns = nil @@ -795,12 +738,14 @@ type Node struct { upMu sync.RWMutex } +// Up returns whether the node is currently up (online) func (n *Node) Up() bool { n.upMu.RLock() defer n.upMu.RUnlock() return n.up } +// SetUp sets the up (online) status of the nodes with the given value func (n *Node) SetUp(up bool) { n.upMu.Lock() defer n.upMu.Unlock() diff --git a/p2p/simulations/network_test.go b/p2p/simulations/network_test.go index b4ee356698f5..f504b9a698b7 100644 --- a/p2p/simulations/network_test.go +++ b/p2p/simulations/network_test.go @@ -394,10 +394,46 @@ func TestNetworkSimulation(t *testing.T) { } } -// TestMultiNodeRetrieval creates a multi-node simulation network. -// Full nodes, bootnodes and lightnodes are created. -// Functions for retrieving specific subgroups of nodes are then tested for correctness. -func TestMultiNodeRetrieval(t *testing.T) { +func createTestNodes(count int, network *Network) (nodes []*Node, err error) { + for i := 0; i < count; i++ { + nodeConf := adapters.RandomNodeConfig() + node, err := network.NewNodeWithConfig(nodeConf) + if err != nil { + return nil, err + } + if err := network.Start(node.ID()); err != nil { + return nil, err + } + + nodes = append(nodes, node) + } + + return nodes, nil +} + +func createTestNodesWithProperty(property string, count int, network *Network) (propertyNodes []*Node, err error) { + for i := 0; i < count; i++ { + nodeConf := adapters.RandomNodeConfig() + nodeConf.Properties = append(nodeConf.Properties, property) + + node, err := network.NewNodeWithConfig(nodeConf) + if err != nil { + return nil, err + } + if err := network.Start(node.ID()); err != nil { + return nil, err + } + + propertyNodes = append(propertyNodes, node) + } + + return propertyNodes, nil +} + +// TestGetNodeIDs creates a set of nodes and attempts to retrieve their IDs,. +// It then tests again whilst excluding a node ID from being returned. +// If a node ID is not returned, or more node IDs than expected are returned, the test fails. +func TestGetNodeIDs(t *testing.T) { adapter := adapters.NewSimAdapter(adapters.Services{ "test": newTestService, }) @@ -406,175 +442,223 @@ func TestMultiNodeRetrieval(t *testing.T) { }) defer network.Shutdown() - // Create a bootnode - bootNodeConf := adapters.RandomNodeConfig() - bootNodeConf.BootNode = true - bootNode, err := network.NewNodeWithConfig(bootNodeConf) + numNodes := 5 + nodes, err := createTestNodes(numNodes, network) if err != nil { - t.Fatalf("error creating bootnode: %s", err) - } - if err := network.Start(bootNode.ID()); err != nil { - t.Fatalf("error starting bootnode: %s", err) + t.Fatalf("Could not creat test nodes %v", err) } - // Create 20 light nodes - lightNodeCount := 20 - lightNodes := make(map[enode.ID]*Node, lightNodeCount) - for i := 0; i < lightNodeCount; i++ { - conf := adapters.RandomNodeConfig() - conf.LightNode = true - node, err := network.NewNodeWithConfig(conf) - if err != nil { - t.Fatalf("error creating light node: %s", err) - } - if err := network.Start(node.ID()); err != nil { - t.Fatalf("error starting light node: %s", err) - } - lightNodes[node.ID()] = node + gotNodeIDs := network.GetNodeIDs() + if len(gotNodeIDs) != numNodes { + t.Fatalf("Expected %d nodes, got %d", numNodes, len(gotNodeIDs)) } - // Create 20 full nodes - fullNodeCount := 20 - fullNodes := make(map[enode.ID]*Node, fullNodeCount) - for i := 0; i < fullNodeCount; i++ { - conf := adapters.RandomNodeConfig() - node, err := network.NewNodeWithConfig(conf) - if err != nil { - t.Fatalf("error creating node: %s", err) + for _, node1 := range nodes { + match := false + for _, node2ID := range gotNodeIDs { + if bytes.Equal(node1.ID().Bytes(), node2ID.Bytes()) { + match = true + break + } } - if err := network.Start(node.ID()); err != nil { - t.Fatalf("error starting node: %s", err) + + if !match { + t.Fatalf("A created node was not returned by GetNodes(), ID: %s", node1.ID().String()) } - fullNodes[node.ID()] = node } - // Check that network.GetBootNodes returns the boot node we created and only that bootnode - bootNodes := network.GetBootNodes() - if len(bootNodes) == 0 { - t.Fatal("GetBootNodes returned empty when size of one was expected") + excludeNodeID := nodes[3].ID() + gotNodeIDsExcl := network.GetNodeIDs(excludeNodeID) + if len(gotNodeIDsExcl) != numNodes-1 { + t.Fatalf("Expected one less node ID to be returned") } - - for _, bn := range bootNodes { - if !bytes.Equal(bn.ID().Bytes(), bootNode.ID().Bytes()) { - t.Fatalf("Found an unexpected node in GetBootNodes: %s", bn.String()) + for _, nodeID := range gotNodeIDsExcl { + if bytes.Equal(excludeNodeID.Bytes(), nodeID.Bytes()) { + t.Fatalf("GetNodeIDs returned the node ID we excluded, ID: %s", nodeID.String()) } } +} - // Check that the boot node's ID is the only one returned by GetBoodNodeIDs() - // If a non-matching ID is found, the test fails - bootNodeIDs := network.GetBootNodeIDs() - if len(bootNodeIDs) == 0 { - t.Fatal("GetBootNodeIDs returned empty when one ID was expected") +// TestGetNodes creates a set of nodes and attempts to retrieve them again. +// It then tests again whilst excluding a node from being returned. +// If a node is not returned, or more nodes than expected are returned, the test fails. +func TestGetNodes(t *testing.T) { + adapter := adapters.NewSimAdapter(adapters.Services{ + "test": newTestService, + }) + network := NewNetwork(adapter, &NetworkConfig{ + DefaultService: "test", + }) + defer network.Shutdown() + + numNodes := 5 + nodes, err := createTestNodes(numNodes, network) + if err != nil { + t.Fatalf("Could not creat test nodes %v", err) } - for _, id := range bootNodeIDs { - if !bytes.Equal(id.Bytes(), bootNode.ID().Bytes()) { - t.Fatalf("Found an unexpected enode.ID in GetBootNodeIDs: %s", id.String()) - } + gotNodes := network.GetNodes() + if len(gotNodes) != numNodes { + t.Fatalf("Expected %d nodes, got %d", numNodes, len(gotNodes)) } - // Check that each of lightNodes (the light nodes we just created) are available from the GetLightNodes method. - // If a light node isn't found in GetLightNodes, the test fails. - for _, ln1 := range lightNodes { + for _, node1 := range nodes { match := false - lightNode1IDBytes := ln1.ID().Bytes() - - for _, ln2 := range network.GetLightNodes() { - lightNode2IDBytes := ln2.ID().Bytes() - if bytes.Equal(lightNode1IDBytes, lightNode2IDBytes) { + for _, node2 := range gotNodes { + if bytes.Equal(node1.ID().Bytes(), node2.ID().Bytes()) { match = true break } } if !match { - t.Fatalf("A created light node was not returned by GetLightNodes(), ID: %s", ln1.ID().String()) + t.Fatalf("A created node was not returned by GetNodes(), ID: %s", node1.ID().String()) } } - // Check that the IDs of each of fullNodes are returned by GetFullNodeIDs() - // If a full not isn't found in GetFullNodeIDs(), the test fails - lightNodeIDs := network.GetLightNodeIDs() - for id1 := range lightNodes { - match := false - for _, id2 := range lightNodeIDs { - if bytes.Equal(id1.Bytes(), id2.Bytes()) { - match = true - break - } + excludeNodeID := nodes[3].ID() + gotNodesExcl := network.GetNodes(excludeNodeID) + if len(gotNodesExcl) != numNodes-1 { + t.Fatalf("Expected one less node to be returned") + } + for _, node := range gotNodesExcl { + if bytes.Equal(excludeNodeID.Bytes(), node.ID().Bytes()) { + t.Fatalf("GetNodes returned the node we excluded, ID: %s", node.ID().String()) } + } +} - if !match { - t.Fatalf("Not all light nodes were returned by GetLightNodeIDs(), ID: %s", id1.String()) - } +// TestGetNodesByID creates a set of nodes and attempts to retrieve a subset of them by ID +// If a node is not returned, or more nodes than expected are returned, the test fails. +func TestGetNodesByID(t *testing.T) { + adapter := adapters.NewSimAdapter(adapters.Services{ + "test": newTestService, + }) + network := NewNetwork(adapter, &NetworkConfig{ + DefaultService: "test", + }) + defer network.Shutdown() + + numNodes := 5 + nodes, err := createTestNodes(numNodes, network) + if err != nil { + t.Fatalf("Could not create test nodes: %v", err) } - // Check that each of fullNodes (the full nodes we just created) are available from the GetFullNodes method. - // If a full node isn't found in GetFullNodes, the test fails. - for _, fn1 := range fullNodes { - match := false - fullNode1IDBytes := fn1.ID().Bytes() + numSubsetNodes := 2 + subsetNodes := nodes[0:numSubsetNodes] + var subsetNodeIDs []enode.ID + for _, node := range subsetNodes { + subsetNodeIDs = append(subsetNodeIDs, node.ID()) + } - for _, fn2 := range network.GetFullNodes() { - fullNode2IDBytes := fn2.ID().Bytes() - if bytes.Equal(fullNode1IDBytes, fullNode2IDBytes) { + gotNodesByID := network.GetNodesByID(subsetNodeIDs) + if len(gotNodesByID) != numSubsetNodes { + t.Fatalf("Expected %d nodes, got %d", numSubsetNodes, len(gotNodesByID)) + } + + for _, node1 := range subsetNodes { + match := false + for _, node2 := range gotNodesByID { + if bytes.Equal(node1.ID().Bytes(), node2.ID().Bytes()) { match = true break } } if !match { - t.Fatalf("A created full node was not returned by GetFullNodes(), ID: %s", fn1.ID().String()) + t.Fatalf("A created node was not returned by GetNodesByID(), ID: %s", node1.ID().String()) } } +} + +// TestGetNodesByProperty creates a subset of nodes with a property assigned. +// GetNodesByProperty is then checked for correctness by comparing the nodes returned to those initially created. +// If a node with a property is not found, or more nodes than expected are returned, the test fails. +func TestGetNodesByProperty(t *testing.T) { + adapter := adapters.NewSimAdapter(adapters.Services{ + "test": newTestService, + }) + network := NewNetwork(adapter, &NetworkConfig{ + DefaultService: "test", + }) + defer network.Shutdown() + + numNodes := 3 + _, err := createTestNodes(numNodes, network) + if err != nil { + t.Fatalf("Failed to create nodes: %v", err) + } + + numPropertyNodes := 3 + propertyTest := "test" + propertyNodes, err := createTestNodesWithProperty(propertyTest, numPropertyNodes, network) + if err != nil { + t.Fatalf("Failed to create nodes with property: %v", err) + } + + gotNodesByProperty := network.GetNodesByProperty(propertyTest) + if len(gotNodesByProperty) != numPropertyNodes { + t.Fatalf("Expected %d nodes with a property, got %d", numPropertyNodes, len(gotNodesByProperty)) + } - // Check that the IDs of each of fullNodes are returned by GetFullNodeIDs() - // If a full not isn't found in GetFullNodeIDs(), the test fails - fullNodeIDs := network.GetFullNodeIDs() - for id1 := range fullNodes { + for _, node1 := range propertyNodes { match := false - for _, id2 := range fullNodeIDs { - if bytes.Equal(id1.Bytes(), id2.Bytes()) { + for _, node2 := range gotNodesByProperty { + if bytes.Equal(node1.ID().Bytes(), node2.ID().Bytes()) { match = true break } } if !match { - t.Fatalf("Not all full nodes were returned by GetFullNodeIDs(), ID: %s", id1.String()) + t.Fatalf("A created node with property was not returned by GetNodesByProperty(), ID: %s", node1.ID().String()) } } +} - // Get all nodes, excluding the bootnode by passing the bootnode ID. - // Checks that the bootnode is excluded as expected and fails the test if not. - nodesExclBootNode := network.GetNodes(bootNode.ID()) - for _, node := range nodesExclBootNode { - if bytes.Equal(node.ID().Bytes(), bootNode.ID().Bytes()) { - t.Fatalf("Bootnode still found in GetNodes when it has been explicitly excluded.") - } +// TestGetNodeIDsByProperty creates a subset of nodes with a property assigned. +// GetNodeIDsByProperty is then checked for correctness by comparing the node IDs returned to those initially created. +// If a node ID with a property is not found, or more nodes IDs than expected are returned, the test fails. +func TestGetNodeIDsByProperty(t *testing.T) { + adapter := adapters.NewSimAdapter(adapters.Services{ + "test": newTestService, + }) + network := NewNetwork(adapter, &NetworkConfig{ + DefaultService: "test", + }) + defer network.Shutdown() + + numNodes := 3 + _, err := createTestNodes(numNodes, network) + if err != nil { + t.Fatalf("Failed to create nodes: %v", err) + } + + numPropertyNodes := 3 + propertyTest := "test" + propertyNodes, err := createTestNodesWithProperty(propertyTest, numPropertyNodes, network) + if err != nil { + t.Fatalf("Failed to created nodes with property: %v", err) } - // Get all node IDs and call GetNodesByID using them. - // The test then confirms that the nodes returned from GetNodes() match those returned from GetNodesByID(allIDs) - var nodeIDs []enode.ID - for _, node := range network.GetNodes() { - nodeIDs = append(nodeIDs, node.ID()) + gotNodeIDsByProperty := network.GetNodeIDsByProperty(propertyTest) + if len(gotNodeIDsByProperty) != numPropertyNodes { + t.Fatalf("Expected %d nodes with a property, got %d", numPropertyNodes, len(gotNodeIDsByProperty)) } - nodesByID := network.GetNodesByID(nodeIDs) - for _, node1 := range network.GetNodes() { + for _, node1 := range propertyNodes { match := false - node1IDBytes := node1.ID().Bytes() - for _, node2 := range nodesByID { - node2IDBytes := node2.ID().Bytes() - if bytes.Equal(node1IDBytes, node2IDBytes) { + id1 := node1.ID() + for _, id2 := range gotNodeIDsByProperty { + if bytes.Equal(id1.Bytes(), id2.Bytes()) { match = true break } } if !match { - t.Fatalf("A node was found in GetNodes() that was not returned by GetNodesByID() for all node IDs") + t.Fatalf("Not all nodes IDs were returned by GetNodeIDsByProperty(), ID: %s", id1.String()) } } }