Go-Ethereum P2P Engine


How does the p2p engine work in go-ethereum?

Welcome back to my first blog post in a long while. I guess I should probably rename this blog from “This Week I Learned” to “Post Whenever I Feel Like It”. Anyway, I learned about a couple cool things in the last two months, one of which is how go-ethereum’s p2p engine works. Before I get into that, I just wanted to give a brief overview of the geth client.

Geth breakdown

Geth can be divided into 3 main parts:

  1. RPC – this is the component that is responsible for giving users information about the protocol / blockchain that the geth node is running.
  2. Core – this is the component responsible for participating in consensus, syncing and reconciling information from the network, among other things necessary for security of the protocol / blockchain.
  3. P2P – this component is responsible for exchanging information with other nodes in the network. It is essentially responsible for all of the node’s networking.

All of the above components are equally important, but in this post, I will write about p2p in particular.

Geth’s P2P Engine

Before talking about how networking works in geth, I’ll first mention the spec/protocol that ethereum clients implement.

Protocol

Geth runs a specific networking protocol called devp2p which encapsulates the several protocols used to interact with other nodes in the network. You can find the specs here. For discovery of other nodes in the network, Geth specifically uses Discovery V4 or discv4 for short.

Implementation

Geth has a p2p.Server mounted on the node that is created and started upon node start-up. The server contains a bunch of information relevant for networking, such as information about peers and about itself as an enode (a p2p node), in addition to all the tools necessary to open connections, perform handshakes, and send messages between peers.

The flow upon p2p.Server creation / start-up is as follows:

  • the p2p.Server is started up
  • it kicks off both a dialer and listener loop
    • the dialer loop is responsible for dialing peers for which the public keys are known by the node
    • the listener loop is responsible for listening for inbound messages from other nodes in the network
  • the dialer, once it successfully creates a connection with another node in the network, proceeds to do an initial encryption handshake according to the RLPx transport protocol.
  • if the handshake is successful, a post-handshake check is done to ensure that the server is even able to accept a new connection (e.g., the server isn’t overloaded with inbound connections and hasn’t exceeded max peer count).
  • if that checkpoint passes, the next step is to do a protocol handshake which consists of exchange information about which protocols the node is running / the node’s capabilities.
  • if the protocol handshake is successful, the next step is doing an addPeerCheckpoint check. This checkpoint verifies that the peer’s capabilities / protocols are compatible.
  • if this check passes, the peer is successfully added to the node’s distributed hash table (dht) and can now be used as a valid peer to sync against.

Here is a diagram to visually represent this process:

p2pServer