github.com/letterj/go-ethereum@v1.8.22-0.20190204142846-520024dfd689/p2p/simulations/network_test.go (about)

     1  // Copyright 2017 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum 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-ethereum 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-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package simulations
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"fmt"
    23  	"strconv"
    24  	"strings"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/ethereum/go-ethereum/log"
    29  	"github.com/ethereum/go-ethereum/node"
    30  	"github.com/ethereum/go-ethereum/p2p/enode"
    31  	"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
    32  )
    33  
    34  // Tests that a created snapshot with a minimal service only contains the expected connections
    35  // and that a network when loaded with this snapshot only contains those same connections
    36  func TestSnapshot(t *testing.T) {
    37  
    38  	// PART I
    39  	// create snapshot from ring network
    40  
    41  	// this is a minimal service, whose protocol will take exactly one message OR close of connection before quitting
    42  	adapter := adapters.NewSimAdapter(adapters.Services{
    43  		"noopwoop": func(ctx *adapters.ServiceContext) (node.Service, error) {
    44  			return NewNoopService(nil), nil
    45  		},
    46  	})
    47  
    48  	// create network
    49  	network := NewNetwork(adapter, &NetworkConfig{
    50  		DefaultService: "noopwoop",
    51  	})
    52  	// \todo consider making a member of network, set to true threadsafe when shutdown
    53  	runningOne := true
    54  	defer func() {
    55  		if runningOne {
    56  			network.Shutdown()
    57  		}
    58  	}()
    59  
    60  	// create and start nodes
    61  	nodeCount := 20
    62  	ids := make([]enode.ID, nodeCount)
    63  	for i := 0; i < nodeCount; i++ {
    64  		conf := adapters.RandomNodeConfig()
    65  		node, err := network.NewNodeWithConfig(conf)
    66  		if err != nil {
    67  			t.Fatalf("error creating node: %s", err)
    68  		}
    69  		if err := network.Start(node.ID()); err != nil {
    70  			t.Fatalf("error starting node: %s", err)
    71  		}
    72  		ids[i] = node.ID()
    73  	}
    74  
    75  	// subscribe to peer events
    76  	evC := make(chan *Event)
    77  	sub := network.Events().Subscribe(evC)
    78  	defer sub.Unsubscribe()
    79  
    80  	// connect nodes in a ring
    81  	// spawn separate thread to avoid deadlock in the event listeners
    82  	go func() {
    83  		for i, id := range ids {
    84  			peerID := ids[(i+1)%len(ids)]
    85  			if err := network.Connect(id, peerID); err != nil {
    86  				t.Fatal(err)
    87  			}
    88  		}
    89  	}()
    90  
    91  	// collect connection events up to expected number
    92  	ctx, cancel := context.WithTimeout(context.TODO(), time.Second)
    93  	defer cancel()
    94  	checkIds := make(map[enode.ID][]enode.ID)
    95  	connEventCount := nodeCount
    96  OUTER:
    97  	for {
    98  		select {
    99  		case <-ctx.Done():
   100  			t.Fatal(ctx.Err())
   101  		case ev := <-evC:
   102  			if ev.Type == EventTypeConn && !ev.Control {
   103  
   104  				// fail on any disconnect
   105  				if !ev.Conn.Up {
   106  					t.Fatalf("unexpected disconnect: %v -> %v", ev.Conn.One, ev.Conn.Other)
   107  				}
   108  				checkIds[ev.Conn.One] = append(checkIds[ev.Conn.One], ev.Conn.Other)
   109  				checkIds[ev.Conn.Other] = append(checkIds[ev.Conn.Other], ev.Conn.One)
   110  				connEventCount--
   111  				log.Debug("ev", "count", connEventCount)
   112  				if connEventCount == 0 {
   113  					break OUTER
   114  				}
   115  			}
   116  		}
   117  	}
   118  
   119  	// create snapshot of current network
   120  	snap, err := network.Snapshot()
   121  	if err != nil {
   122  		t.Fatal(err)
   123  	}
   124  	j, err := json.Marshal(snap)
   125  	if err != nil {
   126  		t.Fatal(err)
   127  	}
   128  	log.Debug("snapshot taken", "nodes", len(snap.Nodes), "conns", len(snap.Conns), "json", string(j))
   129  
   130  	// verify that the snap element numbers check out
   131  	if len(checkIds) != len(snap.Conns) || len(checkIds) != len(snap.Nodes) {
   132  		t.Fatalf("snapshot wrong node,conn counts %d,%d != %d", len(snap.Nodes), len(snap.Conns), len(checkIds))
   133  	}
   134  
   135  	// shut down sim network
   136  	runningOne = false
   137  	sub.Unsubscribe()
   138  	network.Shutdown()
   139  
   140  	// check that we have all the expected connections in the snapshot
   141  	for nodid, nodConns := range checkIds {
   142  		for _, nodConn := range nodConns {
   143  			var match bool
   144  			for _, snapConn := range snap.Conns {
   145  				if snapConn.One == nodid && snapConn.Other == nodConn {
   146  					match = true
   147  					break
   148  				} else if snapConn.Other == nodid && snapConn.One == nodConn {
   149  					match = true
   150  					break
   151  				}
   152  			}
   153  			if !match {
   154  				t.Fatalf("snapshot missing conn %v -> %v", nodid, nodConn)
   155  			}
   156  		}
   157  	}
   158  	log.Info("snapshot checked")
   159  
   160  	// PART II
   161  	// load snapshot and verify that exactly same connections are formed
   162  
   163  	adapter = adapters.NewSimAdapter(adapters.Services{
   164  		"noopwoop": func(ctx *adapters.ServiceContext) (node.Service, error) {
   165  			return NewNoopService(nil), nil
   166  		},
   167  	})
   168  	network = NewNetwork(adapter, &NetworkConfig{
   169  		DefaultService: "noopwoop",
   170  	})
   171  	defer func() {
   172  		network.Shutdown()
   173  	}()
   174  
   175  	// subscribe to peer events
   176  	// every node up and conn up event will generate one additional control event
   177  	// therefore multiply the count by two
   178  	evC = make(chan *Event, (len(snap.Conns)*2)+(len(snap.Nodes)*2))
   179  	sub = network.Events().Subscribe(evC)
   180  	defer sub.Unsubscribe()
   181  
   182  	// load the snapshot
   183  	// spawn separate thread to avoid deadlock in the event listeners
   184  	err = network.Load(snap)
   185  	if err != nil {
   186  		t.Fatal(err)
   187  	}
   188  
   189  	// collect connection events up to expected number
   190  	ctx, cancel = context.WithTimeout(context.TODO(), time.Second*3)
   191  	defer cancel()
   192  
   193  	connEventCount = nodeCount
   194  
   195  OUTER_TWO:
   196  	for {
   197  		select {
   198  		case <-ctx.Done():
   199  			t.Fatal(ctx.Err())
   200  		case ev := <-evC:
   201  			if ev.Type == EventTypeConn && !ev.Control {
   202  
   203  				// fail on any disconnect
   204  				if !ev.Conn.Up {
   205  					t.Fatalf("unexpected disconnect: %v -> %v", ev.Conn.One, ev.Conn.Other)
   206  				}
   207  				log.Debug("conn", "on", ev.Conn.One, "other", ev.Conn.Other)
   208  				checkIds[ev.Conn.One] = append(checkIds[ev.Conn.One], ev.Conn.Other)
   209  				checkIds[ev.Conn.Other] = append(checkIds[ev.Conn.Other], ev.Conn.One)
   210  				connEventCount--
   211  				log.Debug("ev", "count", connEventCount)
   212  				if connEventCount == 0 {
   213  					break OUTER_TWO
   214  				}
   215  			}
   216  		}
   217  	}
   218  
   219  	// check that we have all expected connections in the network
   220  	for _, snapConn := range snap.Conns {
   221  		var match bool
   222  		for nodid, nodConns := range checkIds {
   223  			for _, nodConn := range nodConns {
   224  				if snapConn.One == nodid && snapConn.Other == nodConn {
   225  					match = true
   226  					break
   227  				} else if snapConn.Other == nodid && snapConn.One == nodConn {
   228  					match = true
   229  					break
   230  				}
   231  			}
   232  		}
   233  		if !match {
   234  			t.Fatalf("network missing conn %v -> %v", snapConn.One, snapConn.Other)
   235  		}
   236  	}
   237  
   238  	// verify that network didn't generate any other additional connection events after the ones we have collected within a reasonable period of time
   239  	ctx, cancel = context.WithTimeout(context.TODO(), time.Second)
   240  	defer cancel()
   241  	select {
   242  	case <-ctx.Done():
   243  	case ev := <-evC:
   244  		if ev.Type == EventTypeConn {
   245  			t.Fatalf("Superfluous conn found %v -> %v", ev.Conn.One, ev.Conn.Other)
   246  		}
   247  	}
   248  
   249  	// This test validates if all connections from the snapshot
   250  	// are created in the network.
   251  	t.Run("conns after load", func(t *testing.T) {
   252  		// Create new network.
   253  		n := NewNetwork(
   254  			adapters.NewSimAdapter(adapters.Services{
   255  				"noopwoop": func(ctx *adapters.ServiceContext) (node.Service, error) {
   256  					return NewNoopService(nil), nil
   257  				},
   258  			}),
   259  			&NetworkConfig{
   260  				DefaultService: "noopwoop",
   261  			},
   262  		)
   263  		defer n.Shutdown()
   264  
   265  		// Load the same snapshot.
   266  		err := n.Load(snap)
   267  		if err != nil {
   268  			t.Fatal(err)
   269  		}
   270  
   271  		// Check every connection from the snapshot
   272  		// if it is in the network, too.
   273  		for _, c := range snap.Conns {
   274  			if n.GetConn(c.One, c.Other) == nil {
   275  				t.Errorf("missing connection: %s -> %s", c.One, c.Other)
   276  			}
   277  		}
   278  	})
   279  }
   280  
   281  // TestNetworkSimulation creates a multi-node simulation network with each node
   282  // connected in a ring topology, checks that all nodes successfully handshake
   283  // with each other and that a snapshot fully represents the desired topology
   284  func TestNetworkSimulation(t *testing.T) {
   285  	// create simulation network with 20 testService nodes
   286  	adapter := adapters.NewSimAdapter(adapters.Services{
   287  		"test": newTestService,
   288  	})
   289  	network := NewNetwork(adapter, &NetworkConfig{
   290  		DefaultService: "test",
   291  	})
   292  	defer network.Shutdown()
   293  	nodeCount := 20
   294  	ids := make([]enode.ID, nodeCount)
   295  	for i := 0; i < nodeCount; i++ {
   296  		conf := adapters.RandomNodeConfig()
   297  		node, err := network.NewNodeWithConfig(conf)
   298  		if err != nil {
   299  			t.Fatalf("error creating node: %s", err)
   300  		}
   301  		if err := network.Start(node.ID()); err != nil {
   302  			t.Fatalf("error starting node: %s", err)
   303  		}
   304  		ids[i] = node.ID()
   305  	}
   306  
   307  	// perform a check which connects the nodes in a ring (so each node is
   308  	// connected to exactly two peers) and then checks that all nodes
   309  	// performed two handshakes by checking their peerCount
   310  	action := func(_ context.Context) error {
   311  		for i, id := range ids {
   312  			peerID := ids[(i+1)%len(ids)]
   313  			if err := network.Connect(id, peerID); err != nil {
   314  				return err
   315  			}
   316  		}
   317  		return nil
   318  	}
   319  	check := func(ctx context.Context, id enode.ID) (bool, error) {
   320  		// check we haven't run out of time
   321  		select {
   322  		case <-ctx.Done():
   323  			return false, ctx.Err()
   324  		default:
   325  		}
   326  
   327  		// get the node
   328  		node := network.GetNode(id)
   329  		if node == nil {
   330  			return false, fmt.Errorf("unknown node: %s", id)
   331  		}
   332  
   333  		// check it has exactly two peers
   334  		client, err := node.Client()
   335  		if err != nil {
   336  			return false, err
   337  		}
   338  		var peerCount int64
   339  		if err := client.CallContext(ctx, &peerCount, "test_peerCount"); err != nil {
   340  			return false, err
   341  		}
   342  		switch {
   343  		case peerCount < 2:
   344  			return false, nil
   345  		case peerCount == 2:
   346  			return true, nil
   347  		default:
   348  			return false, fmt.Errorf("unexpected peerCount: %d", peerCount)
   349  		}
   350  	}
   351  
   352  	timeout := 30 * time.Second
   353  	ctx, cancel := context.WithTimeout(context.Background(), timeout)
   354  	defer cancel()
   355  
   356  	// trigger a check every 100ms
   357  	trigger := make(chan enode.ID)
   358  	go triggerChecks(ctx, ids, trigger, 100*time.Millisecond)
   359  
   360  	result := NewSimulation(network).Run(ctx, &Step{
   361  		Action:  action,
   362  		Trigger: trigger,
   363  		Expect: &Expectation{
   364  			Nodes: ids,
   365  			Check: check,
   366  		},
   367  	})
   368  	if result.Error != nil {
   369  		t.Fatalf("simulation failed: %s", result.Error)
   370  	}
   371  
   372  	// take a network snapshot and check it contains the correct topology
   373  	snap, err := network.Snapshot()
   374  	if err != nil {
   375  		t.Fatal(err)
   376  	}
   377  	if len(snap.Nodes) != nodeCount {
   378  		t.Fatalf("expected snapshot to contain %d nodes, got %d", nodeCount, len(snap.Nodes))
   379  	}
   380  	if len(snap.Conns) != nodeCount {
   381  		t.Fatalf("expected snapshot to contain %d connections, got %d", nodeCount, len(snap.Conns))
   382  	}
   383  	for i, id := range ids {
   384  		conn := snap.Conns[i]
   385  		if conn.One != id {
   386  			t.Fatalf("expected conn[%d].One to be %s, got %s", i, id, conn.One)
   387  		}
   388  		peerID := ids[(i+1)%len(ids)]
   389  		if conn.Other != peerID {
   390  			t.Fatalf("expected conn[%d].Other to be %s, got %s", i, peerID, conn.Other)
   391  		}
   392  	}
   393  }
   394  
   395  func triggerChecks(ctx context.Context, ids []enode.ID, trigger chan enode.ID, interval time.Duration) {
   396  	tick := time.NewTicker(interval)
   397  	defer tick.Stop()
   398  	for {
   399  		select {
   400  		case <-tick.C:
   401  			for _, id := range ids {
   402  				select {
   403  				case trigger <- id:
   404  				case <-ctx.Done():
   405  					return
   406  				}
   407  			}
   408  		case <-ctx.Done():
   409  			return
   410  		}
   411  	}
   412  }
   413  
   414  // \todo: refactor to implement shapshots
   415  // and connect configuration methods once these are moved from
   416  // swarm/network/simulations/connect.go
   417  func BenchmarkMinimalService(b *testing.B) {
   418  	b.Run("ring/32", benchmarkMinimalServiceTmp)
   419  }
   420  
   421  func benchmarkMinimalServiceTmp(b *testing.B) {
   422  
   423  	// stop timer to discard setup time pollution
   424  	args := strings.Split(b.Name(), "/")
   425  	nodeCount, err := strconv.ParseInt(args[2], 10, 16)
   426  	if err != nil {
   427  		b.Fatal(err)
   428  	}
   429  
   430  	for i := 0; i < b.N; i++ {
   431  		// this is a minimal service, whose protocol will close a channel upon run of protocol
   432  		// making it possible to bench the time it takes for the service to start and protocol actually to be run
   433  		protoCMap := make(map[enode.ID]map[enode.ID]chan struct{})
   434  		adapter := adapters.NewSimAdapter(adapters.Services{
   435  			"noopwoop": func(ctx *adapters.ServiceContext) (node.Service, error) {
   436  				protoCMap[ctx.Config.ID] = make(map[enode.ID]chan struct{})
   437  				svc := NewNoopService(protoCMap[ctx.Config.ID])
   438  				return svc, nil
   439  			},
   440  		})
   441  
   442  		// create network
   443  		network := NewNetwork(adapter, &NetworkConfig{
   444  			DefaultService: "noopwoop",
   445  		})
   446  		defer network.Shutdown()
   447  
   448  		// create and start nodes
   449  		ids := make([]enode.ID, nodeCount)
   450  		for i := 0; i < int(nodeCount); i++ {
   451  			conf := adapters.RandomNodeConfig()
   452  			node, err := network.NewNodeWithConfig(conf)
   453  			if err != nil {
   454  				b.Fatalf("error creating node: %s", err)
   455  			}
   456  			if err := network.Start(node.ID()); err != nil {
   457  				b.Fatalf("error starting node: %s", err)
   458  			}
   459  			ids[i] = node.ID()
   460  		}
   461  
   462  		// ready, set, go
   463  		b.ResetTimer()
   464  
   465  		// connect nodes in a ring
   466  		for i, id := range ids {
   467  			peerID := ids[(i+1)%len(ids)]
   468  			if err := network.Connect(id, peerID); err != nil {
   469  				b.Fatal(err)
   470  			}
   471  		}
   472  
   473  		// wait for all protocols to signal to close down
   474  		ctx, cancel := context.WithTimeout(context.TODO(), time.Second)
   475  		defer cancel()
   476  		for nodid, peers := range protoCMap {
   477  			for peerid, peerC := range peers {
   478  				log.Debug("getting ", "node", nodid, "peer", peerid)
   479  				select {
   480  				case <-ctx.Done():
   481  					b.Fatal(ctx.Err())
   482  				case <-peerC:
   483  				}
   484  			}
   485  		}
   486  	}
   487  }