192 lines
5.3 KiB
Go
192 lines
5.3 KiB
Go
// 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 <http://www.gnu.org/licenses/>.
|
|
|
|
// 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")
|
|
}
|
|
tick := time.NewTicker(10 * time.Second)
|
|
defer tick.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
|
|
}
|
|
|
|
select {
|
|
case <-quit:
|
|
log.Info("Terminating simulation loop")
|
|
return
|
|
case <-time.After(3 * time.Second):
|
|
}
|
|
|
|
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
|
|
}
|