aboutsummaryrefslogtreecommitdiffstats
path: root/p2p/simulations/README.md
blob: d1f8649eaed2b1b216d69295b370fc6f0b3a6970 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# devp2p Simulations

The `p2p/simulations` package implements a simulation framework which supports
creating a collection of devp2p nodes, connecting them together to form a
simulation network, performing simulation actions in that network and then
extracting useful information.

## Nodes

Each node in a simulation network runs multiple services by wrapping a collection
of objects which implement the `node.Service` interface meaning they:

* can be started and stopped
* run p2p protocols
* expose RPC APIs

This means that any object which implements the `node.Service` interface can be
used to run a node in the simulation.

## Services

Before running a simulation, a set of service initializers must be registered
which can then be used to run nodes in the network.

A service initializer is a function with the following signature:

```go
func(ctx *adapters.ServiceContext) (node.Service, error)
```

These initializers should be registered by calling the `adapters.RegisterServices`
function in an `init()` hook:

```go
func init() {
    adapters.RegisterServices(adapters.Services{
        "service1": initService1,
        "service2": initService2,
    })
}
```

## Node Adapters

The simulation framework includes multiple "node adapters" which are
responsible for creating an environment in which a node runs.

### SimAdapter

The `SimAdapter` runs nodes in-memory, connecting them using an in-memory,
synchronous `net.Pipe` and connecting to their RPC server using an in-memory
`rpc.Client`.

### ExecAdapter

The `ExecAdapter` runs nodes as child processes of the running simulation.

It does this by executing the binary which is running the simulation but
setting `argv[0]` (i.e. the program name) to `p2p-node` which is then
detected by an init hook in the child process which runs the `node.Service`
using the devp2p node stack rather than executing `main()`.

The nodes listen for devp2p connections and WebSocket RPC clients on random
localhost ports.

### DockerAdapter

The `DockerAdapter` is similar to the `ExecAdapter` but executes `docker run`
to run the node in a Docker container using a Docker image containing the
simulation binary at `/bin/p2p-node`.

The Docker image is built using `docker build` when the adapter is initialised,
meaning no prior setup is necessary other than having a working Docker client.

Each node listens on the external IP of the container and the default p2p and
RPC ports (`30303` and `8546` respectively).

## Network

A simulation network is created with an ID and default service (which is used
if a node is created without an explicit service), exposes methods for
creating, starting, stopping, connecting and disconnecting nodes, and emits
events when certain actions occur.

### Events

A simulation network emits the following events:

* node event       - when nodes are created / started / stopped
* connection event - when nodes are connected / disconnected
* message event    - when a protocol message is sent between two nodes

The events have a "control" flag which when set indicates that the event is the
outcome of a controlled simulation action (e.g. creating a node or explicitly
connecting two nodes together).

This is in contrast to a non-control event, otherwise called a "live" event,
which is the outcome of something happening in the network as a result of a
control event (e.g. a node actually started up or a connection was actually
established between two nodes).

Live events are detected by the simulation network by subscribing to node peer
events via RPC when the nodes start up.

## Testing Framework

The `Simulation` type can be used in tests to perform actions in a simulation
network and then wait for expectations to be met.

With a running simulation network, the `Simulation.Run` method can be called
with a `Step` which has the following fields:

* `Action` - a function which performs some action in the network

* `Expect` - an expectation function which returns whether or not a
    given node meets the expectation

* `Trigger` - a channel which receives node IDs which then trigger a check
    of the expectation function to be performed against that node

As a concrete example, consider a simulated network of Ethereum nodes. An
`Action` could be the sending of a transaction, `Expect` it being included in
a block, and `Trigger` a check for every block that is mined.

On return, the `Simulation.Run` method returns a `StepResult` which can be used
to determine if all nodes met the expectation, how long it took them to meet
the expectation and what network events were emitted during the step run.

## HTTP API

The simulation framework includes a HTTP API which can be used to control the
simulation.

The API is initialised with a particular node adapter and has the following
endpoints:

```
GET    /                            Get network information
POST   /start                       Start all nodes in the network
POST   /stop                        Stop all nodes in the network
GET    /events                      Stream network events
GET    /snapshot                    Take a network snapshot
POST   /snapshot                    Load a network snapshot
POST   /nodes                       Create a node
GET    /nodes                       Get all nodes in the network
GET    /nodes/:nodeid               Get node information
POST   /nodes/:nodeid/start         Start a node
POST   /nodes/:nodeid/stop          Stop a node
POST   /nodes/:nodeid/conn/:peerid  Connect two nodes
DELETE /nodes/:nodeid/conn/:peerid  Disconnect two nodes
GET    /nodes/:nodeid/rpc           Make RPC requests to a node via WebSocket
```

For convenience, `nodeid` in the URL can be the name of a node rather than its
ID.

## Command line client

`p2psim` is a command line client for the HTTP API, located in
`cmd/p2psim`.

It provides the following commands:

```
p2psim show
p2psim events [--current] [--filter=FILTER]
p2psim snapshot
p2psim load
p2psim node create [--name=NAME] [--services=SERVICES] [--key=KEY]
p2psim node list
p2psim node show <node>
p2psim node start <node>
p2psim node stop <node>
p2psim node connect <node> <peer>
p2psim node disconnect <node> <peer>
p2psim node rpc <node> <method> [<args>] [--subscribe]
```

## Example

See [p2p/simulations/examples/README.md](examples/README.md).