github.com/aquanetwork/aquachain@v1.7.8/p2p/simulations/network_test.go (about)

     1  // Copyright 2017 The aquachain Authors
     2  // This file is part of the aquachain library.
     3  //
     4  // The aquachain 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 aquachain 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 aquachain 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  	"gitlab.com/aquachain/aquachain/p2p/discover"
    26  	"gitlab.com/aquachain/aquachain/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  		node, err := network.NewNode()
    45  		if err != nil {
    46  			t.Fatalf("error creating node: %s", err)
    47  		}
    48  		if err := network.Start(node.ID()); err != nil {
    49  			t.Fatalf("error starting node: %s", err)
    50  		}
    51  		ids[i] = node.ID()
    52  	}
    53  
    54  	// perform a check which connects the nodes in a ring (so each node is
    55  	// connected to exactly two peers) and then checks that all nodes
    56  	// performed two handshakes by checking their peerCount
    57  	action := func(_ context.Context) error {
    58  		for i, id := range ids {
    59  			peerID := ids[(i+1)%len(ids)]
    60  			if err := network.Connect(id, peerID); err != nil {
    61  				return err
    62  			}
    63  		}
    64  		return nil
    65  	}
    66  	check := func(ctx context.Context, id discover.NodeID) (bool, error) {
    67  		// check we haven't run out of time
    68  		select {
    69  		case <-ctx.Done():
    70  			return false, ctx.Err()
    71  		default:
    72  		}
    73  
    74  		// get the node
    75  		node := network.GetNode(id)
    76  		if node == nil {
    77  			return false, fmt.Errorf("unknown node: %s", id)
    78  		}
    79  
    80  		// check it has exactly two peers
    81  		client, err := node.Client()
    82  		if err != nil {
    83  			return false, err
    84  		}
    85  		var peerCount int64
    86  		if err := client.CallContext(ctx, &peerCount, "test_peerCount"); err != nil {
    87  			return false, err
    88  		}
    89  		switch {
    90  		case peerCount < 2:
    91  			return false, nil
    92  		case peerCount == 2:
    93  			return true, nil
    94  		default:
    95  			return false, fmt.Errorf("unexpected peerCount: %d", peerCount)
    96  		}
    97  	}
    98  
    99  	timeout := 30 * time.Second
   100  	ctx, cancel := context.WithTimeout(context.Background(), timeout)
   101  	defer cancel()
   102  
   103  	// trigger a check every 100ms
   104  	trigger := make(chan discover.NodeID)
   105  	go triggerChecks(ctx, ids, trigger, 100*time.Millisecond)
   106  
   107  	result := NewSimulation(network).Run(ctx, &Step{
   108  		Action:  action,
   109  		Trigger: trigger,
   110  		Expect: &Expectation{
   111  			Nodes: ids,
   112  			Check: check,
   113  		},
   114  	})
   115  	if result.Error != nil {
   116  		t.Fatalf("simulation failed: %s", result.Error)
   117  	}
   118  
   119  	// take a network snapshot and check it contains the correct topology
   120  	snap, err := network.Snapshot()
   121  	if err != nil {
   122  		t.Fatal(err)
   123  	}
   124  	if len(snap.Nodes) != nodeCount {
   125  		t.Fatalf("expected snapshot to contain %d nodes, got %d", nodeCount, len(snap.Nodes))
   126  	}
   127  	if len(snap.Conns) != nodeCount {
   128  		t.Fatalf("expected snapshot to contain %d connections, got %d", nodeCount, len(snap.Conns))
   129  	}
   130  	for i, id := range ids {
   131  		conn := snap.Conns[i]
   132  		if conn.One != id {
   133  			t.Fatalf("expected conn[%d].One to be %s, got %s", i, id, conn.One)
   134  		}
   135  		peerID := ids[(i+1)%len(ids)]
   136  		if conn.Other != peerID {
   137  			t.Fatalf("expected conn[%d].Other to be %s, got %s", i, peerID, conn.Other)
   138  		}
   139  	}
   140  }
   141  
   142  func triggerChecks(ctx context.Context, ids []discover.NodeID, trigger chan discover.NodeID, interval time.Duration) {
   143  	tick := time.NewTicker(interval)
   144  	defer tick.Stop()
   145  	for {
   146  		select {
   147  		case <-tick.C:
   148  			for _, id := range ids {
   149  				select {
   150  				case trigger <- id:
   151  				case <-ctx.Done():
   152  					return
   153  				}
   154  			}
   155  		case <-ctx.Done():
   156  			return
   157  		}
   158  	}
   159  }