// Copyright 2017 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // The go-ethereum library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . // Package simulations simulates p2p networks. // A mocker simulates starting and stopping real nodes in a network. package simulations import ( "fmt" "math/rand" "sync" "time" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/simulations/adapters" ) // a map of mocker names to its function var mockerList = map[string]func(net *Network, quit chan struct{}, nodeCount int){ "startStop": startStop, "probabilistic": probabilistic, "boot": boot, } // LookupMocker looks a mocker by its name, returns the mockerFn func LookupMocker(mockerType string) func(net *Network, quit chan struct{}, nodeCount int) { return mockerList[mockerType] } // GetMockerList returns a list of mockers (keys of the map) // Useful for frontend to build available mocker selection func GetMockerList() []string { list := make([]string, 0, len(mockerList)) for k := range mockerList { list = append(list, k) } return list } // The boot mockerFn only connects the node in a ring and doesn't do anything else func boot(net *Network, quit chan struct{}, nodeCount int) { _, err := connectNodesInRing(net, nodeCount) if err != nil { panic("Could not startup node network for mocker") } } // The startStop mockerFn stops and starts nodes in a defined period (ticker) func startStop(net *Network, quit chan struct{}, nodeCount int) { nodes, err := connectNodesInRing(net, nodeCount) if err != nil { panic("Could not startup node network for mocker") } var ( tick = time.NewTicker(10 * time.Second) timer = time.NewTimer(3 * time.Second) ) defer tick.Stop() defer timer.Stop() for { select { case <-quit: log.Info("Terminating simulation loop") return case <-tick.C: id := nodes[rand.Intn(len(nodes))] log.Info("stopping node", "id", id) if err := net.Stop(id); err != nil { log.Error("error stopping node", "id", id, "err", err) return } timer.Reset(3 * time.Second) select { case <-quit: log.Info("Terminating simulation loop") return case <-timer.C: } log.Debug("starting node", "id", id) if err := net.Start(id); err != nil { log.Error("error starting node", "id", id, "err", err) return } } } } // The probabilistic mocker func has a more probabilistic pattern // (the implementation could probably be improved): // nodes are connected in a ring, then a varying number of random nodes is selected, // mocker then stops and starts them in random intervals, and continues the loop func probabilistic(net *Network, quit chan struct{}, nodeCount int) { nodes, err := connectNodesInRing(net, nodeCount) if err != nil { select { case <-quit: //error may be due to abortion of mocking; so the quit channel is closed return default: panic("Could not startup node network for mocker") } } for { select { case <-quit: log.Info("Terminating simulation loop") return default: } var lowid, highid int var wg sync.WaitGroup randWait := time.Duration(rand.Intn(5000)+1000) * time.Millisecond rand1 := rand.Intn(nodeCount - 1) rand2 := rand.Intn(nodeCount - 1) if rand1 <= rand2 { lowid = rand1 highid = rand2 } else if rand1 > rand2 { highid = rand1 lowid = rand2 } var steps = highid - lowid wg.Add(steps) for i := lowid; i < highid; i++ { select { case <-quit: log.Info("Terminating simulation loop") return case <-time.After(randWait): } log.Debug(fmt.Sprintf("node %v shutting down", nodes[i])) err := net.Stop(nodes[i]) if err != nil { log.Error("Error stopping node", "node", nodes[i]) wg.Done() continue } go func(id enode.ID) { time.Sleep(randWait) err := net.Start(id) if err != nil { log.Error("Error starting node", "node", id) } wg.Done() }(nodes[i]) } wg.Wait() } } // connect nodeCount number of nodes in a ring func connectNodesInRing(net *Network, nodeCount int) ([]enode.ID, error) { ids := make([]enode.ID, nodeCount) for i := 0; i < nodeCount; i++ { conf := adapters.RandomNodeConfig() node, err := net.NewNodeWithConfig(conf) if err != nil { log.Error("Error creating a node!", "err", err) return nil, err } ids[i] = node.ID() } for _, id := range ids { if err := net.Start(id); err != nil { log.Error("Error starting a node!", "err", err) return nil, err } log.Debug(fmt.Sprintf("node %v starting up", id)) } for i, id := range ids { peerID := ids[(i+1)%len(ids)] if err := net.Connect(id, peerID); err != nil { log.Error("Error connecting a node to a peer!", "err", err) return nil, err } } return ids, nil }