fac8bbd917
* Make all handlers async * Added a new `requestResponse` construct that allows you to wait for the results of a particular request * Introduced an optional `NetworkState` type for the protocol * The semantics of `nextMsg` has changed. See the notes in the README |
||
---|---|---|
eth_p2p | ||
tests | ||
.gitignore | ||
.travis.yml | ||
LICENSE-APACHEv2 | ||
LICENSE-MIT | ||
README.md | ||
appveyor.yml | ||
eth_p2p.nim | ||
eth_p2p.nimble |
README.md
Introduction
This library is a Nim re-implementation of the Ethereum DevP2P networking protocol.
Installation
$ nimble install eth_p2p
RLPx
RLPx is the high-level protocol for exchanging messages between peers in the Ethereum network. Most of the client code of this library should not be concerned with the implementation details of the underlying protocols and should use the high-level APIs described in this section.
To obtain a RLPx connection, use the rlpxConnect
proc supplying the
id of another node in the network. On success, the proc will return a
Peer
object representing the connection. Each of the RLPx sub-protocols
consists of a set of strongly-typed messages, which are represented by
this library as regular Nim procs that can be executed over the Peer
object (more on this later).
Defining RLPx sub-protocols
The sub-protocols are defined with the rlpxProtocol
macro. It will accept
a 3-letter identifier for the protocol and the current protocol version:
Here is how the DevP2P wire protocol might look like:
rlpxProtocol p2p, 0:
proc hello(peer: Peer,
version: uint,
clientId: string,
capabilities: openarray[Capability],
listenPort: uint,
nodeId: P2PNodeId) =
peer.id = nodeId
peer.dispatcher = getDispatcher(capabilities)
proc disconnect(peer: Peer, reason: DisconnectionReason)
proc ping(peer: Peer)
proc pong(peer: Peer) =
echo "received pong from ", peer.id
Sending messages
To send a particular message to a particular peer, just call the
corresponding proc over the Peer
object:
peer.hello(4, "Nimbus 1.0", ...)
peer.ping()
Receiving messages
Once a connection is established, incoming messages in RLPx may appear in
arbitrary order, because the sub-protocols may be multiplexed over a single
underlying connection. For this reason, the library assumes that the incoming
messages will be dispatched automatically to their corresponding handlers,
appearing in the protocol definition. The protocol implementations are expected
to maintain a state and to act like a state machine handling the incoming messages.
To achieve this, each protocol may define a State
object that can be accessed as
a state
field of the Peer
object:
rlpxProtocol abc, 1:
type State = object
receivedMsgsCount: int
proc incomingMessage(p: Peer) =
p.state.receivedMsgsCount += 1
Sometimes, you'll need to access the state of another protocol. To do this,
specify the protocol identifier to the state
accessor:
echo "ABC protocol messages: ", peer.state(abc).receivedMsgCount
While the state machine approach is the recommended way of implementing sub-protocols, sometimes in imperative code it may be easier to wait for a particular response message after sending a certain request.
This is enabled by the helper proc nextMsg
:
proc handshakeExample(peer: Peer) {.async.} =
...
# send a hello message
peer.hello(...)
# wait for a matching hello response
let response = await peer.nextMsg(p2p.hello)
echo response.clientId # print the name of the Ethereum client
# used by the other peer (Geth, Parity, Nimbus, etc)
There are few things to note in the above example:
-
The
rlpxProtocol
definition created a pseudo-variable named after the protocol holding various properties of the protocol. -
Each message defined in the protocol received a corresponding type name, matching the message name (e.g.
p2p.hello
). This type will have fields matching the parameter names of the message. If the messages hasopenarray
params, these will be remapped toseq
types.
The future returned by nextMsg
will be resolved only after the handler of the
designated message has been fully executed (so you can count on any side effects
produced by the handler to have taken place). If there are multiple outstanding
calls to nextMsg
, they will complete together. Any other messages received in
the meantime will still be dispatched to their respective handlers.
Checking the other peer's supported sub-protocols
Upon establishing a connection, RLPx will automatically negotiate the list of mutually supported protocols by the peers. To check whether a particular peer supports a particular sub-protocol, use the following code:
if peer.supports(les): # `les` is the identifier of the light clients sub-protocol
peer.getReceipts(nextReqId(), neededReceipts())
License
Distributed under one of the following:
- Apache License, Version 2.0, (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
This file may not be copied, modified, or distributed except according to those terms.