Notice: This document is a work-in-progress for researchers and implementers.
- Introduction
- Modifications in Fulu
This document contains the consensus-layer networking specification for Fulu.
The specification of these changes continues in the same format as the network specifications of previous upgrades, and assumes them as pre-requisite.
Name | Value | Description |
---|---|---|
KZG_COMMITMENTS_INCLUSION_PROOF_DEPTH |
uint64(floorlog2(get_generalized_index(BeaconBlockBody, 'blob_kzg_commitments'))) (= 4) |
Merkle proof index for blob_kzg_commitments |
[New in Fulu:EIP7594]
Name | Value | Description |
---|---|---|
DATA_COLUMN_SIDECAR_SUBNET_COUNT |
128 |
The number of data column sidecar subnets used in the gossipsub protocol |
MAX_REQUEST_DATA_COLUMN_SIDECARS |
MAX_REQUEST_BLOCKS_DENEB * NUMBER_OF_COLUMNS |
Maximum number of data column sidecars in a single request |
MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS |
2**12 (= 4096 epochs, ~18 days) |
The minimum epoch range over which a node must serve data column sidecars |
MAX_REQUEST_BLOB_SIDECARS_FULU |
MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK_FULU |
Maximum number of blob sidecars in a single request |
class DataColumnIdentifier(Container):
block_root: Root
index: ColumnIndex
def verify_data_column_sidecar(sidecar: DataColumnSidecar) -> bool:
"""
Verify if the data column sidecar is valid.
"""
# The sidecar index must be within the valid range
if sidecar.index >= NUMBER_OF_COLUMNS:
return False
# A sidecar for zero blobs is invalid
if len(sidecar.kzg_commitments) == 0:
return False
# The column length must be equal to the number of commitments/proofs
if len(sidecar.column) != len(sidecar.kzg_commitments) or len(sidecar.column) != len(sidecar.kzg_proofs):
return False
return True
def verify_data_column_sidecar_kzg_proofs(sidecar: DataColumnSidecar) -> bool:
"""
Verify if the KZG proofs are correct.
"""
# The column index also represents the cell index
cell_indices = [CellIndex(sidecar.index)] * len(sidecar.column)
# Batch verify that the cells match the corresponding commitments and proofs
return verify_cell_kzg_proof_batch(
commitments_bytes=sidecar.kzg_commitments,
cell_indices=cell_indices,
cells=sidecar.column,
proofs_bytes=sidecar.kzg_proofs,
)
def verify_data_column_sidecar_inclusion_proof(sidecar: DataColumnSidecar) -> bool:
"""
Verify if the given KZG commitments included in the given beacon block.
"""
gindex = get_subtree_index(get_generalized_index(BeaconBlockBody, 'blob_kzg_commitments'))
return is_valid_merkle_branch(
leaf=hash_tree_root(sidecar.kzg_commitments),
branch=sidecar.kzg_commitments_inclusion_proof,
depth=KZG_COMMITMENTS_INCLUSION_PROOF_DEPTH,
index=gindex,
root=sidecar.signed_block_header.message.body_root,
)
def compute_subnet_for_data_column_sidecar(column_index: ColumnIndex) -> SubnetID:
return SubnetID(column_index % DATA_COLUMN_SIDECAR_SUBNET_COUNT)
The MetaData
stored locally by clients is updated with an additional field to communicate the custody subnet count.
(
seq_number: uint64
attnets: Bitvector[ATTESTATION_SUBNET_COUNT]
syncnets: Bitvector[SYNC_COMMITTEE_SUBNET_COUNT]
custody_group_count: uint64 # cgc
)
Where
seq_number
,attnets
, andsyncnets
have the same meaning defined in the Altair document.custody_group_count
represents the node's custody group count. Clients MAY reject peers with a value less thanCUSTODY_REQUIREMENT
.
Some gossip meshes are upgraded in the Fulu fork to support upgraded types.
Updated validation
- [REJECT] The length of KZG commitments is less than or equal to the limitation defined in Consensus Layer --
i.e. validate that
len(body.signed_beacon_block.message.blob_kzg_commitments) <= MAX_BLOBS_PER_BLOCK_FULU
blob_sidecar_{subnet_id}
is deprecated.
This topic is used to propagate column sidecars, where each column maps to some subnet_id
.
The type of the payload of this topic is DataColumnSidecar
.
The following validations MUST pass before forwarding the sidecar: DataColumnSidecar
on the network, assuming the alias block_header = sidecar.signed_block_header.message
:
- [REJECT] The sidecar is valid as verified by
verify_data_column_sidecar(sidecar)
. - [REJECT] The sidecar is for the correct subnet -- i.e.
compute_subnet_for_data_column_sidecar(sidecar.index) == subnet_id
. - [IGNORE] The sidecar is not from a future slot (with a
MAXIMUM_GOSSIP_CLOCK_DISPARITY
allowance) -- i.e. validate thatblock_header.slot <= current_slot
(a client MAY queue future sidecars for processing at the appropriate slot). - [IGNORE] The sidecar is from a slot greater than the latest finalized slot -- i.e. validate that
block_header.slot > compute_start_slot_at_epoch(state.finalized_checkpoint.epoch)
- [REJECT] The proposer signature of
sidecar.signed_block_header
, is valid with respect to theblock_header.proposer_index
pubkey. - [IGNORE] The sidecar's block's parent (defined by
block_header.parent_root
) has been seen (via gossip or non-gossip sources) (a client MAY queue sidecars for processing once the parent block is retrieved). - [REJECT] The sidecar's block's parent (defined by
block_header.parent_root
) passes validation. - [REJECT] The sidecar is from a higher slot than the sidecar's block's parent (defined by
block_header.parent_root
). - [REJECT] The current finalized_checkpoint is an ancestor of the sidecar's block -- i.e.
get_checkpoint_block(store, block_header.parent_root, store.finalized_checkpoint.epoch) == store.finalized_checkpoint.root
. - [REJECT] The sidecar's
kzg_commitments
field inclusion proof is valid as verified byverify_data_column_sidecar_inclusion_proof(sidecar)
. - [REJECT] The sidecar's column data is valid as verified by
verify_data_column_sidecar_kzg_proofs(sidecar)
. - [IGNORE] The sidecar is the first sidecar for the tuple
(block_header.slot, block_header.proposer_index, sidecar.index)
with valid header signature, sidecar inclusion proof, and kzg proof. - [REJECT] The sidecar is proposed by the expected
proposer_index
for the block's slot in the context of the current shuffling (defined byblock_header.parent_root
/block_header.slot
). If theproposer_index
cannot immediately be verified against the expected shuffling, the sidecar MAY be queued for later processing while proposers for the block's branch are calculated -- in such a case do notREJECT
, insteadIGNORE
this message.
Note: In the verify_data_column_sidecar_inclusion_proof(sidecar)
check, for all the sidecars of the same block, it verifies against the same set of kzg_commitments
of the given beacon block. Client can choose to cache the result of the arguments tuple (sidecar.kzg_commitments, sidecar.kzg_commitments_inclusion_proof, sidecar.signed_block_header)
.
Protocol ID: /eth2/beacon_chain/req/blob_sidecars_by_root/3/
[Modified in Fulu:EIP7594]
The <context-bytes>
field is calculated as context = compute_fork_digest(fork_version, genesis_validators_root)
:
fork_version |
Chunk SSZ type |
---|---|
FULU_FORK_VERSION |
fulu.BlobSidecar |
Request Content:
(
List[BlobIdentifier, MAX_REQUEST_BLOB_SIDECARS_FULU]
)
Response Content:
(
List[BlobSidecar, MAX_REQUEST_BLOB_SIDECARS_FULU]
)
Updated validation
No more than MAX_REQUEST_BLOB_SIDECARS_FULU
may be requested at a time.
Protocol ID: /eth2/beacon_chain/req/blob_sidecars_by_range/3/
[Modified in Fulu:EIP7594]
The <context-bytes>
field is calculated as context = compute_fork_digest(fork_version, genesis_validators_root)
:
fork_version |
Chunk SSZ type |
---|---|
FULU_FORK_VERSION |
fulu.BlobSidecar |
Request Content:
(
start_slot: Slot
count: uint64
)
Response Content:
(
List[BlobSidecar, MAX_REQUEST_BLOB_SIDECARS_FULU]
)
Updated validation
Clients MUST respond with at least the blob sidecars of the first blob-carrying block that exists in the range, if they have it, and no more than MAX_REQUEST_BLOB_SIDECARS_FULU
sidecars.
Protocol ID: /eth2/beacon_chain/req/data_column_sidecars_by_root/1/
[New in Fulu:EIP7594]
The <context-bytes>
field is calculated as context = compute_fork_digest(fork_version, genesis_validators_root)
:
fork_version |
Chunk SSZ type |
---|---|
FULU_FORK_VERSION |
fulu.DataColumnSidecar |
Request Content:
(
List[DataColumnIdentifier, MAX_REQUEST_DATA_COLUMN_SIDECARS]
)
Response Content:
(
List[DataColumnSidecar, MAX_REQUEST_DATA_COLUMN_SIDECARS]
)
Requests sidecars by block root and index.
The response is a list of DataColumnIdentifier
whose length is less than or equal to the number of requests.
It may be less in the case that the responding peer is missing blocks or sidecars.
Before consuming the next response chunk, the response reader SHOULD verify the data column sidecar is well-formatted through verify_data_column_sidecar
, has valid inclusion proof through verify_data_column_sidecar_inclusion_proof
, and is correct w.r.t. the expected KZG commitments through verify_data_column_sidecar_kzg_proofs
.
No more than MAX_REQUEST_DATA_COLUMN_SIDECARS
may be requested at a time.
The response MUST consist of zero or more response_chunk
.
Each successful response_chunk
MUST contain a single DataColumnSidecar
payload.
Clients MUST support requesting sidecars since minimum_request_epoch
, where minimum_request_epoch = max(finalized_epoch, current_epoch - MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS, FULU_FORK_EPOCH)
. If any root in the request content references a block earlier than minimum_request_epoch
, peers MAY respond with error code 3: ResourceUnavailable
or not include the data column sidecar in the response.
Clients MUST respond with at least one sidecar, if they have it. Clients MAY limit the number of blocks and sidecars in the response.
Clients SHOULD include a sidecar in the response as soon as it passes the gossip validation rules. Clients SHOULD NOT respond with sidecars related to blocks that fail gossip validation rules. Clients SHOULD NOT respond with sidecars related to blocks that fail the beacon chain state transition
Protocol ID: /eth2/beacon_chain/req/data_column_sidecars_by_range/1/
The <context-bytes>
field is calculated as context = compute_fork_digest(fork_version, genesis_validators_root)
:
fork_version |
Chunk SSZ type |
---|---|
FULU_FORK_VERSION |
fulu.DataColumnSidecar |
Request Content:
(
start_slot: Slot
count: uint64
columns: List[ColumnIndex, NUMBER_OF_COLUMNS]
)
Response Content:
(
List[DataColumnSidecar, MAX_REQUEST_DATA_COLUMN_SIDECARS]
)
Requests data column sidecars in the slot range [start_slot, start_slot + count)
of the given columns
, leading up to the current head block as selected by fork choice.
Before consuming the next response chunk, the response reader SHOULD verify the data column sidecar is well-formatted through verify_data_column_sidecar
, has valid inclusion proof through verify_data_column_sidecar_inclusion_proof
, and is correct w.r.t. the expected KZG commitments through verify_data_column_sidecar_kzg_proofs
.
DataColumnSidecarsByRange
is primarily used to sync data columns that may have been missed on gossip and to sync within the MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS
window.
The request MUST be encoded as an SSZ-container.
The response MUST consist of zero or more response_chunk
.
Each successful response_chunk
MUST contain a single DataColumnSidecar
payload.
Let data_column_serve_range
be [max(current_epoch - MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS, FULU_FORK_EPOCH), current_epoch]
.
Clients MUST keep a record of data column sidecars seen on the epoch range data_column_serve_range
where current_epoch
is defined by the current wall-clock time,
and clients MUST support serving requests of data columns on this range.
Peers that are unable to reply to data column sidecar requests within the
range data_column_serve_range
SHOULD respond with error code 3: ResourceUnavailable
.
Such peers that are unable to successfully reply to this range of requests MAY get descored
or disconnected at any time.
Note: The above requirement implies that nodes that start from a recent weak subjectivity checkpoint
MUST backfill the local data columns database to at least the range data_column_serve_range
to be fully compliant with DataColumnSidecarsByRange
requests.
Note: Although clients that bootstrap from a weak subjectivity checkpoint can begin participating in the networking immediately, other peers MAY disconnect and/or temporarily ban such an un-synced or semi-synced client.
Clients MUST respond with at least the data column sidecars of the first blob-carrying block that exists in the range, if they have it, and no more than MAX_REQUEST_DATA_COLUMN_SIDECARS
sidecars.
Clients MUST include all data column sidecars of each block from which they include data column sidecars.
The following data column sidecars, where they exist, MUST be sent in (slot, column_index)
order.
Slots that do not contain known data columns MUST be skipped, mimicking the behaviour
of the BlocksByRange
request. Only response chunks with known data columns should
therefore be sent.
Clients MAY limit the number of data column sidecars in the response.
The response MUST contain no more than count * NUMBER_OF_COLUMNS
data column sidecars.
Clients MUST respond with data columns sidecars from their view of the current fork choice
-- that is, data column sidecars as included by blocks from the single chain defined by the current head.
Of note, blocks from slots before the finalization MUST lead to the finalized block reported in the Status
handshake.
Clients MUST respond with data column sidecars that are consistent from a single chain within the context of the request.
After the initial data column sidecar, clients MAY stop in the process of responding if their fork choice changes the view of the chain in the context of the request.
Protocol ID: /eth2/beacon_chain/req/metadata/3/
No Request Content.
Response Content:
(
MetaData
)
Requests the MetaData of a peer, using the new MetaData
definition given above that is extended from Altair. Other conditions for the GetMetaData
protocol are unchanged from the Altair p2p networking document.
A new field is added to the ENR under the key cgc
to facilitate custody data column discovery.
Key | Value |
---|---|
cgc |
Custody group count, uint64 big endian integer with no leading zero bytes (0 is encoded as empty byte string) |