github.com/wzbox/go-ethereum@v1.9.2/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  	"reflect"
    24  	"strconv"
    25  	"strings"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/ethereum/go-ethereum/log"
    30  	"github.com/ethereum/go-ethereum/node"
    31  	"github.com/ethereum/go-ethereum/p2p/enode"
    32  	"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
    33  )
    34  
    35  // Tests that a created snapshot with a minimal service only contains the expected connections
    36  // and that a network when loaded with this snapshot only contains those same connections
    37  func TestSnapshot(t *testing.T) {
    38  
    39  	// PART I
    40  	// create snapshot from ring network
    41  
    42  	// this is a minimal service, whose protocol will take exactly one message OR close of connection before quitting
    43  	adapter := adapters.NewSimAdapter(adapters.Services{
    44  		"noopwoop": func(ctx *adapters.ServiceContext) (node.Service, error) {
    45  			return NewNoopService(nil), nil
    46  		},
    47  	})
    48  
    49  	// create network
    50  	network := NewNetwork(adapter, &NetworkConfig{
    51  		DefaultService: "noopwoop",
    52  	})
    53  	// \todo consider making a member of network, set to true threadsafe when shutdown
    54  	runningOne := true
    55  	defer func() {
    56  		if runningOne {
    57  			network.Shutdown()
    58  		}
    59  	}()
    60  
    61  	// create and start nodes
    62  	nodeCount := 20
    63  	ids := make([]enode.ID, nodeCount)
    64  	for i := 0; i < nodeCount; i++ {
    65  		conf := adapters.RandomNodeConfig()
    66  		node, err := network.NewNodeWithConfig(conf)
    67  		if err != nil {
    68  			t.Fatalf("error creating node: %s", err)
    69  		}
    70  		if err := network.Start(node.ID()); err != nil {
    71  			t.Fatalf("error starting node: %s", err)
    72  		}
    73  		ids[i] = node.ID()
    74  	}
    75  
    76  	// subscribe to peer events
    77  	evC := make(chan *Event)
    78  	sub := network.Events().Subscribe(evC)
    79  	defer sub.Unsubscribe()
    80  
    81  	// connect nodes in a ring
    82  	// spawn separate thread to avoid deadlock in the event listeners
    83  	go func() {
    84  		for i, id := range ids {
    85  			peerID := ids[(i+1)%len(ids)]
    86  			if err := network.Connect(id, peerID); err != nil {
    87  				t.Fatal(err)
    88  			}
    89  		}
    90  	}()
    91  
    92  	// collect connection events up to expected number
    93  	ctx, cancel := context.WithTimeout(context.TODO(), time.Second)
    94  	defer cancel()
    95  	checkIds := make(map[enode.ID][]enode.ID)
    96  	connEventCount := nodeCount
    97  OUTER:
    98  	for {
    99  		select {
   100  		case <-ctx.Done():
   101  			t.Fatal(ctx.Err())
   102  		case ev := <-evC:
   103  			if ev.Type == EventTypeConn && !ev.Control {
   104  
   105  				// fail on any disconnect
   106  				if !ev.Conn.Up {
   107  					t.Fatalf("unexpected disconnect: %v -> %v", ev.Conn.One, ev.Conn.Other)
   108  				}
   109  				checkIds[ev.Conn.One] = append(checkIds[ev.Conn.One], ev.Conn.Other)
   110  				checkIds[ev.Conn.Other] = append(checkIds[ev.Conn.Other], ev.Conn.One)
   111  				connEventCount--
   112  				log.Debug("ev", "count", connEventCount)
   113  				if connEventCount == 0 {
   114  					break OUTER
   115  				}
   116  			}
   117  		}
   118  	}
   119  
   120  	// create snapshot of current network
   121  	snap, err := network.Snapshot()
   122  	if err != nil {
   123  		t.Fatal(err)
   124  	}
   125  	j, err := json.Marshal(snap)
   126  	if err != nil {
   127  		t.Fatal(err)
   128  	}
   129  	log.Debug("snapshot taken", "nodes", len(snap.Nodes), "conns", len(snap.Conns), "json", string(j))
   130  
   131  	// verify that the snap element numbers check out
   132  	if len(checkIds) != len(snap.Conns) || len(checkIds) != len(snap.Nodes) {
   133  		t.Fatalf("snapshot wrong node,conn counts %d,%d != %d", len(snap.Nodes), len(snap.Conns), len(checkIds))
   134  	}
   135  
   136  	// shut down sim network
   137  	runningOne = false
   138  	sub.Unsubscribe()
   139  	network.Shutdown()
   140  
   141  	// check that we have all the expected connections in the snapshot
   142  	for nodid, nodConns := range checkIds {
   143  		for _, nodConn := range nodConns {
   144  			var match bool
   145  			for _, snapConn := range snap.Conns {
   146  				if snapConn.One == nodid && snapConn.Other == nodConn {
   147  					match = true
   148  					break
   149  				} else if snapConn.Other == nodid && snapConn.One == nodConn {
   150  					match = true
   151  					break
   152  				}
   153  			}
   154  			if !match {
   155  				t.Fatalf("snapshot missing conn %v -> %v", nodid, nodConn)
   156  			}
   157  		}
   158  	}
   159  	log.Info("snapshot checked")
   160  
   161  	// PART II
   162  	// load snapshot and verify that exactly same connections are formed
   163  
   164  	adapter = adapters.NewSimAdapter(adapters.Services{
   165  		"noopwoop": func(ctx *adapters.ServiceContext) (node.Service, error) {
   166  			return NewNoopService(nil), nil
   167  		},
   168  	})
   169  	network = NewNetwork(adapter, &NetworkConfig{
   170  		DefaultService: "noopwoop",
   171  	})
   172  	defer func() {
   173  		network.Shutdown()
   174  	}()
   175  
   176  	// subscribe to peer events
   177  	// every node up and conn up event will generate one additional control event
   178  	// therefore multiply the count by two
   179  	evC = make(chan *Event, (len(snap.Conns)*2)+(len(snap.Nodes)*2))
   180  	sub = network.Events().Subscribe(evC)
   181  	defer sub.Unsubscribe()
   182  
   183  	// load the snapshot
   184  	// spawn separate thread to avoid deadlock in the event listeners
   185  	err = network.Load(snap)
   186  	if err != nil {
   187  		t.Fatal(err)
   188  	}
   189  
   190  	// collect connection events up to expected number
   191  	ctx, cancel = context.WithTimeout(context.TODO(), time.Second*3)
   192  	defer cancel()
   193  
   194  	connEventCount = nodeCount
   195  
   196  OuterTwo:
   197  	for {
   198  		select {
   199  		case <-ctx.Done():
   200  			t.Fatal(ctx.Err())
   201  		case ev := <-evC:
   202  			if ev.Type == EventTypeConn && !ev.Control {
   203  
   204  				// fail on any disconnect
   205  				if !ev.Conn.Up {
   206  					t.Fatalf("unexpected disconnect: %v -> %v", ev.Conn.One, ev.Conn.Other)
   207  				}
   208  				log.Debug("conn", "on", ev.Conn.One, "other", ev.Conn.Other)
   209  				checkIds[ev.Conn.One] = append(checkIds[ev.Conn.One], ev.Conn.Other)
   210  				checkIds[ev.Conn.Other] = append(checkIds[ev.Conn.Other], ev.Conn.One)
   211  				connEventCount--
   212  				log.Debug("ev", "count", connEventCount)
   213  				if connEventCount == 0 {
   214  					break OuterTwo
   215  				}
   216  			}
   217  		}
   218  	}
   219  
   220  	// check that we have all expected connections in the network
   221  	for _, snapConn := range snap.Conns {
   222  		var match bool
   223  		for nodid, nodConns := range checkIds {
   224  			for _, nodConn := range nodConns {
   225  				if snapConn.One == nodid && snapConn.Other == nodConn {
   226  					match = true
   227  					break
   228  				} else if snapConn.Other == nodid && snapConn.One == nodConn {
   229  					match = true
   230  					break
   231  				}
   232  			}
   233  		}
   234  		if !match {
   235  			t.Fatalf("network missing conn %v -> %v", snapConn.One, snapConn.Other)
   236  		}
   237  	}
   238  
   239  	// verify that network didn't generate any other additional connection events after the ones we have collected within a reasonable period of time
   240  	ctx, cancel = context.WithTimeout(context.TODO(), time.Second)
   241  	defer cancel()
   242  	select {
   243  	case <-ctx.Done():
   244  	case ev := <-evC:
   245  		if ev.Type == EventTypeConn {
   246  			t.Fatalf("Superfluous conn found %v -> %v", ev.Conn.One, ev.Conn.Other)
   247  		}
   248  	}
   249  
   250  	// This test validates if all connections from the snapshot
   251  	// are created in the network.
   252  	t.Run("conns after load", func(t *testing.T) {
   253  		// Create new network.
   254  		n := NewNetwork(
   255  			adapters.NewSimAdapter(adapters.Services{
   256  				"noopwoop": func(ctx *adapters.ServiceContext) (node.Service, error) {
   257  					return NewNoopService(nil), nil
   258  				},
   259  			}),
   260  			&NetworkConfig{
   261  				DefaultService: "noopwoop",
   262  			},
   263  		)
   264  		defer n.Shutdown()
   265  
   266  		// Load the same snapshot.
   267  		err := n.Load(snap)
   268  		if err != nil {
   269  			t.Fatal(err)
   270  		}
   271  
   272  		// Check every connection from the snapshot
   273  		// if it is in the network, too.
   274  		for _, c := range snap.Conns {
   275  			if n.GetConn(c.One, c.Other) == nil {
   276  				t.Errorf("missing connection: %s -> %s", c.One, c.Other)
   277  			}
   278  		}
   279  	})
   280  }
   281  
   282  // TestNetworkSimulation creates a multi-node simulation network with each node
   283  // connected in a ring topology, checks that all nodes successfully handshake
   284  // with each other and that a snapshot fully represents the desired topology
   285  func TestNetworkSimulation(t *testing.T) {
   286  	// create simulation network with 20 testService nodes
   287  	adapter := adapters.NewSimAdapter(adapters.Services{
   288  		"test": newTestService,
   289  	})
   290  	network := NewNetwork(adapter, &NetworkConfig{
   291  		DefaultService: "test",
   292  	})
   293  	defer network.Shutdown()
   294  	nodeCount := 20
   295  	ids := make([]enode.ID, nodeCount)
   296  	for i := 0; i < nodeCount; i++ {
   297  		conf := adapters.RandomNodeConfig()
   298  		node, err := network.NewNodeWithConfig(conf)
   299  		if err != nil {
   300  			t.Fatalf("error creating node: %s", err)
   301  		}
   302  		if err := network.Start(node.ID()); err != nil {
   303  			t.Fatalf("error starting node: %s", err)
   304  		}
   305  		ids[i] = node.ID()
   306  	}
   307  
   308  	// perform a check which connects the nodes in a ring (so each node is
   309  	// connected to exactly two peers) and then checks that all nodes
   310  	// performed two handshakes by checking their peerCount
   311  	action := func(_ context.Context) error {
   312  		for i, id := range ids {
   313  			peerID := ids[(i+1)%len(ids)]
   314  			if err := network.Connect(id, peerID); err != nil {
   315  				return err
   316  			}
   317  		}
   318  		return nil
   319  	}
   320  	check := func(ctx context.Context, id enode.ID) (bool, error) {
   321  		// check we haven't run out of time
   322  		select {
   323  		case <-ctx.Done():
   324  			return false, ctx.Err()
   325  		default:
   326  		}
   327  
   328  		// get the node
   329  		node := network.GetNode(id)
   330  		if node == nil {
   331  			return false, fmt.Errorf("unknown node: %s", id)
   332  		}
   333  
   334  		// check it has exactly two peers
   335  		client, err := node.Client()
   336  		if err != nil {
   337  			return false, err
   338  		}
   339  		var peerCount int64
   340  		if err := client.CallContext(ctx, &peerCount, "test_peerCount"); err != nil {
   341  			return false, err
   342  		}
   343  		switch {
   344  		case peerCount < 2:
   345  			return false, nil
   346  		case peerCount == 2:
   347  			return true, nil
   348  		default:
   349  			return false, fmt.Errorf("unexpected peerCount: %d", peerCount)
   350  		}
   351  	}
   352  
   353  	timeout := 30 * time.Second
   354  	ctx, cancel := context.WithTimeout(context.Background(), timeout)
   355  	defer cancel()
   356  
   357  	// trigger a check every 100ms
   358  	trigger := make(chan enode.ID)
   359  	go triggerChecks(ctx, ids, trigger, 100*time.Millisecond)
   360  
   361  	result := NewSimulation(network).Run(ctx, &Step{
   362  		Action:  action,
   363  		Trigger: trigger,
   364  		Expect: &Expectation{
   365  			Nodes: ids,
   366  			Check: check,
   367  		},
   368  	})
   369  	if result.Error != nil {
   370  		t.Fatalf("simulation failed: %s", result.Error)
   371  	}
   372  
   373  	// take a network snapshot and check it contains the correct topology
   374  	snap, err := network.Snapshot()
   375  	if err != nil {
   376  		t.Fatal(err)
   377  	}
   378  	if len(snap.Nodes) != nodeCount {
   379  		t.Fatalf("expected snapshot to contain %d nodes, got %d", nodeCount, len(snap.Nodes))
   380  	}
   381  	if len(snap.Conns) != nodeCount {
   382  		t.Fatalf("expected snapshot to contain %d connections, got %d", nodeCount, len(snap.Conns))
   383  	}
   384  	for i, id := range ids {
   385  		conn := snap.Conns[i]
   386  		if conn.One != id {
   387  			t.Fatalf("expected conn[%d].One to be %s, got %s", i, id, conn.One)
   388  		}
   389  		peerID := ids[(i+1)%len(ids)]
   390  		if conn.Other != peerID {
   391  			t.Fatalf("expected conn[%d].Other to be %s, got %s", i, peerID, conn.Other)
   392  		}
   393  	}
   394  }
   395  
   396  func triggerChecks(ctx context.Context, ids []enode.ID, trigger chan enode.ID, interval time.Duration) {
   397  	tick := time.NewTicker(interval)
   398  	defer tick.Stop()
   399  	for {
   400  		select {
   401  		case <-tick.C:
   402  			for _, id := range ids {
   403  				select {
   404  				case trigger <- id:
   405  				case <-ctx.Done():
   406  					return
   407  				}
   408  			}
   409  		case <-ctx.Done():
   410  			return
   411  		}
   412  	}
   413  }
   414  
   415  // \todo: refactor to implement shapshots
   416  // and connect configuration methods once these are moved from
   417  // swarm/network/simulations/connect.go
   418  func BenchmarkMinimalService(b *testing.B) {
   419  	b.Run("ring/32", benchmarkMinimalServiceTmp)
   420  }
   421  
   422  func benchmarkMinimalServiceTmp(b *testing.B) {
   423  
   424  	// stop timer to discard setup time pollution
   425  	args := strings.Split(b.Name(), "/")
   426  	nodeCount, err := strconv.ParseInt(args[2], 10, 16)
   427  	if err != nil {
   428  		b.Fatal(err)
   429  	}
   430  
   431  	for i := 0; i < b.N; i++ {
   432  		// this is a minimal service, whose protocol will close a channel upon run of protocol
   433  		// making it possible to bench the time it takes for the service to start and protocol actually to be run
   434  		protoCMap := make(map[enode.ID]map[enode.ID]chan struct{})
   435  		adapter := adapters.NewSimAdapter(adapters.Services{
   436  			"noopwoop": func(ctx *adapters.ServiceContext) (node.Service, error) {
   437  				protoCMap[ctx.Config.ID] = make(map[enode.ID]chan struct{})
   438  				svc := NewNoopService(protoCMap[ctx.Config.ID])
   439  				return svc, nil
   440  			},
   441  		})
   442  
   443  		// create network
   444  		network := NewNetwork(adapter, &NetworkConfig{
   445  			DefaultService: "noopwoop",
   446  		})
   447  		defer network.Shutdown()
   448  
   449  		// create and start nodes
   450  		ids := make([]enode.ID, nodeCount)
   451  		for i := 0; i < int(nodeCount); i++ {
   452  			conf := adapters.RandomNodeConfig()
   453  			node, err := network.NewNodeWithConfig(conf)
   454  			if err != nil {
   455  				b.Fatalf("error creating node: %s", err)
   456  			}
   457  			if err := network.Start(node.ID()); err != nil {
   458  				b.Fatalf("error starting node: %s", err)
   459  			}
   460  			ids[i] = node.ID()
   461  		}
   462  
   463  		// ready, set, go
   464  		b.ResetTimer()
   465  
   466  		// connect nodes in a ring
   467  		for i, id := range ids {
   468  			peerID := ids[(i+1)%len(ids)]
   469  			if err := network.Connect(id, peerID); err != nil {
   470  				b.Fatal(err)
   471  			}
   472  		}
   473  
   474  		// wait for all protocols to signal to close down
   475  		ctx, cancel := context.WithTimeout(context.TODO(), time.Second)
   476  		defer cancel()
   477  		for nodid, peers := range protoCMap {
   478  			for peerid, peerC := range peers {
   479  				log.Debug("getting ", "node", nodid, "peer", peerid)
   480  				select {
   481  				case <-ctx.Done():
   482  					b.Fatal(ctx.Err())
   483  				case <-peerC:
   484  				}
   485  			}
   486  		}
   487  	}
   488  }
   489  
   490  func TestNode_UnmarshalJSON(t *testing.T) {
   491  	t.Run(
   492  		"test unmarshal of Node up field",
   493  		func(t *testing.T) {
   494  			runNodeUnmarshalJSON(t, casesNodeUnmarshalJSONUpField())
   495  		},
   496  	)
   497  	t.Run(
   498  		"test unmarshal of Node Config field",
   499  		func(t *testing.T) {
   500  			runNodeUnmarshalJSON(t, casesNodeUnmarshalJSONConfigField())
   501  		},
   502  	)
   503  }
   504  
   505  func runNodeUnmarshalJSON(t *testing.T, tests []nodeUnmarshalTestCase) {
   506  	t.Helper()
   507  	for _, tt := range tests {
   508  		t.Run(tt.name, func(t *testing.T) {
   509  			var got Node
   510  			if err := got.UnmarshalJSON([]byte(tt.marshaled)); err != nil {
   511  				expectErrorMessageToContain(t, err, tt.wantErr)
   512  			}
   513  			expectNodeEquality(t, got, tt.want)
   514  		})
   515  	}
   516  }
   517  
   518  type nodeUnmarshalTestCase struct {
   519  	name      string
   520  	marshaled string
   521  	want      Node
   522  	wantErr   string
   523  }
   524  
   525  func expectErrorMessageToContain(t *testing.T, got error, want string) {
   526  	t.Helper()
   527  	if got == nil && want == "" {
   528  		return
   529  	}
   530  
   531  	if got == nil && want != "" {
   532  		t.Errorf("error was expected, got: nil, want: %v", want)
   533  		return
   534  	}
   535  
   536  	if !strings.Contains(got.Error(), want) {
   537  		t.Errorf(
   538  			"unexpected error message, got  %v, want: %v",
   539  			want,
   540  			got,
   541  		)
   542  	}
   543  }
   544  
   545  func expectNodeEquality(t *testing.T, got Node, want Node) {
   546  	t.Helper()
   547  	if !reflect.DeepEqual(got, want) {
   548  		t.Errorf("Node.UnmarshalJSON() = %v, want %v", got, want)
   549  	}
   550  }
   551  
   552  func casesNodeUnmarshalJSONUpField() []nodeUnmarshalTestCase {
   553  	return []nodeUnmarshalTestCase{
   554  		{
   555  			name:      "empty json",
   556  			marshaled: "{}",
   557  			want: Node{
   558  				up: false,
   559  			},
   560  		},
   561  		{
   562  			name:      "a stopped node",
   563  			marshaled: "{\"up\": false}",
   564  			want: Node{
   565  				up: false,
   566  			},
   567  		},
   568  		{
   569  			name:      "a running node",
   570  			marshaled: "{\"up\": true}",
   571  			want: Node{
   572  				up: true,
   573  			},
   574  		},
   575  		{
   576  			name:      "invalid JSON value on valid key",
   577  			marshaled: "{\"up\": foo}",
   578  			wantErr:   "invalid character",
   579  		},
   580  		{
   581  			name:      "invalid JSON key and value",
   582  			marshaled: "{foo: bar}",
   583  			wantErr:   "invalid character",
   584  		},
   585  		{
   586  			name:      "bool value expected but got something else (string)",
   587  			marshaled: "{\"up\": \"true\"}",
   588  			wantErr:   "cannot unmarshal string into Go struct",
   589  		},
   590  	}
   591  }
   592  
   593  func casesNodeUnmarshalJSONConfigField() []nodeUnmarshalTestCase {
   594  	// Don't do a big fuss around testing, as adapters.NodeConfig should
   595  	// handle it's own serialization. Just do a sanity check.
   596  	return []nodeUnmarshalTestCase{
   597  		{
   598  			name:      "Config field is omitted",
   599  			marshaled: "{}",
   600  			want: Node{
   601  				Config: nil,
   602  			},
   603  		},
   604  		{
   605  			name:      "Config field is nil",
   606  			marshaled: "{\"config\": nil}",
   607  			want: Node{
   608  				Config: nil,
   609  			},
   610  		},
   611  		{
   612  			name:      "a non default Config field",
   613  			marshaled: "{\"config\":{\"name\":\"node_ecdd0\",\"port\":44665}}",
   614  			want: Node{
   615  				Config: &adapters.NodeConfig{
   616  					Name: "node_ecdd0",
   617  					Port: 44665,
   618  				},
   619  			},
   620  		},
   621  	}
   622  }