github.com/luckypickle/go-ethereum-vet@v1.14.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  	"fmt"
    22  	"testing"
    23  	"time"
    24  
    25  	"github.com/luckypickle/go-ethereum-vet/p2p/discover"
    26  	"github.com/luckypickle/go-ethereum-vet/p2p/simulations/adapters"
    27  )
    28  
    29  // TestNetworkSimulation creates a multi-node simulation network with each node
    30  // connected in a ring topology, checks that all nodes successfully handshake
    31  // with each other and that a snapshot fully represents the desired topology
    32  func TestNetworkSimulation(t *testing.T) {
    33  	// create simulation network with 20 testService nodes
    34  	adapter := adapters.NewSimAdapter(adapters.Services{
    35  		"test": newTestService,
    36  	})
    37  	network := NewNetwork(adapter, &NetworkConfig{
    38  		DefaultService: "test",
    39  	})
    40  	defer network.Shutdown()
    41  	nodeCount := 20
    42  	ids := make([]discover.NodeID, nodeCount)
    43  	for i := 0; i < nodeCount; i++ {
    44  		conf := adapters.RandomNodeConfig()
    45  		node, err := network.NewNodeWithConfig(conf)
    46  		if err != nil {
    47  			t.Fatalf("error creating node: %s", err)
    48  		}
    49  		if err := network.Start(node.ID()); err != nil {
    50  			t.Fatalf("error starting node: %s", err)
    51  		}
    52  		ids[i] = node.ID()
    53  	}
    54  
    55  	// perform a check which connects the nodes in a ring (so each node is
    56  	// connected to exactly two peers) and then checks that all nodes
    57  	// performed two handshakes by checking their peerCount
    58  	action := func(_ context.Context) error {
    59  		for i, id := range ids {
    60  			peerID := ids[(i+1)%len(ids)]
    61  			if err := network.Connect(id, peerID); err != nil {
    62  				return err
    63  			}
    64  		}
    65  		return nil
    66  	}
    67  	check := func(ctx context.Context, id discover.NodeID) (bool, error) {
    68  		// check we haven't run out of time
    69  		select {
    70  		case <-ctx.Done():
    71  			return false, ctx.Err()
    72  		default:
    73  		}
    74  
    75  		// get the node
    76  		node := network.GetNode(id)
    77  		if node == nil {
    78  			return false, fmt.Errorf("unknown node: %s", id)
    79  		}
    80  
    81  		// check it has exactly two peers
    82  		client, err := node.Client()
    83  		if err != nil {
    84  			return false, err
    85  		}
    86  		var peerCount int64
    87  		if err := client.CallContext(ctx, &peerCount, "test_peerCount"); err != nil {
    88  			return false, err
    89  		}
    90  		switch {
    91  		case peerCount < 2:
    92  			return false, nil
    93  		case peerCount == 2:
    94  			return true, nil
    95  		default:
    96  			return false, fmt.Errorf("unexpected peerCount: %d", peerCount)
    97  		}
    98  	}
    99  
   100  	timeout := 30 * time.Second
   101  	ctx, cancel := context.WithTimeout(context.Background(), timeout)
   102  	defer cancel()
   103  
   104  	// trigger a check every 100ms
   105  	trigger := make(chan discover.NodeID)
   106  	go triggerChecks(ctx, ids, trigger, 100*time.Millisecond)
   107  
   108  	result := NewSimulation(network).Run(ctx, &Step{
   109  		Action:  action,
   110  		Trigger: trigger,
   111  		Expect: &Expectation{
   112  			Nodes: ids,
   113  			Check: check,
   114  		},
   115  	})
   116  	if result.Error != nil {
   117  		t.Fatalf("simulation failed: %s", result.Error)
   118  	}
   119  
   120  	// take a network snapshot and check it contains the correct topology
   121  	snap, err := network.Snapshot()
   122  	if err != nil {
   123  		t.Fatal(err)
   124  	}
   125  	if len(snap.Nodes) != nodeCount {
   126  		t.Fatalf("expected snapshot to contain %d nodes, got %d", nodeCount, len(snap.Nodes))
   127  	}
   128  	if len(snap.Conns) != nodeCount {
   129  		t.Fatalf("expected snapshot to contain %d connections, got %d", nodeCount, len(snap.Conns))
   130  	}
   131  	for i, id := range ids {
   132  		conn := snap.Conns[i]
   133  		if conn.One != id {
   134  			t.Fatalf("expected conn[%d].One to be %s, got %s", i, id, conn.One)
   135  		}
   136  		peerID := ids[(i+1)%len(ids)]
   137  		if conn.Other != peerID {
   138  			t.Fatalf("expected conn[%d].Other to be %s, got %s", i, peerID, conn.Other)
   139  		}
   140  	}
   141  }
   142  
   143  func triggerChecks(ctx context.Context, ids []discover.NodeID, trigger chan discover.NodeID, interval time.Duration) {
   144  	tick := time.NewTicker(interval)
   145  	defer tick.Stop()
   146  	for {
   147  		select {
   148  		case <-tick.C:
   149  			for _, id := range ids {
   150  				select {
   151  				case trigger <- id:
   152  				case <-ctx.Done():
   153  					return
   154  				}
   155  			}
   156  		case <-ctx.Done():
   157  			return
   158  		}
   159  	}
   160  }