EIP-2124 A fork identifier for checking chain compatibility (Fork identifier for chain compatibility checks)

Final · Networking & nodes · EIP status Final · created 2019-05-03

Nodes exchange an identifier (forkid) that summarizes the genesis and the history of forks already passed as a 4-byte checksum, letting them tell before connecting whether a peer is a compatible node on the same chain.

Why it is needed

Besides mainnet, Ethereum has countless testnets and private networks, but nodes cannot distinguish between them during the discovery phase where they find each other. To find out whether a peer is on the same chain, a node has to establish a TCP connection, wrap it in the encryption layer (RLPx), and complete the eth handshake, and even after paying that cost it still cannot accurately tell Ethereum and Ethereum Classic apart. The smaller the network, the fewer nodes there are, so more trial and error is needed to find good peers. In addition, nodes that are not updated after a hard fork stay attached to healthy nodes and refuse to accept upgraded blocks, wasting valuable peer slots and bandwidth. On testnets such nodes can linger for months. This EIP defines an identifier that compactly and accurately summarizes a chain's current state, along with validation rules for it, so useless peers can be filtered out before connecting.

What changes

Today

After EIP-2124

Who is affected

In depth

FORK_HASH calculation rule

FORK_HASH is the IEEE CRC32 checksum ([4]byte) of the genesis hash and the fork block numbers already passed. Fork block numbers are entered in ascending order, and if multiple forks apply at the same block, that block number is entered into the checksum only once. Block numbers are encoded as big-endian uint64 integers. If a chain is configured to start from genesis with rules other than Frontier, that is not counted as a fork. The original mainnet example is as follows: genesis is CRC32(<genesis-hash>) = 0xfc64ec04, Homestead is CRC32(<genesis-hash> || uint64(1150000)) = 0x97c2c34c, and the DAO fork appends uint64(1920000) to that, giving 0x91d1f948.

FORK_NEXT and forkid encoding

FORK_NEXT is the block number (uint64) of the upcoming fork, and it is 0 if no next fork is known. The fork identifier is defined as RLP([FORK_HASH, FORK_NEXT]). This forkid judges a peer chain's compatibility not through a simple comparison but through cross validation. Regardless of fork status, both sides must reach the same conclusion, since otherwise one side rejects while the other keeps trying to reconnect. The original test cases also present RLP encoding results that reflect FORK_HASH being a 4-byte binary and FORK_NEXT being an 8-byte quantity. For example, {Hash: 0xdeadbeef, Next: 0xBADDCAFE} is encoded as 0xca84deadbeef84baddcafe.

The four validation rules

Rule 1: if both sides' FORK_HASH match, compare my head against the peer's FORK_NEXT. If a fork the peer announced as not yet passed has already passed on my side, the chains are incompatible and the connection is dropped (1a). If the peer announced no fork, or it has not yet passed on my side either, the nodes connect (1b). Rule 2: if the peer's FORK_HASH is a subset of my past fork history and the peer's FORK_NEXT matches the next fork block number on my side, the nodes connect. This is the case where the peer is syncing. Rule 3: if the peer's FORK_HASH is a superset of my history and can be constructed by filling in future forks I know about, the nodes connect. This is the case where I am syncing. Rule 4: everything else is rejected. There is one asymmetry. Since only the single next fork can be known, not the peer's entire list of future forks, the fact that a syncing peer's software is outdated cannot be known until the peer catches up. The original document explains that this is acceptable since the peer will disconnect first anyway, and it is merely a transient condition during syncing.

Why a 4-byte CRC32

The eth protocol itself can send large data, but the ENR used by the discovery protocol limits the total of all entries to 300 bytes. Shrinking FORK_HASH to a 4-byte checksum leaves room in the ENR for other extensions, and from the standpoint of practical collision probability, 4 bytes is enough to distinguish an arbitrarily large number of Ethereum networks. The original document also explains why CRC32 is used instead of a hash function. Since a node can lie at any time, there is no value gained from using a cryptographic hash function, and if the goal is to fold arbitrary data into 4 bytes without losing information, a CRC32 designed for that purpose is better than truncating the first 4 bytes of Keccak256 or XORing it in 4-byte chunks. IEEE CRC32 is used in Ethernet, gzip, zip, and PNG, so language support is not an issue either.

Why FORK_NEXT is needed and why only one future fork is announced

Sharing only past fork history cannot distinguish whether a peer is simply behind in syncing or has stalled on outdated software. FORK_NEXT makes that distinction possible. On the other hand, the reason future forks are not all bundled together the way FORK_HASH bundles past forks is that a fork already passed can be treated as immutable, whereas nothing about a future fork is final. A fork that has not yet passed can be postponed, and forcing it would split the network. A node may also not know about all future forks because its software has not been updated for a while. So only the single next fork is announced, and even that is not used as grounds for rejection until it has actually passed.

Scope limitation: covers only the identifier definition

This EIP defines only the identifier scheme and validation rules, with no functional changes. The same scheme can be used in several networking protocols, such as the eth/6x handshake (distinguishing Ethereum from Ethereum Classic) and discovery (rejecting without connecting). It does not address the clean separation case of a three-way fork, where the network splits into three at the same future block. Separating forked parties from each other would require case-by-case special handling, and the original text explains that by not addressing this, the proposal stays practical and simple, and does not make it too easy to split off from mainnet.

Status and Implementation

The status of this EIP is Final, a finalized standard. The original text provides, as of the time of writing (as of the Petersburg fork), a test suite covering all possible fork IDs for mainnet, Ropsten, Rinkeby, and Görli, acceptance/rejection test cases for a mainnet node against various remote identifiers, and RLP encoding tests. For example, the identifier for a mainnet Petersburg node (head 7987396) is {Hash: 0x668db0af, Next: 0}. The implementation lives in the core/forkid package of go-ethereum (Geth).

Original EIP · ← EIPs