aboutsummaryrefslogtreecommitdiffstats
path: root/p2p/simulations/examples
diff options
context:
space:
mode:
authorLewis Marshall <lewis@lmars.net>2017-09-25 16:08:07 +0800
committerFelix Lange <fjl@users.noreply.github.com>2017-09-25 16:08:07 +0800
commit9feec51e2dd754819e5c730ac5985d28d57adb48 (patch)
tree32b07b659cf7d0b4c1a7da67b5c49daf7a10a9d3 /p2p/simulations/examples
parent673007d7aed1d2678ea3277eceb7b55dc29cf092 (diff)
downloaddexon-9feec51e2dd754819e5c730ac5985d28d57adb48.tar.gz
dexon-9feec51e2dd754819e5c730ac5985d28d57adb48.tar.zst
dexon-9feec51e2dd754819e5c730ac5985d28d57adb48.zip
p2p: add network simulation framework (#14982)
This commit introduces a network simulation framework which can be used to run simulated networks of devp2p nodes. The intention is to use this for testing protocols, performing benchmarks and visualising emergent network behaviour.
Diffstat (limited to 'p2p/simulations/examples')
-rw-r--r--p2p/simulations/examples/README.md39
-rw-r--r--p2p/simulations/examples/ping-pong.go184
-rwxr-xr-xp2p/simulations/examples/ping-pong.sh40
3 files changed, 263 insertions, 0 deletions
diff --git a/p2p/simulations/examples/README.md b/p2p/simulations/examples/README.md
new file mode 100644
index 000000000..822a48dcb
--- /dev/null
+++ b/p2p/simulations/examples/README.md
@@ -0,0 +1,39 @@
+# devp2p simulation examples
+
+## ping-pong
+
+`ping-pong.go` implements a simulation network which contains nodes running a
+simple "ping-pong" protocol where nodes send a ping message to all their
+connected peers every 10s and receive pong messages in return.
+
+To run the simulation, run `go run ping-pong.go` in one terminal to start the
+simulation API and `./ping-pong.sh` in another to start and connect the nodes:
+
+```
+$ go run ping-pong.go
+INFO [08-15|13:53:49] using sim adapter
+INFO [08-15|13:53:49] starting simulation server on 0.0.0.0:8888...
+```
+
+```
+$ ./ping-pong.sh
+---> 13:58:12 creating 10 nodes
+Created node01
+Started node01
+...
+Created node10
+Started node10
+---> 13:58:13 connecting node01 to all other nodes
+Connected node01 to node02
+...
+Connected node01 to node10
+---> 13:58:14 done
+```
+
+Use the `--adapter` flag to choose the adapter type:
+
+```
+$ go run ping-pong.go --adapter exec
+INFO [08-15|14:01:14] using exec adapter tmpdir=/var/folders/k6/wpsgfg4n23ddbc6f5cnw5qg00000gn/T/p2p-example992833779
+INFO [08-15|14:01:14] starting simulation server on 0.0.0.0:8888...
+```
diff --git a/p2p/simulations/examples/ping-pong.go b/p2p/simulations/examples/ping-pong.go
new file mode 100644
index 000000000..6a0ead53a
--- /dev/null
+++ b/p2p/simulations/examples/ping-pong.go
@@ -0,0 +1,184 @@
+// 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 main
+
+import (
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "os"
+ "sync/atomic"
+ "time"
+
+ "github.com/ethereum/go-ethereum/log"
+ "github.com/ethereum/go-ethereum/node"
+ "github.com/ethereum/go-ethereum/p2p"
+ "github.com/ethereum/go-ethereum/p2p/discover"
+ "github.com/ethereum/go-ethereum/p2p/simulations"
+ "github.com/ethereum/go-ethereum/p2p/simulations/adapters"
+ "github.com/ethereum/go-ethereum/rpc"
+)
+
+var adapterType = flag.String("adapter", "sim", `node adapter to use (one of "sim", "exec" or "docker")`)
+
+// main() starts a simulation network which contains nodes running a simple
+// ping-pong protocol
+func main() {
+ flag.Parse()
+
+ // set the log level to Trace
+ log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(false))))
+
+ // register a single ping-pong service
+ services := map[string]adapters.ServiceFunc{
+ "ping-pong": func(ctx *adapters.ServiceContext) (node.Service, error) {
+ return newPingPongService(ctx.Config.ID), nil
+ },
+ }
+ adapters.RegisterServices(services)
+
+ // create the NodeAdapter
+ var adapter adapters.NodeAdapter
+
+ switch *adapterType {
+
+ case "sim":
+ log.Info("using sim adapter")
+ adapter = adapters.NewSimAdapter(services)
+
+ case "exec":
+ tmpdir, err := ioutil.TempDir("", "p2p-example")
+ if err != nil {
+ log.Crit("error creating temp dir", "err", err)
+ }
+ defer os.RemoveAll(tmpdir)
+ log.Info("using exec adapter", "tmpdir", tmpdir)
+ adapter = adapters.NewExecAdapter(tmpdir)
+
+ case "docker":
+ log.Info("using docker adapter")
+ var err error
+ adapter, err = adapters.NewDockerAdapter()
+ if err != nil {
+ log.Crit("error creating docker adapter", "err", err)
+ }
+
+ default:
+ log.Crit(fmt.Sprintf("unknown node adapter %q", *adapterType))
+ }
+
+ // start the HTTP API
+ log.Info("starting simulation server on 0.0.0.0:8888...")
+ network := simulations.NewNetwork(adapter, &simulations.NetworkConfig{
+ DefaultService: "ping-pong",
+ })
+ if err := http.ListenAndServe(":8888", simulations.NewServer(network)); err != nil {
+ log.Crit("error starting simulation server", "err", err)
+ }
+}
+
+// pingPongService runs a ping-pong protocol between nodes where each node
+// sends a ping to all its connected peers every 10s and receives a pong in
+// return
+type pingPongService struct {
+ id discover.NodeID
+ log log.Logger
+ received int64
+}
+
+func newPingPongService(id discover.NodeID) *pingPongService {
+ return &pingPongService{
+ id: id,
+ log: log.New("node.id", id),
+ }
+}
+
+func (p *pingPongService) Protocols() []p2p.Protocol {
+ return []p2p.Protocol{{
+ Name: "ping-pong",
+ Version: 1,
+ Length: 2,
+ Run: p.Run,
+ NodeInfo: p.Info,
+ }}
+}
+
+func (p *pingPongService) APIs() []rpc.API {
+ return nil
+}
+
+func (p *pingPongService) Start(server *p2p.Server) error {
+ p.log.Info("ping-pong service starting")
+ return nil
+}
+
+func (p *pingPongService) Stop() error {
+ p.log.Info("ping-pong service stopping")
+ return nil
+}
+
+func (p *pingPongService) Info() interface{} {
+ return struct {
+ Received int64 `json:"received"`
+ }{
+ atomic.LoadInt64(&p.received),
+ }
+}
+
+const (
+ pingMsgCode = iota
+ pongMsgCode
+)
+
+// Run implements the ping-pong protocol which sends ping messages to the peer
+// at 10s intervals, and responds to pings with pong messages.
+func (p *pingPongService) Run(peer *p2p.Peer, rw p2p.MsgReadWriter) error {
+ log := p.log.New("peer.id", peer.ID())
+
+ errC := make(chan error)
+ go func() {
+ for range time.Tick(10 * time.Second) {
+ log.Info("sending ping")
+ if err := p2p.Send(rw, pingMsgCode, "PING"); err != nil {
+ errC <- err
+ return
+ }
+ }
+ }()
+ go func() {
+ for {
+ msg, err := rw.ReadMsg()
+ if err != nil {
+ errC <- err
+ return
+ }
+ payload, err := ioutil.ReadAll(msg.Payload)
+ if err != nil {
+ errC <- err
+ return
+ }
+ log.Info("received message", "msg.code", msg.Code, "msg.payload", string(payload))
+ atomic.AddInt64(&p.received, 1)
+ if msg.Code == pingMsgCode {
+ log.Info("sending pong")
+ go p2p.Send(rw, pongMsgCode, "PONG")
+ }
+ }
+ }()
+ return <-errC
+}
diff --git a/p2p/simulations/examples/ping-pong.sh b/p2p/simulations/examples/ping-pong.sh
new file mode 100755
index 000000000..47936bd9a
--- /dev/null
+++ b/p2p/simulations/examples/ping-pong.sh
@@ -0,0 +1,40 @@
+#!/bin/bash
+#
+# Boot a ping-pong network simulation using the HTTP API started by ping-pong.go
+
+set -e
+
+main() {
+ if ! which p2psim &>/dev/null; then
+ fail "missing p2psim binary (you need to build cmd/p2psim and put it in \$PATH)"
+ fi
+
+ info "creating 10 nodes"
+ for i in $(seq 1 10); do
+ p2psim node create --name "$(node_name $i)"
+ p2psim node start "$(node_name $i)"
+ done
+
+ info "connecting node01 to all other nodes"
+ for i in $(seq 2 10); do
+ p2psim node connect "node01" "$(node_name $i)"
+ done
+
+ info "done"
+}
+
+node_name() {
+ local num=$1
+ echo "node$(printf '%02d' $num)"
+}
+
+info() {
+ echo -e "\033[1;32m---> $(date +%H:%M:%S) ${@}\033[0m"
+}
+
+fail() {
+ echo -e "\033[1;31mERROR: ${@}\033[0m" >&2
+ exit 1
+}
+
+main "$@"