nim-eth-p2p/README.md

5.5 KiB

nim-eth-p2p Build Status Build statusLicense: ApacheLicense: MITStability: experimental

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:

  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.

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:

This file may not be copied, modified, or distributed except according to those terms.