2018-09-05 04:24:14 +00:00
# eth_p2p
[![Build Status (Travis) ](https://img.shields.io/travis/status-im/nim-eth-p2p/master.svg?label=Linux%20/%20macOS "Linux/macOS build status (Travis )")](https://travis-ci.org/status-im/nim-eth-p2p)
[![Windows build status (Appveyor) ](https://img.shields.io/appveyor/ci/nimbus/nim-eth-p2p/master.svg?label=Windows "Windows build status (Appveyor )")](https://ci.appveyor.com/project/nimbus/nim-eth-p2p)
2018-07-09 17:00:34 +00:00
[![License: Apache ](https://img.shields.io/badge/License-Apache%202.0-blue.svg )](https://opensource.org/licenses/Apache-2.0)
[![License: MIT ](https://img.shields.io/badge/License-MIT-blue.svg )](https://opensource.org/licenses/MIT)
2018-09-05 04:24:14 +00:00
![Stability: experimental ](https://img.shields.io/badge/stability-experimental-orange.svg )
2018-05-27 21:14:01 +00:00
## Introduction
2018-03-30 16:27:59 +00:00
2018-07-09 17:00:34 +00:00
This library implements the DevP2P family of networking protocols used
in the Ethereum world.
2018-04-13 12:59:08 +00:00
2018-05-27 21:14:01 +00:00
## Installation
2018-07-09 17:00:34 +00:00
``` bash
nimble install eth_p2p
2018-06-16 18:58:41 +00:00
```
2018-07-09 17:00:34 +00:00
## Connecting to the Ethereum network
A connection to the Ethereum network can be created by instantiating
the `EthereumNode` type:
``` nim
proc newEthereumNode*(keys: KeyPair,
chain: AbstractChainDB,
clientId = "nim-eth-p2p",
addAllCapabilities = true): EthereumNode =
```
#### Parameters:
`keys` :
A pair of public and private keys used to authenticate the node
on the network and to determine its node ID.
See the [eth_keys ](https://github.com/status-im/nim-eth-keys )
library for utilities that will help you generate and manage
such keys.
`chain` :
An abstract instance of the Ethereum blockchain associated
with the node. This library allows you to plug any instance
conforming to the abstract interface defined in the
[eth_common ](https://github.com/status-im/nim-eth-common )
package.
`clientId` :
A name used to identify the software package connecting
to the network (i.e. similar to the `User-Agent` string
in a browser).
`addAllCapabilities` :
By default, the node will support all RPLx protocols imported in
your project. You can specify `false` if you prefer to create a
node with a more limited set of protocols. Use one or more calls
to `node.addCapability` to specify the desired set:
```nim
node.addCapability(eth)
node.addCapability(ssh)
```
Each supplied protocol identifier is a name of a protocol introduced
by the `rlpxProtocol` macro discussed later in this document.
Instantiating an `EthereumNode` does not immediately connect you to
the network. To start the connection process, call `node.connectToNetwork` :
``` nim
proc connectToNetwork*(node: var EthereumNode,
address: Address,
listeningPort = Port(30303),
bootstrapNodes: openarray[ENode],
networkId: int,
startListening = true)
2018-06-16 18:58:41 +00:00
```
2018-05-27 21:14:01 +00:00
2018-07-09 17:00:34 +00:00
The `EthereumNode` will automatically find and maintan a pool of peers
using the Ethereum node discovery protocol. You can access the pool as
`node.peers` .
## Communicating with Peers using RLPx
2018-04-13 12:59:08 +00:00
[RLPx ](https://github.com/ethereum/devp2p/blob/master/rlpx.md ) 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.
2018-07-09 17:00:34 +00:00
The RLPx protocols are defined as a collection of strongly-typed messages,
which are grouped into sub-protocols multiplexed over the same TCP connection.
This library represents each such message as a regular Nim function call
over the `Peer` object. Certain messages act only as notifications, while
others fit the request/response pattern.
2018-04-13 12:59:08 +00:00
2018-07-09 17:00:34 +00:00
To understand more about how messages are defined and used, let's look at
the definition of a RLPx protocol:
### RLPx sub-protocols
2018-04-13 12:59:08 +00:00
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 ](https://github.com/ethereum/wiki/wiki/%C3%90%CE%9EVp2p-Wire-Protocol ) might look like:
``` nim
rlpxProtocol p2p, 0:
proc hello(peer: Peer,
version: uint,
clientId: string,
capabilities: openarray[Capability],
listenPort: uint,
nodeId: P2PNodeId) =
peer.id = nodeId
proc disconnect(peer: Peer, reason: DisconnectionReason)
2018-07-09 17:00:34 +00:00
proc ping(peer: Peer) =
await peer.pong()
2018-04-13 12:59:08 +00:00
proc pong(peer: Peer) =
echo "received pong from ", peer.id
```
2018-07-09 17:00:34 +00:00
As seen in the example above, a protocol definition determines both the
available messages that can be sent to another peer (e.g. as in `peer.pong()` )
and the asynchronous code responsible for handling the incoming messages.
2018-04-13 12:59:08 +00:00
2018-07-09 17:00:34 +00:00
### Protocol state
2018-04-13 12:59:08 +00:00
2018-07-09 17:00:34 +00:00
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:
2018-04-13 12:59:08 +00:00
``` nim
rlpxProtocol abc, 1:
type State = object
receivedMsgsCount: int
proc incomingMessage(p: Peer) =
p.state.receivedMsgsCount += 1
```
2018-07-09 17:00:34 +00:00
Besides the per-peer state demonstrated above, there is also support for
maintaining a network-wide state. In the example above, we'll just have
to change the name of the state type to `NetworkState` and the accessor
expression to `p.network.state` .
The state objects are initialized to zero by default, but you can modify
this behaviour by overriding the following procs for your state types:
```nim
proc initProtocolState*(state: var MyPeerState, p: Peer)
proc initProtocolState*(state: var MyNetworkState, n: EthereumNode)
```
Please note that the state type will have to be placed outside of the
protocol definition in order to achieve this.
2018-04-13 12:59:08 +00:00
Sometimes, you'll need to access the state of another protocol. To do this,
2018-07-09 17:00:34 +00:00
specify the protocol identifier to the `state` accessors:
2018-04-13 12:59:08 +00:00
``` nim
echo "ABC protocol messages: ", peer.state(abc).receivedMsgCount
```
2018-07-09 17:00:34 +00:00
While the state machine approach may be a particularly robust way of
implementing sub-protocols (it is more amenable to proving the correctness
of the implementation through formal verification methods), sometimes it may
be more convenient to use more imperative style of communication where the
code is able to wait for a particular response after sending a particular
request. The library provides two mechanisms for achieving this:
2018-04-13 12:59:08 +00:00
2018-07-09 17:00:34 +00:00
### Waiting particular messages with `nextMsg`
The `nextMsg` helper proc can be used to pause the execution of an async
proc until a particular incoming message from a peer arrives:
2018-04-13 12:59:08 +00:00
``` nim
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:
1. The `rlpxProtocol` definition created a pseudo-variable named after the
protocol holding various properties of the protocol.
2. 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 has `openarray`
params, these will be remapped to `seq` types.
2018-07-09 17:00:34 +00:00
If the designated messages also has an attached handler, the future returned
by `nextMsg` will be resolved only after the handler 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.
### `requestResponse` pairs
``` nim
rlpxProtocol les, 2:
...
requestResponse:
proc getProofs(p: Peer, proofs: openarray[ProofRequest])
proc proofs(p: Peer, BV: uint, proofs: openarray[Blob])
...
```
Two or more messages within the protocol may be grouped into a
`requestResponse` block. The last message in the group is assumed
to be the response while all other messages are considered requests.
When a request message is sent, the return type will be a `Future`
that will be completed once the response is received. Please note
that there is a mandatory timeout parameter, so the actual return
type is `Future[Option[MessageType]]` . The `timeout` parameter can
be specified for each individual call and the default value can be
overridden on the level of individual message, or the entire protocol:
``` nim
rlpxProtocol abc, 1:
timeout = 5000 # value in milliseconds
useRequestIds = false
requestResponse:
proc myReq(dataId: int, timeout = 3000)
proc myRes(data: string)
```
By default, the library will take care of inserting a hidden `reqId`
parameter as used in the [LES protocol ](https://github.com/zsfelfoldi/go-ethereum/wiki/Light-Ethereum-Subprotocol-%28LES%29 ),
but you can disable this behavior by overriding the protocol setting
`useRequestIds` .
### Implementing handshakes and reacting to other events
Besides message definitions and implementations, a protocol specification may
also include handlers for certain important events such as newly connected
peers or misbehaving or disconnecting peers:
``` nim
rlpxProtocol les, 2:
onPeerConnected do (peer: Peer):
asyncCheck peer.status [
"networkId": rlp.encode(1),
"keyGenesisHash": rlp.encode(peer.network.chain.genesisHash)
...
]
let otherPeerStatus = await peer.nextMsg(les.status)
...
onPeerDisconnected do (peer: Peer, reason: DisconnectionReason):
debug "peer disconnected", peer
```
2018-04-13 12:59:08 +00:00
### 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:
``` nim
if peer.supports(les): # `les` is the identifier of the light clients sub-protocol
peer.getReceipts(nextReqId(), neededReceipts())
```
2018-03-30 14:17:34 +00:00
## License
2018-09-05 18:47:38 +00:00
Licensed and distributed under either of
* MIT license: [LICENSE-MIT ](LICENSE-MIT ) or http://opensource.org/licenses/MIT
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
at your option. This file may not be copied, modified, or distributed except according to those terms.
2018-05-27 21:14:01 +00:00