2018-03-30 16:27:59 +00:00
|
|
|
# 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)
|
|
|
|
|
2018-04-30 18:08:18 +00:00
|
|
|
[Nim] Ethereum P2P protocol implementation
|
2018-04-13 12:59:08 +00:00
|
|
|
|
|
|
|
## RLPx
|
|
|
|
|
|
|
|
[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.
|
|
|
|
|
|
|
|
To obtain a RLPx connection, use the proc `rlpxConnect` 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](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
|
|
|
|
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:
|
|
|
|
|
|
|
|
``` 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:
|
|
|
|
|
|
|
|
``` nim
|
|
|
|
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:
|
|
|
|
|
|
|
|
``` 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.
|
|
|
|
|
|
|
|
This is enabled by the helper proc `nextMsg`:
|
|
|
|
|
|
|
|
``` 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.
|
|
|
|
|
|
|
|
By default, `nextMsg` will still automatically dispatch all messages different
|
|
|
|
from the awaited one, but you can prevent this behavior by specifying the extra
|
|
|
|
flag `discardOthers = true`.
|
|
|
|
|
|
|
|
### 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
|
|
|
|
|
|
|
|
Licensed and distributed under either of
|
|
|
|
* MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
|
|
|
|
* Apache v2 license (license terms in the root directory or at 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.
|