Notice: This document is a work-in-progress for researchers and implementers.
This document is the specification for the Portal Network overlay network that supports the on-demand availability of Beacon Chain data.
A beacon chain light client could keep track of the chain of beacon block headers by performing Light client state updates following the light client sync protocol. The LightClientBootstrap structure allow setting up a LightClientStore with the initial sync committee and block header from a user-configured trusted block root.
Once the client establishes a recent header, it could sync to other headers by processing objects of type LightClientUpdate, LightClientFinalityUpdate and LightClientOptimisticUpdate. These data types allow a client to stay up-to-date with the beacon chain.
To verify canonicalness of an execution block header older than ~27 hours, we need the ongoing BeaconState accumulator (state.historical_summaries) which stores Merkle roots of recent history logs.
The Beacon Chain network is a Kademlia DHT that forms an overlay network on top of
the Discovery v5 network. The term overlay network means that the beacon chain network operates
with its routing table independent of the base Discovery v5 routing table and uses the extensible TALKREQ and TALKRESP messages from the base Discovery v5 protocol for communication.
The TALKREQ and TALKRESP protocol messages are application-level messages whose contents are specific to the Beacon Chain Light Client network. We specify these messages below.
The Beacon Chain network uses a modified version of the routing table structure from the Discovery v5 network and the lookup algorithm from section 2.3 of the Kademlia paper.
- LightClientBootstrap
- LightClientUpdate
- LightClientFinalityUpdate
- LightClientOptimisticUpdate
- HistoricalSummaries
Light client data types are specified in light client sync protocol.
The network supports the following mechanisms for data retrieval:
LightClientBootstrapstructure by a post-Altair beacon block root.LightClientUpdatesByRange- requests theLightClientUpdateinstances in the sync committee period range [start_period, start_period + count), leading up to the current head sync committee period as selected by fork choice.- The latest
LightClientFinalityUpdateknown by a peer. - The latest
LightClientOptimisticUpdateknown by a peer. - The latest
HistoricalSummariesknown by a peer.
The beacon chain network uses the stock XOR distance metric defined in the portal wire protocol specification.
The beacon chain network uses the SHA256 Content ID derivation function from the portal wire protocol specification.
The Portal wire protocol is used as the wire protocol for the Beacon Chain Light Client network.
As specified in the Protocol identifiers section of the Portal wire protocol, the protocol field in the TALKREQ message MUST contain the value of 0x501A.
The beacon chain network supports the following protocol messages:
Ping-PongFind Nodes-NodesFind Content-Found ContentOffer-Accept
In the beacon chain network the custom_payload field of the Ping and Pong messages is the serialization of an SSZ Container specified as custom_data:
custom_data = Container(data_radius: uint256)
custom_payload = serialize(custom_data)The Beacon Chain Network uses the standard routing table structure from the Portal Wire Protocol.
Nodes running the beacon chain network MUST store and provide all beacon light client content for the range as is specified by the consensus light client specifications: https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/full-node.md#deriving-light-client-data
This means that data radius and the concept of closeness to data is not applicable for this content.
When a node cannot fulfill a request for any of this data it SHOULD return an empty list of ENRs. It MAY return a list of ENRs of nodes that have provided this data in the past.
When a node gossips any of this data, it MUST use random gossip instead of neighborhood gossip.
The Beacon Chain Network includes one additional piece of node state that should be tracked. Nodes must track the data_radius
from the Ping and Pong messages for other nodes in the network. This value is a 256 bit integer and represents the data that
a node is "interested" in.
We define the following function to determine whether node in the network should be interested in a piece of content:
interested(node, content) = distance(node.id, content.id) <= node.radiusA node is expected to maintain radius information for each node in its local node table. A node's radius value may fluctuate as the contents of its local key-value store change.
A node should track their own radius value and provide this value in all Ping or Pong messages it sends to other nodes.
The beacon chain DHT stores the following data items:
- LightClientBootstrap
- LightClientUpdate
The following data objects are ephemeral and we store only the latest values:
- LightClientFinalityUpdate
- LightClientOptimisticUpdate
- HistoricalSummaries
We use the following constants from the beacon chain specs which are used in the various data type definitions:
# Maximum number of `LightClientUpdate` instances in a single request
# Defined in https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/p2p-interface.md#configuration
MAX_REQUEST_LIGHT_CLIENT_UPDATES = 2**7 # = 128
# Maximum number of `HistoricalSummary` records
# Defined in https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#state-list-lengths
HISTORICAL_ROOTS_LIMIT = 2**24 # = 16,777,2164-byte fork digest for the current beacon chain version and genesis_validators_root.
light_client_bootstrap_key = Container(block_hash: Bytes32)
selector = 0x10
content = ForkDigest + SSZ.serialize(LightlientBootstrap)
content_key = selector + SSZ.serialize(light_client_bootstrap_key)light_client_update_keys = Container(start_period: uint64, count: uint64)
selector = 0x11
content = List(ForkDigest + LightClientUpdate, limit=MAX_REQUEST_LIGHT_CLIENT_UPDATES)
content_key = selector + SSZ.serialize(light_client_update_keys)If a node cannot provide one of the
LightClientUpdateobjects in the the requested range it MUST NOT reply any content.
light_client_finality_update_key = Container(finalized_slot: uint64)
selector = 0x12
content = ForkDigest + SSZ.serialize(light_client_finality_update)
content_key = selector + SSZ.serialize(light_client_finality_update_key)The
LightClientFinalityUpdateobjects are ephemeral and only the latest is of use to the node. The content key requires thefinalized_slotto be provided so that this object can be more efficiently gossiped. Nodes should decide to reject anLightClientFinalityUpdatein case it is not newer than the one they already have. ForFindContentrequests, a node will either know the last previous finalized slot, if it has been following the updates, or it will have to guess slots that are potentially finalized.
light_client_optimistic_update_key = Container(optimistic_slot: uint64)
selector = 0x13
content = ForkDigest + SSZ.serialize(light_client_optimistic_update)
content_key = selector + SSZ.serialize(light_client_optimistic_update_key)The
LightClientOptimisticUpdateobjects are ephemeral and only the latest is of use to the node. The content key requires theoptimistic_slot(corresponding to thesignature_slotin the the update) to be provided so that this object can be more efficiently gossiped. Nodes should decide to reject anLightClientOptimisticUpdatein case it is not newer than the one they already have. ForFindContentrequests, a node should compute the current slot based on its local clock and then use that slot as a starting point for retrieving the most recent update.
Latest HistoricalSummariesWithProof object is stored in the network every epoch, even though the historical_summaries only updates every period (8192 slots). This is done to have an up to date proof every epoch, which makes it easier to verify the historical_summaries when starting the beacon light client sync.
HistoricalSummariesProof = Vector[Bytes32, 5]
historical_summaries_with_proof = HistoricalSummariesWithProof(
epoch: uint64,
# HistoricalSummary object is defined in consensus specs:
# https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/beacon-chain.md#historicalsummary.
historical_summaries: List(HistoricalSummary, limit=HISTORICAL_ROOTS_LIMIT),
proof: HistoricalSummariesProof
)
historical_summaries_key = Container(epoch: uint64)
selector = 0x14
content = ForkDigest + SSZ.serialize(historical_summaries_with_proof)
content_key = selector + SSZ.serialize(historical_summaries_key)A node SHOULD return the latest
HistoricalSummariesWithProofobject it has in response to aFindContentrequest. If a node cannot provide the requested or newerHistoricalSummariesWithProofobject, it MUST NOT reply with any content.
We use the term random gossip to refer to the process through which content is disseminated to a random set DHT nodes.
The process works as follows:
- A DHT node is offered piece of content that is specified to be gossiped via random gossip.
- The node selects a random node from a random bucket and does this for
nnodes. - The node offers the content to the
nselected nodes.
While still light client syncing a node SHOULD only allow to store an offered LightClientBootstrap that it knows to be canonical.
That is, a bootstrap which it can verify as it maps to a known trusted-block-root.
E.g. trusted-block-root(s) provided through client config or pre-loaded in the client.
Once a node is light client synced, it can verify a new LightClientBootstrap and then store and re-gossip it on successful verification.
While still light client syncing a node SHOULD NOT store any offered LightClientUpdate. It SHOULD retrieve the updates required to sync and store those when verified.
Once a node is light client synced, it can verify a new LightClientUpdate and then store and re-gossip it on successful verification.
Validating LightClientFinalityUpdate and LightClientOptimisticUpdate follows the gossip domain(gossipsub) consensus specs.