github.com/core-coin/go-core/v2@v2.1.9/p2p/simulations/network_test.go (about)

     1  // Copyright 2017 by the Authors
     2  // This file is part of the go-core library.
     3  //
     4  // The go-core library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-core library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-core library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package simulations
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"encoding/json"
    23  	"fmt"
    24  	"reflect"
    25  	"strconv"
    26  	"strings"
    27  	"testing"
    28  	"time"
    29  
    30  	"github.com/core-coin/go-core/v2/log"
    31  	"github.com/core-coin/go-core/v2/node"
    32  	"github.com/core-coin/go-core/v2/p2p/enode"
    33  	"github.com/core-coin/go-core/v2/p2p/simulations/adapters"
    34  )
    35  
    36  // Tests that a created snapshot with a minimal service only contains the expected connections
    37  // and that a network when loaded with this snapshot only contains those same connections
    38  func TestSnapshot(t *testing.T) {
    39  
    40  	// PART I
    41  	// create snapshot from ring network
    42  
    43  	// this is a minimal service, whose protocol will take exactly one message OR close of connection before quitting
    44  	adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{
    45  		"noopwoop": func(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) {
    46  			return NewNoopService(nil), nil
    47  		},
    48  	})
    49  
    50  	// create network
    51  	network := NewNetwork(adapter, &NetworkConfig{
    52  		DefaultService: "noopwoop",
    53  	})
    54  	// \todo consider making a member of network, set to true threadsafe when shutdown
    55  	runningOne := true
    56  	defer func() {
    57  		if runningOne {
    58  			network.Shutdown()
    59  		}
    60  	}()
    61  
    62  	// create and start nodes
    63  	nodeCount := 20
    64  	ids := make([]enode.ID, nodeCount)
    65  	for i := 0; i < nodeCount; i++ {
    66  		conf := adapters.RandomNodeConfig()
    67  		node, err := network.NewNodeWithConfig(conf)
    68  		if err != nil {
    69  			t.Fatalf("error creating node: %s", err)
    70  		}
    71  		if err := network.Start(node.ID()); err != nil {
    72  			t.Fatalf("error starting node: %s", err)
    73  		}
    74  		ids[i] = node.ID()
    75  	}
    76  
    77  	// subscribe to peer events
    78  	evC := make(chan *Event)
    79  	sub := network.Events().Subscribe(evC)
    80  	defer sub.Unsubscribe()
    81  
    82  	// connect nodes in a ring
    83  	// spawn separate thread to avoid deadlock in the event listeners
    84  	connectErr := make(chan error, 1)
    85  	go func() {
    86  		for i, id := range ids {
    87  			peerID := ids[(i+1)%len(ids)]
    88  			if err := network.Connect(id, peerID); err != nil {
    89  				connectErr <- err
    90  				return
    91  			}
    92  		}
    93  	}()
    94  
    95  	// collect connection events up to expected number
    96  	ctx, cancel := context.WithTimeout(context.TODO(), time.Second)
    97  	defer cancel()
    98  	checkIds := make(map[enode.ID][]enode.ID)
    99  	connEventCount := nodeCount
   100  OUTER:
   101  	for {
   102  		select {
   103  		case <-ctx.Done():
   104  			t.Fatal(ctx.Err())
   105  		case err := <-connectErr:
   106  			t.Fatal(err)
   107  		case ev := <-evC:
   108  			if ev.Type == EventTypeConn && !ev.Control {
   109  				// fail on any disconnect
   110  				if !ev.Conn.Up {
   111  					t.Fatalf("unexpected disconnect: %v -> %v", ev.Conn.One, ev.Conn.Other)
   112  				}
   113  				checkIds[ev.Conn.One] = append(checkIds[ev.Conn.One], ev.Conn.Other)
   114  				checkIds[ev.Conn.Other] = append(checkIds[ev.Conn.Other], ev.Conn.One)
   115  				connEventCount--
   116  				log.Debug("ev", "count", connEventCount)
   117  				if connEventCount == 0 {
   118  					break OUTER
   119  				}
   120  			}
   121  		}
   122  	}
   123  
   124  	// create snapshot of current network
   125  	snap, err := network.Snapshot()
   126  	if err != nil {
   127  		t.Fatal(err)
   128  	}
   129  	j, err := json.Marshal(snap)
   130  	if err != nil {
   131  		t.Fatal(err)
   132  	}
   133  	log.Debug("snapshot taken", "nodes", len(snap.Nodes), "conns", len(snap.Conns), "json", string(j))
   134  
   135  	// verify that the snap element numbers check out
   136  	if len(checkIds) != len(snap.Conns) || len(checkIds) != len(snap.Nodes) {
   137  		t.Fatalf("snapshot wrong node,conn counts %d,%d != %d", len(snap.Nodes), len(snap.Conns), len(checkIds))
   138  	}
   139  
   140  	// shut down sim network
   141  	runningOne = false
   142  	sub.Unsubscribe()
   143  	network.Shutdown()
   144  
   145  	// check that we have all the expected connections in the snapshot
   146  	for nodid, nodConns := range checkIds {
   147  		for _, nodConn := range nodConns {
   148  			var match bool
   149  			for _, snapConn := range snap.Conns {
   150  				if snapConn.One == nodid && snapConn.Other == nodConn {
   151  					match = true
   152  					break
   153  				} else if snapConn.Other == nodid && snapConn.One == nodConn {
   154  					match = true
   155  					break
   156  				}
   157  			}
   158  			if !match {
   159  				t.Fatalf("snapshot missing conn %v -> %v", nodid, nodConn)
   160  			}
   161  		}
   162  	}
   163  	log.Info("snapshot checked")
   164  
   165  	// PART II
   166  	// load snapshot and verify that exactly same connections are formed
   167  
   168  	adapter = adapters.NewSimAdapter(adapters.LifecycleConstructors{
   169  		"noopwoop": func(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) {
   170  			return NewNoopService(nil), nil
   171  		},
   172  	})
   173  	network = NewNetwork(adapter, &NetworkConfig{
   174  		DefaultService: "noopwoop",
   175  	})
   176  	defer func() {
   177  		network.Shutdown()
   178  	}()
   179  
   180  	// subscribe to peer events
   181  	// every node up and conn up event will generate one additional control event
   182  	// therefore multiply the count by two
   183  	evC = make(chan *Event, (len(snap.Conns)*2)+(len(snap.Nodes)*2))
   184  	sub = network.Events().Subscribe(evC)
   185  	defer sub.Unsubscribe()
   186  
   187  	// load the snapshot
   188  	// spawn separate thread to avoid deadlock in the event listeners
   189  	err = network.Load(snap)
   190  	if err != nil {
   191  		t.Fatal(err)
   192  	}
   193  
   194  	// collect connection events up to expected number
   195  	ctx, cancel = context.WithTimeout(context.TODO(), time.Second*3)
   196  	defer cancel()
   197  
   198  	connEventCount = nodeCount
   199  
   200  OuterTwo:
   201  	for {
   202  		select {
   203  		case <-ctx.Done():
   204  			t.Fatal(ctx.Err())
   205  		case ev := <-evC:
   206  			if ev.Type == EventTypeConn && !ev.Control {
   207  
   208  				// fail on any disconnect
   209  				if !ev.Conn.Up {
   210  					t.Fatalf("unexpected disconnect: %v -> %v", ev.Conn.One, ev.Conn.Other)
   211  				}
   212  				log.Debug("conn", "on", ev.Conn.One, "other", ev.Conn.Other)
   213  				checkIds[ev.Conn.One] = append(checkIds[ev.Conn.One], ev.Conn.Other)
   214  				checkIds[ev.Conn.Other] = append(checkIds[ev.Conn.Other], ev.Conn.One)
   215  				connEventCount--
   216  				log.Debug("ev", "count", connEventCount)
   217  				if connEventCount == 0 {
   218  					break OuterTwo
   219  				}
   220  			}
   221  		}
   222  	}
   223  
   224  	// check that we have all expected connections in the network
   225  	for _, snapConn := range snap.Conns {
   226  		var match bool
   227  		for nodid, nodConns := range checkIds {
   228  			for _, nodConn := range nodConns {
   229  				if snapConn.One == nodid && snapConn.Other == nodConn {
   230  					match = true
   231  					break
   232  				} else if snapConn.Other == nodid && snapConn.One == nodConn {
   233  					match = true
   234  					break
   235  				}
   236  			}
   237  		}
   238  		if !match {
   239  			t.Fatalf("network missing conn %v -> %v", snapConn.One, snapConn.Other)
   240  		}
   241  	}
   242  
   243  	// verify that network didn't generate any other additional connection events after the ones we have collected within a reasonable period of time
   244  	ctx, cancel = context.WithTimeout(context.TODO(), time.Second)
   245  	defer cancel()
   246  	select {
   247  	case <-ctx.Done():
   248  	case ev := <-evC:
   249  		if ev.Type == EventTypeConn {
   250  			t.Fatalf("Superfluous conn found %v -> %v", ev.Conn.One, ev.Conn.Other)
   251  		}
   252  	}
   253  
   254  	// This test validates if all connections from the snapshot
   255  	// are created in the network.
   256  	t.Run("conns after load", func(t *testing.T) {
   257  		// Create new network.
   258  		n := NewNetwork(
   259  			adapters.NewSimAdapter(adapters.LifecycleConstructors{
   260  				"noopwoop": func(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) {
   261  					return NewNoopService(nil), nil
   262  				},
   263  			}),
   264  			&NetworkConfig{
   265  				DefaultService: "noopwoop",
   266  			},
   267  		)
   268  		defer n.Shutdown()
   269  
   270  		// Load the same snapshot.
   271  		err := n.Load(snap)
   272  		if err != nil {
   273  			t.Fatal(err)
   274  		}
   275  
   276  		// Check every connection from the snapshot
   277  		// if it is in the network, too.
   278  		for _, c := range snap.Conns {
   279  			if n.GetConn(c.One, c.Other) == nil {
   280  				t.Errorf("missing connection: %s -> %s", c.One, c.Other)
   281  			}
   282  		}
   283  	})
   284  }
   285  
   286  // TestNetworkSimulation creates a multi-node simulation network with each node
   287  // connected in a ring topology, checks that all nodes successfully handshake
   288  // with each other and that a snapshot fully represents the desired topology
   289  func TestNetworkSimulation(t *testing.T) {
   290  	// create simulation network with 20 testService nodes
   291  	adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{
   292  		"test": newTestService,
   293  	})
   294  	network := NewNetwork(adapter, &NetworkConfig{
   295  		DefaultService: "test",
   296  	})
   297  	defer network.Shutdown()
   298  	nodeCount := 20
   299  	ids := make([]enode.ID, nodeCount)
   300  	for i := 0; i < nodeCount; i++ {
   301  		conf := adapters.RandomNodeConfig()
   302  		node, err := network.NewNodeWithConfig(conf)
   303  		if err != nil {
   304  			t.Fatalf("error creating node: %s", err)
   305  		}
   306  		if err := network.Start(node.ID()); err != nil {
   307  			t.Fatalf("error starting node: %s", err)
   308  		}
   309  		ids[i] = node.ID()
   310  	}
   311  
   312  	// perform a check which connects the nodes in a ring (so each node is
   313  	// connected to exactly two peers) and then checks that all nodes
   314  	// performed two handshakes by checking their peerCount
   315  	action := func(_ context.Context) error {
   316  		for i, id := range ids {
   317  			peerID := ids[(i+1)%len(ids)]
   318  			if err := network.Connect(id, peerID); err != nil {
   319  				return err
   320  			}
   321  		}
   322  		return nil
   323  	}
   324  	check := func(ctx context.Context, id enode.ID) (bool, error) {
   325  		// check we haven't run out of time
   326  		select {
   327  		case <-ctx.Done():
   328  			return false, ctx.Err()
   329  		default:
   330  		}
   331  
   332  		// get the node
   333  		node := network.GetNode(id)
   334  		if node == nil {
   335  			return false, fmt.Errorf("unknown node: %s", id)
   336  		}
   337  
   338  		// check it has exactly two peers
   339  		client, err := node.Client()
   340  		if err != nil {
   341  			return false, err
   342  		}
   343  		var peerCount int64
   344  		if err := client.CallContext(ctx, &peerCount, "test_peerCount"); err != nil {
   345  			return false, err
   346  		}
   347  		switch {
   348  		case peerCount < 2:
   349  			return false, nil
   350  		case peerCount == 2:
   351  			return true, nil
   352  		default:
   353  			return false, fmt.Errorf("unexpected peerCount: %d", peerCount)
   354  		}
   355  	}
   356  
   357  	timeout := 30 * time.Second
   358  	ctx, cancel := context.WithTimeout(context.Background(), timeout)
   359  	defer cancel()
   360  
   361  	// trigger a check every 100ms
   362  	trigger := make(chan enode.ID)
   363  	go triggerChecks(ctx, ids, trigger, 100*time.Millisecond)
   364  
   365  	result := NewSimulation(network).Run(ctx, &Step{
   366  		Action:  action,
   367  		Trigger: trigger,
   368  		Expect: &Expectation{
   369  			Nodes: ids,
   370  			Check: check,
   371  		},
   372  	})
   373  	if result.Error != nil {
   374  		t.Fatalf("simulation failed: %s", result.Error)
   375  	}
   376  
   377  	// take a network snapshot and check it contains the correct topology
   378  	snap, err := network.Snapshot()
   379  	if err != nil {
   380  		t.Fatal(err)
   381  	}
   382  	if len(snap.Nodes) != nodeCount {
   383  		t.Fatalf("expected snapshot to contain %d nodes, got %d", nodeCount, len(snap.Nodes))
   384  	}
   385  	if len(snap.Conns) != nodeCount {
   386  		t.Fatalf("expected snapshot to contain %d connections, got %d", nodeCount, len(snap.Conns))
   387  	}
   388  	for i, id := range ids {
   389  		conn := snap.Conns[i]
   390  		if conn.One != id {
   391  			t.Fatalf("expected conn[%d].One to be %s, got %s", i, id, conn.One)
   392  		}
   393  		peerID := ids[(i+1)%len(ids)]
   394  		if conn.Other != peerID {
   395  			t.Fatalf("expected conn[%d].Other to be %s, got %s", i, peerID, conn.Other)
   396  		}
   397  	}
   398  }
   399  
   400  func createTestNodes(count int, network *Network) (nodes []*Node, err error) {
   401  	for i := 0; i < count; i++ {
   402  		nodeConf := adapters.RandomNodeConfig()
   403  		node, err := network.NewNodeWithConfig(nodeConf)
   404  		if err != nil {
   405  			return nil, err
   406  		}
   407  		if err := network.Start(node.ID()); err != nil {
   408  			return nil, err
   409  		}
   410  
   411  		nodes = append(nodes, node)
   412  	}
   413  
   414  	return nodes, nil
   415  }
   416  
   417  func createTestNodesWithProperty(property string, count int, network *Network) (propertyNodes []*Node, err error) {
   418  	for i := 0; i < count; i++ {
   419  		nodeConf := adapters.RandomNodeConfig()
   420  		nodeConf.Properties = append(nodeConf.Properties, property)
   421  
   422  		node, err := network.NewNodeWithConfig(nodeConf)
   423  		if err != nil {
   424  			return nil, err
   425  		}
   426  		if err := network.Start(node.ID()); err != nil {
   427  			return nil, err
   428  		}
   429  
   430  		propertyNodes = append(propertyNodes, node)
   431  	}
   432  
   433  	return propertyNodes, nil
   434  }
   435  
   436  // TestGetNodeIDs creates a set of nodes and attempts to retrieve their IDs,.
   437  // It then tests again whilst excluding a node ID from being returned.
   438  // If a node ID is not returned, or more node IDs than expected are returned, the test fails.
   439  func TestGetNodeIDs(t *testing.T) {
   440  	adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{
   441  		"test": newTestService,
   442  	})
   443  	network := NewNetwork(adapter, &NetworkConfig{
   444  		DefaultService: "test",
   445  	})
   446  	defer network.Shutdown()
   447  
   448  	numNodes := 5
   449  	nodes, err := createTestNodes(numNodes, network)
   450  	if err != nil {
   451  		t.Fatalf("Could not creat test nodes %v", err)
   452  	}
   453  
   454  	gotNodeIDs := network.GetNodeIDs()
   455  	if len(gotNodeIDs) != numNodes {
   456  		t.Fatalf("Expected %d nodes, got %d", numNodes, len(gotNodeIDs))
   457  	}
   458  
   459  	for _, node1 := range nodes {
   460  		match := false
   461  		for _, node2ID := range gotNodeIDs {
   462  			if bytes.Equal(node1.ID().Bytes(), node2ID.Bytes()) {
   463  				match = true
   464  				break
   465  			}
   466  		}
   467  
   468  		if !match {
   469  			t.Fatalf("A created node was not returned by GetNodes(), ID: %s", node1.ID().String())
   470  		}
   471  	}
   472  
   473  	excludeNodeID := nodes[3].ID()
   474  	gotNodeIDsExcl := network.GetNodeIDs(excludeNodeID)
   475  	if len(gotNodeIDsExcl) != numNodes-1 {
   476  		t.Fatalf("Expected one less node ID to be returned")
   477  	}
   478  	for _, nodeID := range gotNodeIDsExcl {
   479  		if bytes.Equal(excludeNodeID.Bytes(), nodeID.Bytes()) {
   480  			t.Fatalf("GetNodeIDs returned the node ID we excluded, ID: %s", nodeID.String())
   481  		}
   482  	}
   483  }
   484  
   485  // TestGetNodes creates a set of nodes and attempts to retrieve them again.
   486  // It then tests again whilst excluding a node from being returned.
   487  // If a node is not returned, or more nodes than expected are returned, the test fails.
   488  func TestGetNodes(t *testing.T) {
   489  	adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{
   490  		"test": newTestService,
   491  	})
   492  	network := NewNetwork(adapter, &NetworkConfig{
   493  		DefaultService: "test",
   494  	})
   495  	defer network.Shutdown()
   496  
   497  	numNodes := 5
   498  	nodes, err := createTestNodes(numNodes, network)
   499  	if err != nil {
   500  		t.Fatalf("Could not creat test nodes %v", err)
   501  	}
   502  
   503  	gotNodes := network.GetNodes()
   504  	if len(gotNodes) != numNodes {
   505  		t.Fatalf("Expected %d nodes, got %d", numNodes, len(gotNodes))
   506  	}
   507  
   508  	for _, node1 := range nodes {
   509  		match := false
   510  		for _, node2 := range gotNodes {
   511  			if bytes.Equal(node1.ID().Bytes(), node2.ID().Bytes()) {
   512  				match = true
   513  				break
   514  			}
   515  		}
   516  
   517  		if !match {
   518  			t.Fatalf("A created node was not returned by GetNodes(), ID: %s", node1.ID().String())
   519  		}
   520  	}
   521  
   522  	excludeNodeID := nodes[3].ID()
   523  	gotNodesExcl := network.GetNodes(excludeNodeID)
   524  	if len(gotNodesExcl) != numNodes-1 {
   525  		t.Fatalf("Expected one less node to be returned")
   526  	}
   527  	for _, node := range gotNodesExcl {
   528  		if bytes.Equal(excludeNodeID.Bytes(), node.ID().Bytes()) {
   529  			t.Fatalf("GetNodes returned the node we excluded, ID: %s", node.ID().String())
   530  		}
   531  	}
   532  }
   533  
   534  // TestGetNodesByID creates a set of nodes and attempts to retrieve a subset of them by ID
   535  // If a node is not returned, or more nodes than expected are returned, the test fails.
   536  func TestGetNodesByID(t *testing.T) {
   537  	adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{
   538  		"test": newTestService,
   539  	})
   540  	network := NewNetwork(adapter, &NetworkConfig{
   541  		DefaultService: "test",
   542  	})
   543  	defer network.Shutdown()
   544  
   545  	numNodes := 5
   546  	nodes, err := createTestNodes(numNodes, network)
   547  	if err != nil {
   548  		t.Fatalf("Could not create test nodes: %v", err)
   549  	}
   550  
   551  	numSubsetNodes := 2
   552  	subsetNodes := nodes[0:numSubsetNodes]
   553  	var subsetNodeIDs []enode.ID
   554  	for _, node := range subsetNodes {
   555  		subsetNodeIDs = append(subsetNodeIDs, node.ID())
   556  	}
   557  
   558  	gotNodesByID := network.GetNodesByID(subsetNodeIDs)
   559  	if len(gotNodesByID) != numSubsetNodes {
   560  		t.Fatalf("Expected %d nodes, got %d", numSubsetNodes, len(gotNodesByID))
   561  	}
   562  
   563  	for _, node1 := range subsetNodes {
   564  		match := false
   565  		for _, node2 := range gotNodesByID {
   566  			if bytes.Equal(node1.ID().Bytes(), node2.ID().Bytes()) {
   567  				match = true
   568  				break
   569  			}
   570  		}
   571  
   572  		if !match {
   573  			t.Fatalf("A created node was not returned by GetNodesByID(), ID: %s", node1.ID().String())
   574  		}
   575  	}
   576  }
   577  
   578  // TestGetNodesByProperty creates a subset of nodes with a property assigned.
   579  // GetNodesByProperty is then checked for correctness by comparing the nodes returned to those initially created.
   580  // If a node with a property is not found, or more nodes than expected are returned, the test fails.
   581  func TestGetNodesByProperty(t *testing.T) {
   582  	adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{
   583  		"test": newTestService,
   584  	})
   585  	network := NewNetwork(adapter, &NetworkConfig{
   586  		DefaultService: "test",
   587  	})
   588  	defer network.Shutdown()
   589  
   590  	numNodes := 3
   591  	_, err := createTestNodes(numNodes, network)
   592  	if err != nil {
   593  		t.Fatalf("Failed to create nodes: %v", err)
   594  	}
   595  
   596  	numPropertyNodes := 3
   597  	propertyTest := "test"
   598  	propertyNodes, err := createTestNodesWithProperty(propertyTest, numPropertyNodes, network)
   599  	if err != nil {
   600  		t.Fatalf("Failed to create nodes with property: %v", err)
   601  	}
   602  
   603  	gotNodesByProperty := network.GetNodesByProperty(propertyTest)
   604  	if len(gotNodesByProperty) != numPropertyNodes {
   605  		t.Fatalf("Expected %d nodes with a property, got %d", numPropertyNodes, len(gotNodesByProperty))
   606  	}
   607  
   608  	for _, node1 := range propertyNodes {
   609  		match := false
   610  		for _, node2 := range gotNodesByProperty {
   611  			if bytes.Equal(node1.ID().Bytes(), node2.ID().Bytes()) {
   612  				match = true
   613  				break
   614  			}
   615  		}
   616  
   617  		if !match {
   618  			t.Fatalf("A created node with property was not returned by GetNodesByProperty(), ID: %s", node1.ID().String())
   619  		}
   620  	}
   621  }
   622  
   623  // TestGetNodeIDsByProperty creates a subset of nodes with a property assigned.
   624  // GetNodeIDsByProperty is then checked for correctness by comparing the node IDs returned to those initially created.
   625  // If a node ID with a property is not found, or more nodes IDs than expected are returned, the test fails.
   626  func TestGetNodeIDsByProperty(t *testing.T) {
   627  	adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{
   628  		"test": newTestService,
   629  	})
   630  	network := NewNetwork(adapter, &NetworkConfig{
   631  		DefaultService: "test",
   632  	})
   633  	defer network.Shutdown()
   634  
   635  	numNodes := 3
   636  	_, err := createTestNodes(numNodes, network)
   637  	if err != nil {
   638  		t.Fatalf("Failed to create nodes: %v", err)
   639  	}
   640  
   641  	numPropertyNodes := 3
   642  	propertyTest := "test"
   643  	propertyNodes, err := createTestNodesWithProperty(propertyTest, numPropertyNodes, network)
   644  	if err != nil {
   645  		t.Fatalf("Failed to created nodes with property: %v", err)
   646  	}
   647  
   648  	gotNodeIDsByProperty := network.GetNodeIDsByProperty(propertyTest)
   649  	if len(gotNodeIDsByProperty) != numPropertyNodes {
   650  		t.Fatalf("Expected %d nodes with a property, got %d", numPropertyNodes, len(gotNodeIDsByProperty))
   651  	}
   652  
   653  	for _, node1 := range propertyNodes {
   654  		match := false
   655  		id1 := node1.ID()
   656  		for _, id2 := range gotNodeIDsByProperty {
   657  			if bytes.Equal(id1.Bytes(), id2.Bytes()) {
   658  				match = true
   659  				break
   660  			}
   661  		}
   662  
   663  		if !match {
   664  			t.Fatalf("Not all nodes IDs were returned by GetNodeIDsByProperty(), ID: %s", id1.String())
   665  		}
   666  	}
   667  }
   668  
   669  func triggerChecks(ctx context.Context, ids []enode.ID, trigger chan enode.ID, interval time.Duration) {
   670  	tick := time.NewTicker(interval)
   671  	defer tick.Stop()
   672  	for {
   673  		select {
   674  		case <-tick.C:
   675  			for _, id := range ids {
   676  				select {
   677  				case trigger <- id:
   678  				case <-ctx.Done():
   679  					return
   680  				}
   681  			}
   682  		case <-ctx.Done():
   683  			return
   684  		}
   685  	}
   686  }
   687  
   688  // \todo: refactor to implement shapshots
   689  // and connect configuration methods once these are moved from
   690  // swarm/network/simulations/connect.go
   691  func BenchmarkMinimalService(b *testing.B) {
   692  	b.Run("ring/32", benchmarkMinimalServiceTmp)
   693  }
   694  
   695  func benchmarkMinimalServiceTmp(b *testing.B) {
   696  
   697  	// stop timer to discard setup time pollution
   698  	args := strings.Split(b.Name(), "/")
   699  	nodeCount, err := strconv.ParseInt(args[2], 10, 16)
   700  	if err != nil {
   701  		b.Fatal(err)
   702  	}
   703  
   704  	for i := 0; i < b.N; i++ {
   705  		// this is a minimal service, whose protocol will close a channel upon run of protocol
   706  		// making it possible to bench the time it takes for the service to start and protocol actually to be run
   707  		protoCMap := make(map[enode.ID]map[enode.ID]chan struct{})
   708  		adapter := adapters.NewSimAdapter(adapters.LifecycleConstructors{
   709  			"noopwoop": func(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) {
   710  				protoCMap[ctx.Config.ID] = make(map[enode.ID]chan struct{})
   711  				svc := NewNoopService(protoCMap[ctx.Config.ID])
   712  				return svc, nil
   713  			},
   714  		})
   715  
   716  		// create network
   717  		network := NewNetwork(adapter, &NetworkConfig{
   718  			DefaultService: "noopwoop",
   719  		})
   720  		defer network.Shutdown()
   721  
   722  		// create and start nodes
   723  		ids := make([]enode.ID, nodeCount)
   724  		for i := 0; i < int(nodeCount); i++ {
   725  			conf := adapters.RandomNodeConfig()
   726  			node, err := network.NewNodeWithConfig(conf)
   727  			if err != nil {
   728  				b.Fatalf("error creating node: %s", err)
   729  			}
   730  			if err := network.Start(node.ID()); err != nil {
   731  				b.Fatalf("error starting node: %s", err)
   732  			}
   733  			ids[i] = node.ID()
   734  		}
   735  
   736  		// ready, set, go
   737  		b.ResetTimer()
   738  
   739  		// connect nodes in a ring
   740  		for i, id := range ids {
   741  			peerID := ids[(i+1)%len(ids)]
   742  			if err := network.Connect(id, peerID); err != nil {
   743  				b.Fatal(err)
   744  			}
   745  		}
   746  
   747  		// wait for all protocols to signal to close down
   748  		ctx, cancel := context.WithTimeout(context.TODO(), time.Second)
   749  		defer cancel()
   750  		for nodid, peers := range protoCMap {
   751  			for peerid, peerC := range peers {
   752  				log.Debug("getting ", "node", nodid, "peer", peerid)
   753  				select {
   754  				case <-ctx.Done():
   755  					b.Fatal(ctx.Err())
   756  				case <-peerC:
   757  				}
   758  			}
   759  		}
   760  	}
   761  }
   762  
   763  func TestNode_UnmarshalJSON(t *testing.T) {
   764  	t.Run("up_field", func(t *testing.T) {
   765  		runNodeUnmarshalJSON(t, casesNodeUnmarshalJSONUpField())
   766  	})
   767  	t.Run("config_field", func(t *testing.T) {
   768  		runNodeUnmarshalJSON(t, casesNodeUnmarshalJSONConfigField())
   769  	})
   770  }
   771  
   772  func runNodeUnmarshalJSON(t *testing.T, tests []nodeUnmarshalTestCase) {
   773  	t.Helper()
   774  	for _, tt := range tests {
   775  		t.Run(tt.name, func(t *testing.T) {
   776  			var got *Node
   777  			if err := json.Unmarshal([]byte(tt.marshaled), &got); err != nil {
   778  				expectErrorMessageToContain(t, err, tt.wantErr)
   779  				got = nil
   780  			}
   781  			expectNodeEquality(t, got, tt.want)
   782  		})
   783  	}
   784  }
   785  
   786  type nodeUnmarshalTestCase struct {
   787  	name      string
   788  	marshaled string
   789  	want      *Node
   790  	wantErr   string
   791  }
   792  
   793  func expectErrorMessageToContain(t *testing.T, got error, want string) {
   794  	t.Helper()
   795  	if got == nil && want == "" {
   796  		return
   797  	}
   798  
   799  	if got == nil && want != "" {
   800  		t.Errorf("error was expected, got: nil, want: %v", want)
   801  		return
   802  	}
   803  
   804  	if !strings.Contains(got.Error(), want) {
   805  		t.Errorf(
   806  			"unexpected error message, got  %v, want: %v",
   807  			want,
   808  			got,
   809  		)
   810  	}
   811  }
   812  
   813  func expectNodeEquality(t *testing.T, got, want *Node) {
   814  	t.Helper()
   815  	if !reflect.DeepEqual(got, want) {
   816  		t.Errorf("Node.UnmarshalJSON() = %v, want %v", got, want)
   817  	}
   818  }
   819  
   820  func casesNodeUnmarshalJSONUpField() []nodeUnmarshalTestCase {
   821  	return []nodeUnmarshalTestCase{
   822  		{
   823  			name:      "empty json",
   824  			marshaled: "{}",
   825  			want:      newNode(nil, nil, false),
   826  		},
   827  		{
   828  			name:      "a stopped node",
   829  			marshaled: "{\"up\": false}",
   830  			want:      newNode(nil, nil, false),
   831  		},
   832  		{
   833  			name:      "a running node",
   834  			marshaled: "{\"up\": true}",
   835  			want:      newNode(nil, nil, true),
   836  		},
   837  		{
   838  			name:      "invalid JSON value on valid key",
   839  			marshaled: "{\"up\": foo}",
   840  			wantErr:   "invalid character",
   841  		},
   842  		{
   843  			name:      "invalid JSON key and value",
   844  			marshaled: "{foo: bar}",
   845  			wantErr:   "invalid character",
   846  		},
   847  		{
   848  			name:      "bool value expected but got something else (string)",
   849  			marshaled: "{\"up\": \"true\"}",
   850  			wantErr:   "cannot unmarshal string into Go struct",
   851  		},
   852  	}
   853  }
   854  
   855  func casesNodeUnmarshalJSONConfigField() []nodeUnmarshalTestCase {
   856  	// Don't do a big fuss around testing, as adapters.NodeConfig should
   857  	// handle it's own serialization. Just do a sanity check.
   858  	return []nodeUnmarshalTestCase{
   859  		{
   860  			name:      "Config field is omitted",
   861  			marshaled: "{}",
   862  			want:      newNode(nil, nil, false),
   863  		},
   864  		{
   865  			name:      "Config field is nil",
   866  			marshaled: "{\"config\": null}",
   867  			want:      newNode(nil, nil, false),
   868  		},
   869  		{
   870  			name:      "a non default Config field",
   871  			marshaled: "{\"config\":{\"name\":\"node_ecdd0\",\"port\":44665}}",
   872  			want:      newNode(nil, &adapters.NodeConfig{Name: "node_ecdd0", Port: 44665}, false),
   873  		},
   874  	}
   875  }