github.com/codingfuture/orig-energi3@v0.8.4/swarm/network/simulations/discovery/discovery_test.go (about)

     1  // Copyright 2018 The Energi Core Authors
     2  // Copyright 2018 The go-ethereum Authors
     3  // This file is part of the Energi Core library.
     4  //
     5  // The Energi Core library is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Lesser General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // The Energi Core library is distributed in the hope that it will be useful,
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    13  // GNU Lesser General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Lesser General Public License
    16  // along with the Energi Core library. If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package discovery
    19  
    20  import (
    21  	"context"
    22  	"flag"
    23  	"fmt"
    24  	"io/ioutil"
    25  	"os"
    26  	"path"
    27  	"strings"
    28  	"testing"
    29  	"time"
    30  
    31  	"github.com/ethereum/go-ethereum/common"
    32  	"github.com/ethereum/go-ethereum/log"
    33  	"github.com/ethereum/go-ethereum/node"
    34  	"github.com/ethereum/go-ethereum/p2p"
    35  	"github.com/ethereum/go-ethereum/p2p/enode"
    36  	"github.com/ethereum/go-ethereum/p2p/simulations"
    37  	"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
    38  	"github.com/ethereum/go-ethereum/swarm/network"
    39  	"github.com/ethereum/go-ethereum/swarm/state"
    40  	colorable "github.com/mattn/go-colorable"
    41  )
    42  
    43  // serviceName is used with the exec adapter so the exec'd binary knows which
    44  // service to execute
    45  const serviceName = "discovery"
    46  const testNeighbourhoodSize = 2
    47  const discoveryPersistenceDatadir = "discovery_persistence_test_store"
    48  
    49  var discoveryPersistencePath = path.Join(os.TempDir(), discoveryPersistenceDatadir)
    50  var discoveryEnabled = true
    51  var persistenceEnabled = false
    52  
    53  var services = adapters.Services{
    54  	serviceName: newService,
    55  }
    56  
    57  func cleanDbStores() error {
    58  	entries, err := ioutil.ReadDir(os.TempDir())
    59  	if err != nil {
    60  		return err
    61  	}
    62  
    63  	for _, f := range entries {
    64  		if strings.HasPrefix(f.Name(), discoveryPersistenceDatadir) {
    65  			os.RemoveAll(path.Join(os.TempDir(), f.Name()))
    66  		}
    67  	}
    68  	return nil
    69  
    70  }
    71  
    72  func getDbStore(nodeID string) (*state.DBStore, error) {
    73  	if _, err := os.Stat(discoveryPersistencePath + "_" + nodeID); os.IsNotExist(err) {
    74  		log.Info(fmt.Sprintf("directory for nodeID %s does not exist. creating...", nodeID))
    75  		ioutil.TempDir("", discoveryPersistencePath+"_"+nodeID)
    76  	}
    77  	log.Info(fmt.Sprintf("opening storage directory for nodeID %s", nodeID))
    78  	store, err := state.NewDBStore(discoveryPersistencePath + "_" + nodeID)
    79  	if err != nil {
    80  		return nil, err
    81  	}
    82  	return store, nil
    83  }
    84  
    85  var (
    86  	nodeCount = flag.Int("nodes", 32, "number of nodes to create (default 32)")
    87  	initCount = flag.Int("conns", 1, "number of originally connected peers	 (default 1)")
    88  	loglevel  = flag.Int("loglevel", 3, "verbosity of logs")
    89  	rawlog    = flag.Bool("rawlog", false, "remove terminal formatting from logs")
    90  )
    91  
    92  func init() {
    93  	testing.Init()
    94  	flag.Parse()
    95  	// register the discovery service which will run as a devp2p
    96  	// protocol when using the exec adapter
    97  	adapters.RegisterServices(services)
    98  
    99  	log.PrintOrigins(true)
   100  	log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(!*rawlog))))
   101  }
   102  
   103  // Benchmarks to test the average time it takes for an N-node ring
   104  // to full a healthy kademlia topology
   105  func BenchmarkDiscovery_8_1(b *testing.B)   { benchmarkDiscovery(b, 8, 1) }
   106  func BenchmarkDiscovery_16_1(b *testing.B)  { benchmarkDiscovery(b, 16, 1) }
   107  func BenchmarkDiscovery_32_1(b *testing.B)  { benchmarkDiscovery(b, 32, 1) }
   108  func BenchmarkDiscovery_64_1(b *testing.B)  { benchmarkDiscovery(b, 64, 1) }
   109  func BenchmarkDiscovery_128_1(b *testing.B) { benchmarkDiscovery(b, 128, 1) }
   110  func BenchmarkDiscovery_256_1(b *testing.B) { benchmarkDiscovery(b, 256, 1) }
   111  
   112  func BenchmarkDiscovery_8_2(b *testing.B)   { benchmarkDiscovery(b, 8, 2) }
   113  func BenchmarkDiscovery_16_2(b *testing.B)  { benchmarkDiscovery(b, 16, 2) }
   114  func BenchmarkDiscovery_32_2(b *testing.B)  { benchmarkDiscovery(b, 32, 2) }
   115  func BenchmarkDiscovery_64_2(b *testing.B)  { benchmarkDiscovery(b, 64, 2) }
   116  func BenchmarkDiscovery_128_2(b *testing.B) { benchmarkDiscovery(b, 128, 2) }
   117  func BenchmarkDiscovery_256_2(b *testing.B) { benchmarkDiscovery(b, 256, 2) }
   118  
   119  func BenchmarkDiscovery_8_4(b *testing.B)   { benchmarkDiscovery(b, 8, 4) }
   120  func BenchmarkDiscovery_16_4(b *testing.B)  { benchmarkDiscovery(b, 16, 4) }
   121  func BenchmarkDiscovery_32_4(b *testing.B)  { benchmarkDiscovery(b, 32, 4) }
   122  func BenchmarkDiscovery_64_4(b *testing.B)  { benchmarkDiscovery(b, 64, 4) }
   123  func BenchmarkDiscovery_128_4(b *testing.B) { benchmarkDiscovery(b, 128, 4) }
   124  func BenchmarkDiscovery_256_4(b *testing.B) { benchmarkDiscovery(b, 256, 4) }
   125  
   126  func TestDiscoverySimulationExecAdapter(t *testing.T) {
   127  	testDiscoverySimulationExecAdapter(t, *nodeCount, *initCount)
   128  }
   129  
   130  func testDiscoverySimulationExecAdapter(t *testing.T, nodes, conns int) {
   131  	baseDir, err := ioutil.TempDir("", "swarm-test")
   132  	if err != nil {
   133  		t.Fatal(err)
   134  	}
   135  	defer os.RemoveAll(baseDir)
   136  	testDiscoverySimulation(t, nodes, conns, adapters.NewExecAdapter(baseDir))
   137  }
   138  
   139  func TestDiscoverySimulationSimAdapter(t *testing.T) {
   140  	testDiscoverySimulationSimAdapter(t, *nodeCount, *initCount)
   141  }
   142  
   143  func TestDiscoveryPersistenceSimulationSimAdapter(t *testing.T) {
   144  	testDiscoveryPersistenceSimulationSimAdapter(t, *nodeCount, *initCount)
   145  }
   146  
   147  func testDiscoveryPersistenceSimulationSimAdapter(t *testing.T, nodes, conns int) {
   148  	testDiscoveryPersistenceSimulation(t, nodes, conns, adapters.NewSimAdapter(services))
   149  }
   150  
   151  func testDiscoverySimulationSimAdapter(t *testing.T, nodes, conns int) {
   152  	testDiscoverySimulation(t, nodes, conns, adapters.NewSimAdapter(services))
   153  }
   154  
   155  func testDiscoverySimulation(t *testing.T, nodes, conns int, adapter adapters.NodeAdapter) {
   156  	startedAt := time.Now()
   157  	result, err := discoverySimulation(nodes, conns, adapter)
   158  	if err != nil {
   159  		t.Fatalf("Setting up simulation failed: %v", err)
   160  	}
   161  	if result.Error != nil {
   162  		t.Fatalf("Simulation failed: %s", result.Error)
   163  	}
   164  	t.Logf("Simulation with %d nodes passed in %s", nodes, result.FinishedAt.Sub(result.StartedAt))
   165  	var min, max time.Duration
   166  	var sum int
   167  	for _, pass := range result.Passes {
   168  		duration := pass.Sub(result.StartedAt)
   169  		if sum == 0 || duration < min {
   170  			min = duration
   171  		}
   172  		if duration > max {
   173  			max = duration
   174  		}
   175  		sum += int(duration.Nanoseconds())
   176  	}
   177  	t.Logf("Min: %s, Max: %s, Average: %s", min, max, time.Duration(sum/len(result.Passes))*time.Nanosecond)
   178  	finishedAt := time.Now()
   179  	t.Logf("Setup: %s, shutdown: %s", result.StartedAt.Sub(startedAt), finishedAt.Sub(result.FinishedAt))
   180  }
   181  
   182  func testDiscoveryPersistenceSimulation(t *testing.T, nodes, conns int, adapter adapters.NodeAdapter) map[int][]byte {
   183  	persistenceEnabled = true
   184  	discoveryEnabled = true
   185  
   186  	result, err := discoveryPersistenceSimulation(nodes, conns, adapter)
   187  
   188  	if err != nil {
   189  		t.Fatalf("Setting up simulation failed: %v", err)
   190  	}
   191  	if result.Error != nil {
   192  		t.Fatalf("Simulation failed: %s", result.Error)
   193  	}
   194  	t.Logf("Simulation with %d nodes passed in %s", nodes, result.FinishedAt.Sub(result.StartedAt))
   195  	// set the discovery and persistence flags again to default so other
   196  	// tests will not be affected
   197  	discoveryEnabled = true
   198  	persistenceEnabled = false
   199  	return nil
   200  }
   201  
   202  func benchmarkDiscovery(b *testing.B, nodes, conns int) {
   203  	for i := 0; i < b.N; i++ {
   204  		result, err := discoverySimulation(nodes, conns, adapters.NewSimAdapter(services))
   205  		if err != nil {
   206  			b.Fatalf("setting up simulation failed: %v", err)
   207  		}
   208  		if result.Error != nil {
   209  			b.Logf("simulation failed: %s", result.Error)
   210  		}
   211  	}
   212  }
   213  
   214  func discoverySimulation(nodes, conns int, adapter adapters.NodeAdapter) (*simulations.StepResult, error) {
   215  	// create network
   216  	net := simulations.NewNetwork(adapter, &simulations.NetworkConfig{
   217  		ID:             "0",
   218  		DefaultService: serviceName,
   219  	})
   220  	defer net.Shutdown()
   221  	trigger := make(chan enode.ID)
   222  	ids := make([]enode.ID, nodes)
   223  	for i := 0; i < nodes; i++ {
   224  		conf := adapters.RandomNodeConfig()
   225  		node, err := net.NewNodeWithConfig(conf)
   226  		if err != nil {
   227  			return nil, fmt.Errorf("error starting node: %s", err)
   228  		}
   229  		if err := net.Start(node.ID()); err != nil {
   230  			return nil, fmt.Errorf("error starting node %s: %s", node.ID().TerminalString(), err)
   231  		}
   232  		if err := triggerChecks(trigger, net, node.ID()); err != nil {
   233  			return nil, fmt.Errorf("error triggering checks for node %s: %s", node.ID().TerminalString(), err)
   234  		}
   235  		ids[i] = node.ID()
   236  	}
   237  
   238  	// run a simulation which connects the 10 nodes in a ring and waits
   239  	// for full peer discovery
   240  	var addrs [][]byte
   241  	action := func(ctx context.Context) error {
   242  		return nil
   243  	}
   244  	for i := range ids {
   245  		// collect the overlay addresses, to
   246  		addrs = append(addrs, ids[i].Bytes())
   247  	}
   248  	err := net.ConnectNodesChain(nil)
   249  	if err != nil {
   250  		return nil, err
   251  	}
   252  	log.Debug(fmt.Sprintf("nodes: %v", len(addrs)))
   253  	// construct the peer pot, so that kademlia health can be checked
   254  	ppmap := network.NewPeerPotMap(network.NewKadParams().NeighbourhoodSize, addrs)
   255  	check := func(ctx context.Context, id enode.ID) (bool, error) {
   256  		select {
   257  		case <-ctx.Done():
   258  			return false, ctx.Err()
   259  		default:
   260  		}
   261  
   262  		node := net.GetNode(id)
   263  		if node == nil {
   264  			return false, fmt.Errorf("unknown node: %s", id)
   265  		}
   266  		client, err := node.Client()
   267  		if err != nil {
   268  			return false, fmt.Errorf("error getting node client: %s", err)
   269  		}
   270  
   271  		healthy := &network.Health{}
   272  		if err := client.Call(&healthy, "hive_getHealthInfo", ppmap[common.Bytes2Hex(id.Bytes())]); err != nil {
   273  			return false, fmt.Errorf("error getting node health: %s", err)
   274  		}
   275  		log.Debug(fmt.Sprintf("node %4s healthy: connected nearest neighbours: %v, know nearest neighbours: %v,\n\n%v", id, healthy.ConnectNN, healthy.KnowNN, healthy.Hive))
   276  		return healthy.KnowNN && healthy.ConnectNN, nil
   277  	}
   278  
   279  	// 64 nodes ~ 1min
   280  	// 128 nodes ~
   281  	timeout := 300 * time.Second
   282  	ctx, cancel := context.WithTimeout(context.Background(), timeout)
   283  	defer cancel()
   284  	result := simulations.NewSimulation(net).Run(ctx, &simulations.Step{
   285  		Action:  action,
   286  		Trigger: trigger,
   287  		Expect: &simulations.Expectation{
   288  			Nodes: ids,
   289  			Check: check,
   290  		},
   291  	})
   292  	if result.Error != nil {
   293  		return result, nil
   294  	}
   295  	return result, nil
   296  }
   297  
   298  func discoveryPersistenceSimulation(nodes, conns int, adapter adapters.NodeAdapter) (*simulations.StepResult, error) {
   299  	cleanDbStores()
   300  	defer cleanDbStores()
   301  
   302  	// create network
   303  	net := simulations.NewNetwork(adapter, &simulations.NetworkConfig{
   304  		ID:             "0",
   305  		DefaultService: serviceName,
   306  	})
   307  	defer net.Shutdown()
   308  	trigger := make(chan enode.ID)
   309  	ids := make([]enode.ID, nodes)
   310  	var addrs [][]byte
   311  
   312  	for i := 0; i < nodes; i++ {
   313  		conf := adapters.RandomNodeConfig()
   314  		node, err := net.NewNodeWithConfig(conf)
   315  		if err != nil {
   316  			panic(err)
   317  		}
   318  		if err != nil {
   319  			return nil, fmt.Errorf("error starting node: %s", err)
   320  		}
   321  		if err := net.Start(node.ID()); err != nil {
   322  			return nil, fmt.Errorf("error starting node %s: %s", node.ID().TerminalString(), err)
   323  		}
   324  		if err := triggerChecks(trigger, net, node.ID()); err != nil {
   325  			return nil, fmt.Errorf("error triggering checks for node %s: %s", node.ID().TerminalString(), err)
   326  		}
   327  		// TODO we shouldn't be equating underaddr and overaddr like this, as they are not the same in production
   328  		ids[i] = node.ID()
   329  		a := ids[i].Bytes()
   330  
   331  		addrs = append(addrs, a)
   332  	}
   333  
   334  	// run a simulation which connects the 10 nodes in a ring and waits
   335  	// for full peer discovery
   336  
   337  	var restartTime time.Time
   338  
   339  	action := func(ctx context.Context) error {
   340  		ticker := time.NewTicker(500 * time.Millisecond)
   341  
   342  		for range ticker.C {
   343  			isHealthy := true
   344  			for _, id := range ids {
   345  				//call Healthy RPC
   346  				node := net.GetNode(id)
   347  				if node == nil {
   348  					return fmt.Errorf("unknown node: %s", id)
   349  				}
   350  				client, err := node.Client()
   351  				if err != nil {
   352  					return fmt.Errorf("error getting node client: %s", err)
   353  				}
   354  				healthy := &network.Health{}
   355  				addr := id.String()
   356  				ppmap := network.NewPeerPotMap(network.NewKadParams().NeighbourhoodSize, addrs)
   357  				if err := client.Call(&healthy, "hive_getHealthInfo", ppmap[common.Bytes2Hex(id.Bytes())]); err != nil {
   358  					return fmt.Errorf("error getting node health: %s", err)
   359  				}
   360  
   361  				log.Info(fmt.Sprintf("NODE: %s, IS HEALTHY: %t", addr, healthy.ConnectNN && healthy.KnowNN && healthy.CountKnowNN > 0))
   362  				var nodeStr string
   363  				if err := client.Call(&nodeStr, "hive_string"); err != nil {
   364  					return fmt.Errorf("error getting node string %s", err)
   365  				}
   366  				log.Info(nodeStr)
   367  				if !healthy.ConnectNN || healthy.CountKnowNN == 0 {
   368  					isHealthy = false
   369  					break
   370  				}
   371  			}
   372  			if isHealthy {
   373  				break
   374  			}
   375  		}
   376  		ticker.Stop()
   377  
   378  		log.Info("reached healthy kademlia. starting to shutdown nodes.")
   379  		shutdownStarted := time.Now()
   380  		// stop all ids, then start them again
   381  		for _, id := range ids {
   382  			node := net.GetNode(id)
   383  
   384  			if err := net.Stop(node.ID()); err != nil {
   385  				return fmt.Errorf("error stopping node %s: %s", node.ID().TerminalString(), err)
   386  			}
   387  		}
   388  		log.Info(fmt.Sprintf("shutting down nodes took: %s", time.Since(shutdownStarted)))
   389  		persistenceEnabled = true
   390  		discoveryEnabled = false
   391  		restartTime = time.Now()
   392  		for _, id := range ids {
   393  			node := net.GetNode(id)
   394  			if err := net.Start(node.ID()); err != nil {
   395  				return fmt.Errorf("error starting node %s: %s", node.ID().TerminalString(), err)
   396  			}
   397  			if err := triggerChecks(trigger, net, node.ID()); err != nil {
   398  				return fmt.Errorf("error triggering checks for node %s: %s", node.ID().TerminalString(), err)
   399  			}
   400  		}
   401  
   402  		log.Info(fmt.Sprintf("restarting nodes took: %s", time.Since(restartTime)))
   403  
   404  		return nil
   405  	}
   406  	net.ConnectNodesChain(nil)
   407  	log.Debug(fmt.Sprintf("nodes: %v", len(addrs)))
   408  	// construct the peer pot, so that kademlia health can be checked
   409  	check := func(ctx context.Context, id enode.ID) (bool, error) {
   410  		select {
   411  		case <-ctx.Done():
   412  			return false, ctx.Err()
   413  		default:
   414  		}
   415  
   416  		node := net.GetNode(id)
   417  		if node == nil {
   418  			return false, fmt.Errorf("unknown node: %s", id)
   419  		}
   420  		client, err := node.Client()
   421  		if err != nil {
   422  			return false, fmt.Errorf("error getting node client: %s", err)
   423  		}
   424  		healthy := &network.Health{}
   425  		ppmap := network.NewPeerPotMap(network.NewKadParams().NeighbourhoodSize, addrs)
   426  
   427  		if err := client.Call(&healthy, "hive_getHealthInfo", ppmap[common.Bytes2Hex(id.Bytes())]); err != nil {
   428  			return false, fmt.Errorf("error getting node health: %s", err)
   429  		}
   430  		log.Info(fmt.Sprintf("node %4s healthy: got nearest neighbours: %v, know nearest neighbours: %v", id, healthy.ConnectNN, healthy.KnowNN))
   431  
   432  		return healthy.KnowNN && healthy.ConnectNN, nil
   433  	}
   434  
   435  	// 64 nodes ~ 1min
   436  	// 128 nodes ~
   437  	timeout := 300 * time.Second
   438  	ctx, cancel := context.WithTimeout(context.Background(), timeout)
   439  	defer cancel()
   440  	result := simulations.NewSimulation(net).Run(ctx, &simulations.Step{
   441  		Action:  action,
   442  		Trigger: trigger,
   443  		Expect: &simulations.Expectation{
   444  			Nodes: ids,
   445  			Check: check,
   446  		},
   447  	})
   448  	if result.Error != nil {
   449  		return result, nil
   450  	}
   451  
   452  	return result, nil
   453  }
   454  
   455  // triggerChecks triggers a simulation step check whenever a peer is added or
   456  // removed from the given node, and also every second to avoid a race between
   457  // peer events and kademlia becoming healthy
   458  func triggerChecks(trigger chan enode.ID, net *simulations.Network, id enode.ID) error {
   459  	node := net.GetNode(id)
   460  	if node == nil {
   461  		return fmt.Errorf("unknown node: %s", id)
   462  	}
   463  	client, err := node.Client()
   464  	if err != nil {
   465  		return err
   466  	}
   467  	events := make(chan *p2p.PeerEvent)
   468  	sub, err := client.Subscribe(context.Background(), "admin", events, "peerEvents")
   469  	if err != nil {
   470  		return fmt.Errorf("error getting peer events for node %v: %s", id, err)
   471  	}
   472  	go func() {
   473  		defer sub.Unsubscribe()
   474  
   475  		tick := time.NewTicker(time.Second)
   476  		defer tick.Stop()
   477  
   478  		for {
   479  			select {
   480  			case <-events:
   481  				trigger <- id
   482  			case <-tick.C:
   483  				trigger <- id
   484  			case err := <-sub.Err():
   485  				if err != nil {
   486  					log.Error(fmt.Sprintf("error getting peer events for node %v", id), "err", err)
   487  				}
   488  				return
   489  			}
   490  		}
   491  	}()
   492  	return nil
   493  }
   494  
   495  func newService(ctx *adapters.ServiceContext) (node.Service, error) {
   496  	addr := network.NewAddr(ctx.Config.Node())
   497  
   498  	kp := network.NewKadParams()
   499  	kp.NeighbourhoodSize = testNeighbourhoodSize
   500  
   501  	if ctx.Config.Reachable != nil {
   502  		kp.Reachable = func(o *network.BzzAddr) bool {
   503  			return ctx.Config.Reachable(o.ID())
   504  		}
   505  	}
   506  	kad := network.NewKademlia(addr.Over(), kp)
   507  	hp := network.NewHiveParams()
   508  	hp.KeepAliveInterval = time.Duration(200) * time.Millisecond
   509  	hp.Discovery = discoveryEnabled
   510  
   511  	log.Info(fmt.Sprintf("discovery for nodeID %s is %t", ctx.Config.ID.String(), hp.Discovery))
   512  
   513  	config := &network.BzzConfig{
   514  		OverlayAddr:  addr.Over(),
   515  		UnderlayAddr: addr.Under(),
   516  		HiveParams:   hp,
   517  	}
   518  
   519  	if persistenceEnabled {
   520  		log.Info(fmt.Sprintf("persistence enabled for nodeID %s", ctx.Config.ID.String()))
   521  		store, err := getDbStore(ctx.Config.ID.String())
   522  		if err != nil {
   523  			return nil, err
   524  		}
   525  		return network.NewBzz(config, kad, store, nil, nil), nil
   526  	}
   527  
   528  	return network.NewBzz(config, kad, nil, nil, nil), nil
   529  }