Added documentation
This commit is contained in:
parent
96cf717778
commit
6ac6397d33
230
README.md
230
README.md
|
@ -1,16 +1,85 @@
|
|||
**nim-eth-p2p** [![Build Status](https://travis-ci.org/status-im/nim-eth-p2p.svg?branch=master)](https://travis-ci.org/status-im/nim-eth-p2p) [![Build status](https://ci.appveyor.com/api/projects/status/i4txsa2pdyaahmn0/branch/master?svg=true)](https://ci.appveyor.com/project/cheatfate/nim-eth-p2p/branch/master)[![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)![Stability: experimental](https://img.shields.io/badge/stability-experimental-orange.svg)
|
||||
**nim-eth-p2p**
|
||||
[![Build Status](https://travis-ci.org/status-im/nim-eth-p2p.svg?branch=master)](https://travis-ci.org/status-im/nim-eth-p2p)
|
||||
[![Build status](https://ci.appveyor.com/api/projects/status/i4txsa2pdyaahmn0/branch/master?svg=true)](https://ci.appveyor.com/project/cheatfate/nim-eth-p2p/branch/master)
|
||||
[![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)
|
||||
[![Stability: experimental](https://img.shields.io/badge/stability-experimental-orange.svg)]
|
||||
|
||||
## Introduction
|
||||
|
||||
This library is a Nim re-implementation of the Ethereum DevP2P networking protocol.
|
||||
This library implements the DevP2P family of networking protocols used
|
||||
in the Ethereum world.
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
$ nimble install eth_p2p
|
||||
``` bash
|
||||
nimble install eth_p2p
|
||||
```
|
||||
|
||||
## RLPx
|
||||
## 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)
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
[RLPx](https://github.com/ethereum/devp2p/blob/master/rlpx.md) is the
|
||||
high-level protocol for exchanging messages between peers in the Ethereum
|
||||
|
@ -18,14 +87,17 @@ 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).
|
||||
The RLPx protocols are defined as a collection of strongly-typed messages,
|
||||
which are grouped into sub-protocols multiplexed over the same TCP connection.
|
||||
|
||||
### Defining RLPx sub-protocols
|
||||
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.
|
||||
|
||||
To understand more about how messages are defined and used, let's look at
|
||||
the definition of a RLPx protocol:
|
||||
|
||||
### 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:
|
||||
|
@ -41,36 +113,26 @@ rlpxProtocol p2p, 0:
|
|||
listenPort: uint,
|
||||
nodeId: P2PNodeId) =
|
||||
peer.id = nodeId
|
||||
peer.dispatcher = getDispatcher(capabilities)
|
||||
|
||||
proc disconnect(peer: Peer, reason: DisconnectionReason)
|
||||
|
||||
proc ping(peer: Peer)
|
||||
proc ping(peer: Peer) =
|
||||
await peer.pong()
|
||||
|
||||
proc pong(peer: Peer) =
|
||||
echo "received pong from ", peer.id
|
||||
```
|
||||
|
||||
#### Sending messages
|
||||
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.
|
||||
|
||||
To send a particular message to a particular peer, just call the
|
||||
corresponding proc over the `Peer` object:
|
||||
### Protocol state
|
||||
|
||||
``` nim
|
||||
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:
|
||||
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:
|
||||
|
||||
``` nim
|
||||
rlpxProtocol abc, 1:
|
||||
|
@ -82,18 +144,40 @@ rlpxProtocol abc, 1:
|
|||
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
Sometimes, you'll need to access the state of another protocol. To do this,
|
||||
specify the protocol identifier to the `state` accessor:
|
||||
specify the protocol identifier to the `state` accessors:
|
||||
|
||||
``` nim
|
||||
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.
|
||||
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:
|
||||
|
||||
This is enabled by the helper proc `nextMsg`:
|
||||
### 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:
|
||||
|
||||
``` nim
|
||||
proc handshakeExample(peer: Peer) {.async.} =
|
||||
|
@ -117,11 +201,73 @@ There are few things to note in the above example:
|
|||
matching the parameter names of the message. If the messages has `openarray`
|
||||
params, these will be remapped to `seq` 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.
|
||||
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
|
||||
```
|
||||
|
||||
### Checking the other peer's supported sub-protocols
|
||||
|
||||
|
|
Loading…
Reference in New Issue