Skip to main content
netspecsEthereum networking

3.1.5.Consensus discovery

Stage
Current
Version
Pulled
consensus-specsp2p-interface.mdconsensus-specsp2p-interface.mdconsensus-specsp2p-interface.mdconsensus-specsvalidator.md
Related
PeerDAS custodyNode bootstrapFork identificationdiscv5DNS discoveryPeering and scoringlibp2p stack

Consensus discovery requirements

The discovery domain: discv5

Discovery Version 5 (discv5) (Protocol version v5.1) is used for peer discovery.

discv5 is a standalone protocol, running on UDP on a dedicated port, meant for peer discovery only. discv5 supports self-certified, flexible peer records (ENRs) and topic-based advertisement, both of which are (or will be) requirements in this context.

Integration into libp2p stacks

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.

ENR structure

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.

eth2 field

[Modified in Fulu:EIP7892]

Note: The structure of ENRForkID has not changed but the field value computations have changed. Unless explicitly mentioned here, all specifications from phase0/p2p-interface.md#eth2-field carry over.

ENRs MUST carry a generic eth2 key with an 16-byte value of the node’s current fork digest, next fork version, and next fork epoch to ensure connections are made with peers on the intended Ethereum network.

KeyValue
eth2SSZ ENRForkID

Specifically, the value of the eth2 key MUST be the following SSZ encoded object (ENRForkID):

(
  fork_digest: ForkDigest
  next_fork_version: Version
  next_fork_epoch: Epoch
)

The fields of ENRForkID are defined as:

  • fork_digest is compute_fork_digest(genesis_validators_root, epoch) where:
    • genesis_validators_root is the static Root found in state.genesis_validators_root.
    • epoch is the node’s current epoch defined by the wall-clock time (not necessarily the epoch to which the node is sync).
  • next_fork_version is the fork version corresponding to the next planned fork at a future epoch. The fork version will only change for regular forks, not BPO forks. Note that it is possible for the blob schedule to define a change at the same epoch as a regular fork; this situation would be considered a regular fork. If no future fork is planned, set next_fork_version = current_fork_version to signal this fact.
  • next_fork_epoch is the epoch at which the next fork (whether a regular fork or a BPO fork) is planned. If no future fork is planned, set next_fork_epoch = FAR_FUTURE_EPOCH to signal this fact.
Genesis and peer compatibility

Note: fork_digest is composed of values that are not known until the genesis block/state are available. Due to this, clients SHOULD NOT form ENRs and begin peer discovery until genesis values are known. One notable exception to this rule is the distribution of bootnode ENRs prior to genesis. In this case, bootnode ENRs SHOULD be initially distributed with eth2 field set as ENRForkID(fork_digest=compute_fork_digest(b'\x00'*32, GENESIS_EPOCH), next_fork_version=GENESIS_FORK_VERSION, next_fork_epoch=FAR_FUTURE_EPOCH). After genesis values are known, the bootnodes SHOULD update ENRs to participate in normal discovery operations.

Clients SHOULD connect to peers with fork_digest, next_fork_version, and next_fork_epoch that match local values.

Clients MAY connect to peers with the same fork_digest but a different next_fork_version/next_fork_epoch. Unless ENRForkID is manually updated to matching prior to the earlier next_fork_epoch of the two clients, these connecting clients will be unable to successfully interact starting at the earlier next_fork_epoch.

Attestation subnet bitfield

The ENR attnets entry signifies the attestation subnet bitfield with the following form to more easily discover peers participating in particular attestation gossip subnets.

KeyValue
attnetsSSZ BitVector[ATTESTATION_SUBNET_COUNT]

If a node’s MetaData.attnets has any non-zero bit, the ENR MUST include the attnets entry with the same value as MetaData.attnets.

If a node’s MetaData.attnets is composed of all zeros, the ENR MAY optionally include the attnets entry or leave it out entirely.

Sync committee bitfield

An additional bitfield is added to the ENR under the key syncnets to facilitate sync committee subnet discovery. The length of this bitfield is SYNC_COMMITTEE_SUBNET_COUNT where each bit corresponds to a distinct subnet_id for a specific sync committee subnet. The ith bit is set in this bitfield if the validator is currently subscribed to the sync_committee_{i} topic.

KeyValue
syncnetsSSZ BitVector[SYNC_COMMITTEE_SUBNET_COUNT]

See the validator document for further details on how the new bits are used.

Custody group count and next fork digest once Fulu is scheduled

A new field is added to the ENR under the key cgc to facilitate custody data column discovery. This new field MUST be added once FULU_FORK_EPOCH is assigned any value other than FAR_FUTURE_EPOCH.

KeyValue
cgcCustody group count, Uint64 big endian integer with no leading zero bytes (0 is encoded as empty byte string)
Next fork digest

A new entry is added to the ENR under the key nfd, short for next fork digest. This entry communicates the digest of the next scheduled fork, regardless of whether it is a regular or a Blob-Parameters-Only fork. This new entry MUST be added once FULU_FORK_EPOCH is assigned any value other than FAR_FUTURE_EPOCH. Adding this entry prior to the Fulu upgrade will not impact peering as nodes will ignore unknown ENR entries and nfd mismatches do not cause disconnects.

If no next fork is scheduled, the nfd entry contains the default value for the type (i.e., the SSZ representation of a zero-filled array).

KeyValue
nfdSSZ Bytes4 ForkDigest

When discovering and interfacing with peers, nodes MUST evaluate nfd alongside their existing consideration of the ENRForkID::next_* fields under the eth2 key, to form a more accurate view of the peer’s intended next fork for the purposes of sustained peering. If there is a mismatch, the node MUST NOT disconnect before the fork boundary, but it MAY disconnect at/after the fork boundary.

Nodes unprepared to follow the Fulu upgrade will be unaware of nfd entries. However, their existing comparison of eth2 entries (concretely next_fork_epoch) is sufficient to detect upcoming divergence.

Finding attestation subnet peers for upcoming duties

The beacon-chain shufflings are designed to provide a minimum of 1 epoch lookahead on the validator’s upcoming committee assignments for attesting dictated by the shuffling and slot. Note that this lookahead does not apply to proposing, which must be checked during the epoch in question.

get_committee_assignment should be called at the start of each epoch to get the assignment for the next epoch (current_epoch + 1). A validator should plan for future assignments by noting their assigned attestation slot and joining the committee index attestation subnet related to their committee assignment.

Specifically a validator should:

  • Call _, committee_index, _ = get_committee_assignment(state, next_epoch, validator_index) when checking for next epoch assignments.
  • Calculate the committees per slot for the next epoch: committees_per_slot = get_committee_count_per_slot(state, next_epoch)
  • Calculate the subnet index: subnet_id = compute_subnet_for_attestation(committees_per_slot, slot, committee_index)
  • Find peers of the pubsub topic beacon_attestation_{subnet_id}.
    • If an insufficient number of current peers are subscribed to the topic, the validator must discover new peers on this topic. Via the discovery protocol, find peers with an ENR containing the attnets entry such that ENR["attnets"][subnet_id] == True. Then validate that the peers are still persisted on the desired topic by requesting GetMetaData and checking the resulting attnets field.
    • If the validator is assigned to be an aggregator for the slot (see is_aggregator()), then subscribe to the topic.

Note: If the validator is not assigned to be an aggregator, the validator only needs sufficient number of peers on the topic to be able to publish messages. The validator does not need to subscribe and listen to all messages on the topic.