30cd5c1854
Package p2p/enode provides a generalized representation of p2p nodes which can contain arbitrary information in key/value pairs. It is also the new home for the node database. The "v4" identity scheme is also moved here from p2p/enr to remove the dependency on Ethereum crypto from that package. Record signature handling is changed significantly. The identity scheme registry is removed and acceptable schemes must be passed to any method that needs identity. This means records must now be validated explicitly after decoding. The enode API is designed to make signature handling easy and safe: most APIs around the codebase work with enode.Node, which is a wrapper around a valid record. Going from enr.Record to enode.Node requires a valid signature. * p2p/discover: port to p2p/enode This ports the discovery code to the new node representation in p2p/enode. The wire protocol is unchanged, this can be considered a refactoring change. The Kademlia table can now deal with nodes using an arbitrary identity scheme. This requires a few incompatible API changes: - Table.Lookup is not available anymore. It used to take a public key as argument because v4 protocol requires one. Its replacement is LookupRandom. - Table.Resolve takes *enode.Node instead of NodeID. This is also for v4 protocol compatibility because nodes cannot be looked up by ID alone. - Types Node and NodeID are gone. Further commits in the series will be fixes all over the the codebase to deal with those removals. * p2p: port to p2p/enode and discovery changes This adapts package p2p to the changes in p2p/discover. All uses of discover.Node and discover.NodeID are replaced by their equivalents from p2p/enode. New API is added to retrieve the enode.Node instance of a peer. The behavior of Server.Self with discovery disabled is improved. It now tries much harder to report a working IP address, falling back to 127.0.0.1 if no suitable address can be determined through other means. These changes were needed for tests of other packages later in the series. * p2p/simulations, p2p/testing: port to p2p/enode No surprises here, mostly replacements of discover.Node, discover.NodeID with their new equivalents. The 'interesting' API changes are: - testing.ProtocolSession tracks complete nodes, not just their IDs. - adapters.NodeConfig has a new method to create a complete node. These changes were needed to make swarm tests work. Note that the NodeID change makes the code incompatible with old simulation snapshots. * whisper/whisperv5, whisper/whisperv6: port to p2p/enode This port was easy because whisper uses []byte for node IDs and URL strings in the API. * eth: port to p2p/enode Again, easy to port because eth uses strings for node IDs and doesn't care about node information in any way. * les: port to p2p/enode Apart from replacing discover.NodeID with enode.ID, most changes are in the server pool code. It now deals with complete nodes instead of (Pubkey, IP, Port) triples. The database format is unchanged for now, but we should probably change it to use the node database later. * node: port to p2p/enode This change simply replaces discover.Node and discover.NodeID with their new equivalents. * swarm/network: port to p2p/enode Swarm has its own node address representation, BzzAddr, containing both an overlay address (the hash of a secp256k1 public key) and an underlay address (enode:// URL). There are no changes to the BzzAddr format in this commit, but certain operations such as creating a BzzAddr from a node ID are now impossible because node IDs aren't public keys anymore. Most swarm-related changes in the series remove uses of NewAddrFromNodeID, replacing it with NewAddr which takes a complete node as argument. ToOverlayAddr is removed because we can just use the node ID directly. |
||
---|---|---|
.. | ||
adapters | ||
examples | ||
pipes | ||
events.go | ||
http_test.go | ||
http.go | ||
mocker_test.go | ||
mocker.go | ||
network_test.go | ||
network.go | ||
README.md | ||
simulation.go |
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:
func(ctx *adapters.ServiceContext) (node.Service, error)
These initializers should be registered by calling the adapters.RegisterServices
function in an init()
hook:
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]