Skip to main content
netspecsEthereum networking

1.4.Addressing

Stage
Current
Validity
Active non-fork protocol.
Pulled
consensus-specsp2p-interface.mdlibp2pREADME.md
Related
Networking concepts

ENRs and multiaddrs

Ethereum Node Records are self-certified node records. Nodes craft and disseminate ENRs for themselves, proving authorship via a cryptographic signature. ENRs are sequentially indexed, enabling conflicts to be resolved.

ENRs are key-value records with string-indexed ASCII keys. They can store arbitrary information, but EIP-778 specifies a pre-defined dictionary, including IPv4 and IPv6 addresses, secp256k1 public keys, etc.

Comparing ENRs and multiaddrs is like comparing apples and oranges. ENRs are self-certified containers of identity, addresses, and metadata about a node. Multiaddrs are address strings with the peculiarity that they’re self-describing, composable and future-proof. An ENR can contain multiaddrs, and multiaddrs can be derived securely from the fields of an authenticated ENR.

Consensus discovery endpoints

The Ethereum Node Record (ENR) for an Ethereum consensus client MUST contain the following entries (exclusive of the sequence number and signature, which MUST be present in an ENR):

  • The compressed secp256k1 publickey, 33 bytes (secp256k1 field).

The ENR MAY contain the following entries:

  • An IPv4 address (ip field) and/or IPv6 address (ip6 field).
  • An IPv4 QUIC port (quic field) representing the local libp2p QUIC (UDP) listening port and/or the corresponding IPv6 port (quic6 field).
  • An IPv4 TCP port (tcp field) representing the local libp2p TCP listening port and/or the corresponding IPv6 port (tcp6 field).
  • An IPv4 UDP port (udp field) representing the local discv5 listening port and/or the corresponding IPv6 port (udp6 field).

Specifications of these parameters can be found in the ENR Specification.

Multiaddresses

multiaddrs are used throughout libp2p for encoding network addresses. When addresses need to be shared or exchanged between processes, they are encoded in the binary representation of multiaddr.

When exchanging addresses, peers send a multiaddr containing both their network address and peer id, as described in the section on the p2p multiaddr.

multiaddr basics

A multiaddr is a sequence of instructions that can be traversed to some destination.

For example, the /ip4/198.51.100/tcp/1234 multiaddr starts with ip4, which is the lowest-level protocol that requires an address. The tcp protocol runs on top of ip4, so it comes next.

The multiaddr above consists of two components, the /ip4/198.51.100 component, and the /tcp/1234 component. It’s not possible to split either one further; /ip4 alone is an invalid multiaddr, because the ip4 protocol was defined to require a 32 bit address. Similarly, tcp requires a 16 bit port number.

Although we referred to /ip4/198.51.100 and /tcp/1234 as “components” of a larger TCP/IP address, each is actually a valid multiaddr according to the multiaddr spec. However, not every syntactically valid multiaddr is a functional description of a process in the network. As we’ve seen, even a simple TCP/IP connection requires composing two multiaddrs into one. See the section on composing multiaddrs for information on how multiaddrs can be combined, and the Transport multiaddrs section for the combinations that describe valid transport addresses.

The multiaddr protocol table contains all currently defined protocols and the length of their address components.

Composing multiaddrs

As shown above, protocol addresses can be composed within a multiaddr in a way that mirrors the composition of protocols within a networking stack.

The terms generally used to describe composition of multiaddrs are “encapsulation” and “decapsulation”, and they essentially refer to adding and removing protocol components from a multiaddr, respectively.

Encapsulation

A protocol is said to be “encapsulated within” another protocol when data from an “inner” protocol is wrapped by another “outer” protocol, often by re-framing the data from the inner protocol into the type of packets, frames or datagrams used by the outer protocol.

Some examples of protocol encapsulation are HTTP requests encapsulated within TCP/IP streams, or TCP segments themselves encapsulated within IP datagrams.

The multiaddr format was designed so that addresses encapsulate each other in the same manner as the protocols that they describe. The result is an address that begins with the “outermost” layer of the network stack and works progressively “inward”. For example, in the address /ip4/198.51.100/tcp/80/ws, the outermost protocol is IPv4, which encapsulates TCP streams, which in turn encapsulate WebSockets.

All multiaddr implementations provide a way to encapsulate two multiaddrs into a composite. For example, /ip4/198.51.100 can encapsulate /tcp/42 to become /ip4/198.51.100/tcp/42.

Decapsulation

Decapsulation takes a composite multiaddr and removes an “inner” multiaddr from it, returning the result.

For example, if we start with /ip4/198.51.100/tcp/1234/ws and decapsulate /ws, the result is /ip4/198.51.100/tcp/1234.

It’s important to note that decapsulation returns the original multiaddr up to the last occurrence of the decapsulated multiaddr. This may remove more than just the decapsulated component itself if there are more protocols encapsulated within it. Using our example above, decapsulating either /tcp/1234/ws or /tcp/1234 from /ip4/198.51.100/tcp/1234/ws will result in /ip4/198.51.100. This is unsurprising if you consider the utility of the /ip4/198.51.100/ws address that would result from simply removing the tcp component.

The p2p multiaddr

libp2p defines the p2p multiaddr protocol, whose address component is the peer id of a libp2p peer. The text representation of a p2p multiaddr looks like this:

/p2p/QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N

Where QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N is the string representation of a peer’s peer ID derived from its public key.

By itself, a p2p address does not give you enough addressing information to locate a peer on the network; it is not a transport address. However, like the ws protocol for WebSockets, a p2p address can be encapsulated within another multiaddr.

For example, the above p2p address can be combined with the transport address on which the node is listening:

/ip4/198.51.100/tcp/1234/p2p/QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N

This combination of transport address plus p2p address is the format in which peers exchange addresses over the wire in the identify protocol and other core libp2p protocols.

From discovery records to dialable addresses

discv5 SHOULD be integrated into the client’s libp2p stack by implementing an adaptor to make it conform to the service discovery and peer routing abstractions and interfaces (go-libp2p links provided).

Inputs to operations include peer IDs (when locating a specific peer) or capabilities (when searching for peers with a specific capability), and the outputs will be multiaddrs converted from the ENR records returned by the discv5 backend.

This integration enables the libp2p stack to subsequently form connections and streams with discovered peers.