Skip to content

Commit fd777bb

Browse files
holisticodefjl
authored andcommitted
p2p/simulations: add mocker functionality (ethereum#15207)
This commit adds mocker functionality to p2p/simulations. A mocker allows to starting/stopping of nodes via the HTTP API.
1 parent 3da1bf8 commit fd777bb

File tree

5 files changed

+480
-2
lines changed

5 files changed

+480
-2
lines changed

p2p/simulations/adapters/state.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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+
package adapters
17+
18+
type SimStateStore struct {
19+
m map[string][]byte
20+
}
21+
22+
func (self *SimStateStore) Load(s string) ([]byte, error) {
23+
return self.m[s], nil
24+
}
25+
26+
func (self *SimStateStore) Save(s string, data []byte) error {
27+
self.m[s] = data
28+
return nil
29+
}
30+
31+
func NewSimStateStore() *SimStateStore {
32+
return &SimStateStore{
33+
make(map[string][]byte),
34+
}
35+
}

p2p/simulations/http.go

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"net/http"
2828
"strconv"
2929
"strings"
30+
"sync"
3031

3132
"github.com/ethereum/go-ethereum/event"
3233
"github.com/ethereum/go-ethereum/p2p"
@@ -263,8 +264,10 @@ func (c *Client) Send(method, path string, in, out interface{}) error {
263264

264265
// Server is an HTTP server providing an API to manage a simulation network
265266
type Server struct {
266-
router *httprouter.Router
267-
network *Network
267+
router *httprouter.Router
268+
network *Network
269+
mockerStop chan struct{} // when set, stops the current mocker
270+
mockerMtx sync.Mutex // synchronises access to the mockerStop field
268271
}
269272

270273
// NewServer returns a new simulation API server
@@ -278,6 +281,10 @@ func NewServer(network *Network) *Server {
278281
s.GET("/", s.GetNetwork)
279282
s.POST("/start", s.StartNetwork)
280283
s.POST("/stop", s.StopNetwork)
284+
s.POST("/mocker/start", s.StartMocker)
285+
s.POST("/mocker/stop", s.StopMocker)
286+
s.GET("/mocker", s.GetMockers)
287+
s.POST("/reset", s.ResetNetwork)
281288
s.GET("/events", s.StreamNetworkEvents)
282289
s.GET("/snapshot", s.CreateSnapshot)
283290
s.POST("/snapshot", s.LoadSnapshot)
@@ -318,6 +325,59 @@ func (s *Server) StopNetwork(w http.ResponseWriter, req *http.Request) {
318325
w.WriteHeader(http.StatusOK)
319326
}
320327

328+
// StartMocker starts the mocker node simulation
329+
func (s *Server) StartMocker(w http.ResponseWriter, req *http.Request) {
330+
s.mockerMtx.Lock()
331+
defer s.mockerMtx.Unlock()
332+
if s.mockerStop != nil {
333+
http.Error(w, "mocker already running", http.StatusInternalServerError)
334+
return
335+
}
336+
mockerType := req.FormValue("mocker-type")
337+
mockerFn := LookupMocker(mockerType)
338+
if mockerFn == nil {
339+
http.Error(w, fmt.Sprintf("unknown mocker type %q", mockerType), http.StatusBadRequest)
340+
return
341+
}
342+
nodeCount, err := strconv.Atoi(req.FormValue("node-count"))
343+
if err != nil {
344+
http.Error(w, "invalid node-count provided", http.StatusBadRequest)
345+
return
346+
}
347+
s.mockerStop = make(chan struct{})
348+
go mockerFn(s.network, s.mockerStop, nodeCount)
349+
350+
w.WriteHeader(http.StatusOK)
351+
}
352+
353+
// StopMocker stops the mocker node simulation
354+
func (s *Server) StopMocker(w http.ResponseWriter, req *http.Request) {
355+
s.mockerMtx.Lock()
356+
defer s.mockerMtx.Unlock()
357+
if s.mockerStop == nil {
358+
http.Error(w, "stop channel not initialized", http.StatusInternalServerError)
359+
return
360+
}
361+
close(s.mockerStop)
362+
s.mockerStop = nil
363+
364+
w.WriteHeader(http.StatusOK)
365+
}
366+
367+
// GetMockerList returns a list of available mockers
368+
func (s *Server) GetMockers(w http.ResponseWriter, req *http.Request) {
369+
370+
list := GetMockerList()
371+
s.JSON(w, http.StatusOK, list)
372+
}
373+
374+
// ResetNetwork resets all properties of a network to its initial (empty) state
375+
func (s *Server) ResetNetwork(w http.ResponseWriter, req *http.Request) {
376+
s.network.Reset()
377+
378+
w.WriteHeader(http.StatusOK)
379+
}
380+
321381
// StreamNetworkEvents streams network events as a server-sent-events stream
322382
func (s *Server) StreamNetworkEvents(w http.ResponseWriter, req *http.Request) {
323383
events := make(chan *Event)

p2p/simulations/mocker.go

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
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 simulates p2p networks.
18+
// A mocker simulates starting and stopping real nodes in a network.
19+
package simulations
20+
21+
import (
22+
"fmt"
23+
"math/rand"
24+
"sync"
25+
"time"
26+
27+
"github.com/ethereum/go-ethereum/log"
28+
"github.com/ethereum/go-ethereum/p2p/discover"
29+
)
30+
31+
//a map of mocker names to its function
32+
var mockerList = map[string]func(net *Network, quit chan struct{}, nodeCount int){
33+
"startStop": startStop,
34+
"probabilistic": probabilistic,
35+
"boot": boot,
36+
}
37+
38+
//Lookup a mocker by its name, returns the mockerFn
39+
func LookupMocker(mockerType string) func(net *Network, quit chan struct{}, nodeCount int) {
40+
return mockerList[mockerType]
41+
}
42+
43+
//Get a list of mockers (keys of the map)
44+
//Useful for frontend to build available mocker selection
45+
func GetMockerList() []string {
46+
list := make([]string, 0, len(mockerList))
47+
for k := range mockerList {
48+
list = append(list, k)
49+
}
50+
return list
51+
}
52+
53+
//The boot mockerFn only connects the node in a ring and doesn't do anything else
54+
func boot(net *Network, quit chan struct{}, nodeCount int) {
55+
_, err := connectNodesInRing(net, nodeCount)
56+
if err != nil {
57+
panic("Could not startup node network for mocker")
58+
}
59+
}
60+
61+
//The startStop mockerFn stops and starts nodes in a defined period (ticker)
62+
func startStop(net *Network, quit chan struct{}, nodeCount int) {
63+
nodes, err := connectNodesInRing(net, nodeCount)
64+
if err != nil {
65+
panic("Could not startup node network for mocker")
66+
}
67+
tick := time.NewTicker(10 * time.Second)
68+
defer tick.Stop()
69+
for {
70+
select {
71+
case <-quit:
72+
log.Info("Terminating simulation loop")
73+
return
74+
case <-tick.C:
75+
id := nodes[rand.Intn(len(nodes))]
76+
log.Info("stopping node", "id", id)
77+
if err := net.Stop(id); err != nil {
78+
log.Error("error stopping node", "id", id, "err", err)
79+
return
80+
}
81+
82+
select {
83+
case <-quit:
84+
log.Info("Terminating simulation loop")
85+
return
86+
case <-time.After(3 * time.Second):
87+
}
88+
89+
log.Debug("starting node", "id", id)
90+
if err := net.Start(id); err != nil {
91+
log.Error("error starting node", "id", id, "err", err)
92+
return
93+
}
94+
}
95+
}
96+
}
97+
98+
//The probabilistic mocker func has a more probabilistic pattern
99+
//(the implementation could probably be improved):
100+
//nodes are connected in a ring, then a varying number of random nodes is selected,
101+
//mocker then stops and starts them in random intervals, and continues the loop
102+
func probabilistic(net *Network, quit chan struct{}, nodeCount int) {
103+
nodes, err := connectNodesInRing(net, nodeCount)
104+
if err != nil {
105+
panic("Could not startup node network for mocker")
106+
}
107+
for {
108+
select {
109+
case <-quit:
110+
log.Info("Terminating simulation loop")
111+
return
112+
default:
113+
}
114+
var lowid, highid int
115+
var wg sync.WaitGroup
116+
randWait := time.Duration(rand.Intn(5000)+1000) * time.Millisecond
117+
rand1 := rand.Intn(nodeCount - 1)
118+
rand2 := rand.Intn(nodeCount - 1)
119+
if rand1 < rand2 {
120+
lowid = rand1
121+
highid = rand2
122+
} else if rand1 > rand2 {
123+
highid = rand1
124+
lowid = rand2
125+
} else {
126+
if rand1 == 0 {
127+
rand2 = 9
128+
} else if rand1 == 9 {
129+
rand1 = 0
130+
}
131+
lowid = rand1
132+
highid = rand2
133+
}
134+
var steps = highid - lowid
135+
wg.Add(steps)
136+
for i := lowid; i < highid; i++ {
137+
select {
138+
case <-quit:
139+
log.Info("Terminating simulation loop")
140+
return
141+
case <-time.After(randWait):
142+
}
143+
log.Debug(fmt.Sprintf("node %v shutting down", nodes[i]))
144+
err := net.Stop(nodes[i])
145+
if err != nil {
146+
log.Error(fmt.Sprintf("Error stopping node %s", nodes[i]))
147+
wg.Done()
148+
continue
149+
}
150+
go func(id discover.NodeID) {
151+
time.Sleep(randWait)
152+
err := net.Start(id)
153+
if err != nil {
154+
log.Error(fmt.Sprintf("Error starting node %s", id))
155+
}
156+
wg.Done()
157+
}(nodes[i])
158+
}
159+
wg.Wait()
160+
}
161+
162+
}
163+
164+
//connect nodeCount number of nodes in a ring
165+
func connectNodesInRing(net *Network, nodeCount int) ([]discover.NodeID, error) {
166+
ids := make([]discover.NodeID, nodeCount)
167+
for i := 0; i < nodeCount; i++ {
168+
node, err := net.NewNode()
169+
if err != nil {
170+
log.Error("Error creating a node! %s", err)
171+
return nil, err
172+
}
173+
ids[i] = node.ID()
174+
}
175+
176+
for _, id := range ids {
177+
if err := net.Start(id); err != nil {
178+
log.Error("Error starting a node! %s", err)
179+
return nil, err
180+
}
181+
log.Debug(fmt.Sprintf("node %v starting up", id))
182+
}
183+
for i, id := range ids {
184+
peerID := ids[(i+1)%len(ids)]
185+
if err := net.Connect(id, peerID); err != nil {
186+
log.Error("Error connecting a node to a peer! %s", err)
187+
return nil, err
188+
}
189+
}
190+
191+
return ids, nil
192+
}

0 commit comments

Comments
 (0)