Gloas -- Networking
Note: This document is a work-in-progress for researchers and implementers.
Introduction
This document contains the consensus-layer networking specifications for Gloas.
The specification of these changes continues in the same format as the network
specifications of previous upgrades, and assumes them as pre-requisite.
Preset
Type-specific SSZ bounds
[New in Gloas:EIP7688]
These constants supersede
type-specific SSZ bounds
for the corresponding variable-size libp2p messages.
| Name |
Value |
MAX_SIGNED_AGGREGATE_AND_PROOF_SIZE |
Uint64(16829) (= ~16 KiB) |
MAX_ATTESTER_SLASHING_SIZE |
Uint64(2097616) (= ~2 MiB) |
MAX_DATA_COLUMN_SIDECAR_SIZE |
Uint64(8585272) (= ~8 MiB) |
MAX_SIGNED_EXECUTION_PAYLOAD_BID_SIZE |
Uint64(196932) (= ~192 KiB) |
Configuration
| Name |
Value |
MAX_REQUEST_PAYLOADS |
Uint64(2**7) (= 128) |
Types
Modified DataColumn
| # [Modified in Gloas:EIP7688]
class DataColumn(ProgressiveList[Cell]):
"""
A column of the extended blob data matrix, with at most one cell per blob.
"""
|
Modified KZGProofs
| # [Modified in Gloas:EIP7688]
class KZGProofs(ProgressiveList[KZGProof]):
"""
The KZG cell proofs of the cells held by a data column.
"""
|
New ExecutionPayloadEnvelopeRoots
| class ExecutionPayloadEnvelopeRoots(List[Root, MAX_REQUEST_PAYLOADS]):
"""
The beacon block roots of the payload envelopes requested in an
``ExecutionPayloadEnvelopesByRoot`` request.
"""
|
New SignedExecutionPayloadEnvelopes
| class SignedExecutionPayloadEnvelopes(List[SignedExecutionPayloadEnvelope, MAX_REQUEST_PAYLOADS]):
"""
Signed execution payload envelopes returned in an
``ExecutionPayloadEnvelopesByRange`` or
``ExecutionPayloadEnvelopesByRoot`` response.
"""
|
Containers
Modified DataColumnSidecar
Note: The signed_block_header, kzg_commitments, and
kzg_commitments_inclusion_proof fields have been removed from
DataColumnSidecar in Gloas as header and inclusion proof verifications are no
longer required in Gloas. The KZG commitments are now located at
block.body.signed_execution_payload_bid.message.blob_kzg_commitments where
block is the BeaconBlock associated with beacon_block_root.
| class DataColumnSidecar(Container):
index: ColumnIndex
# [Modified in Gloas:EIP7688]
column: DataColumn
# [Modified in Gloas:EIP7732]
# Removed `kzg_commitments`
# [Modified in Gloas:EIP7688]
kzg_proofs: KZGProofs
# [Modified in Gloas:EIP7732]
# Removed `signed_block_header`
# [Modified in Gloas:EIP7732]
# Removed `kzg_commitments_inclusion_proof`
# [New in Gloas:EIP7732]
slot: Slot
# [New in Gloas:EIP7732]
beacon_block_root: Root
|
New ProposerPreferences
[New in Gloas:EIP7732]
| class ProposerPreferences(Container):
dependent_root: Root
proposal_slot: Slot
validator_index: ValidatorIndex
fee_recipient: ExecutionAddress
target_gas_limit: Uint64
|
New SignedProposerPreferences
[New in Gloas:EIP7732]
| class SignedProposerPreferences(Container):
message: ProposerPreferences
signature: BLSSignature
|
Helpers
Modified Seen
| @dataclass
class Seen:
proposer_slots: Set[Tuple[Slot, ValidatorIndex]]
aggregator_epochs: Set[Tuple[Epoch, ValidatorIndex]]
aggregate_data_roots: Dict[Tuple[Root, CommitteeIndex], Set[Tuple[Boolean, ...]]]
voluntary_exit_indices: Set[ValidatorIndex]
proposer_slashing_indices: Set[ValidatorIndex]
attester_slashing_indices: Set[ValidatorIndex]
attestation_validator_epochs: Set[Tuple[Epoch, ValidatorIndex]]
sync_contribution_aggregator_slots: Set[Tuple[Slot, ValidatorIndex, Uint64]]
sync_contribution_data: Dict[Tuple[Slot, Root, Uint64], Set[Tuple[Boolean, ...]]]
sync_message_validator_slots: Set[Tuple[Slot, ValidatorIndex, Uint64]]
bls_to_execution_change_indices: Set[ValidatorIndex]
# [Modified in Gloas:EIP7732]
data_column_sidecar_tuples: Set[Tuple[Root, ColumnIndex]]
# [Modified in Gloas:EIP7732]
# Removed `partial_data_column_headers`
# [New in Gloas:EIP7732]
execution_payloads: Dict[Hash32, ExecutionPayload]
# [New in Gloas:EIP7732]
execution_payload_envelopes: Set[Tuple[Root, BuilderIndex]]
# [New in Gloas:EIP7732]
payload_attestation_validators: Set[Tuple[Slot, ValidatorIndex]]
# [New in Gloas:EIP7732]
execution_payload_bids: Set[Tuple[Slot, Hash32, Root, BuilderIndex]]
# [New in Gloas:EIP7732]
best_execution_payload_bid: Dict[Tuple[Slot, Hash32, Root], Gwei]
# [New in Gloas:EIP7732]
proposer_preferences: Dict[Tuple[Root, Slot], ProposerPreferences]
|
Modified compute_fork_version
| def compute_fork_version(epoch: Epoch) -> Version:
"""
Return the fork version at the given ``epoch``.
"""
if epoch >= GLOAS_FORK_EPOCH:
return GLOAS_FORK_VERSION
if epoch >= FULU_FORK_EPOCH:
return FULU_FORK_VERSION
if epoch >= ELECTRA_FORK_EPOCH:
return ELECTRA_FORK_VERSION
if epoch >= DENEB_FORK_EPOCH:
return DENEB_FORK_VERSION
if epoch >= CAPELLA_FORK_EPOCH:
return CAPELLA_FORK_VERSION
if epoch >= BELLATRIX_FORK_EPOCH:
return BELLATRIX_FORK_VERSION
if epoch >= ALTAIR_FORK_EPOCH:
return ALTAIR_FORK_VERSION
return GENESIS_FORK_VERSION
|
Modified verify_data_column_sidecar_kzg_proofs
| def verify_data_column_sidecar_kzg_proofs(
sidecar: DataColumnSidecar,
# [New in Gloas:EIP7732]
kzg_commitments: BlobKZGCommitments,
) -> 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 kzg.verify_cell_kzg_proof_batch(
# [Modified in Gloas:EIP7732]
commitments_bytes=kzg_commitments,
cell_indices=cell_indices,
cells=sidecar.column,
proofs_bytes=sidecar.kzg_proofs,
)
|
Modified verify_data_column_sidecar
| def verify_data_column_sidecar(
sidecar: DataColumnSidecar,
# [New in Gloas:EIP7732]
kzg_commitments: BlobKZGCommitments,
) -> 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
# [Modified in Gloas:EIP7732]
# A sidecar for zero blobs is invalid
if len(sidecar.column) == 0:
return False
# [Modified in Gloas:EIP7732]
# The column length must be equal to the number of commitments
if len(sidecar.column) != len(kzg_commitments):
return False
# The column length must be equal to the number of proofs
if len(sidecar.column) != len(sidecar.kzg_proofs):
return False
return True
|
New is_current_or_next_slot
| def is_current_or_next_slot(
store: Store,
slot: Slot,
current_time_ms: Uint64,
) -> bool:
"""
Check if the given slot is the current slot or the next slot
(with MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance).
"""
is_current = is_current_slot(store, slot, current_time_ms)
is_next = is_current_slot(store, Slot(slot - 1), current_time_ms)
return is_current or is_next
|
New is_past_slot
| def is_past_slot(
store: Store,
slot: Slot,
current_time_ms: Uint64,
) -> bool:
"""
Check if the given slot is in the past
(with MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance).
"""
slot_time_ms = compute_time_at_slot_ms(store, slot)
return current_time_ms > slot_time_ms + MAXIMUM_GOSSIP_CLOCK_DISPARITY
|
New is_gas_limit_target_compatible
| def is_gas_limit_target_compatible(
parent_gas_limit: Uint64, gas_limit: Uint64, target_gas_limit: Uint64
) -> bool:
"""
Check if ``gas_limit`` is compatible with ``target_gas_limit`` under the
EIP-1559 transition rule from ``parent_gas_limit``.
"""
max_gas_limit_difference = max(parent_gas_limit // 1024, 1) - 1
min_gas_limit = parent_gas_limit - max_gas_limit_difference
max_gas_limit = parent_gas_limit + max_gas_limit_difference
if target_gas_limit < min_gas_limit:
return gas_limit == min_gas_limit
if target_gas_limit > max_gas_limit:
return gas_limit == max_gas_limit
return gas_limit == target_gas_limit
|
New is_bid_compatible_with_head
| def is_bid_compatible_with_head(store: Store, bid: ExecutionPayloadBid) -> bool:
"""
Check if ``bid`` is compatible with the head branch.
"""
head_node = get_head(store)
head_block = store.blocks[head_node.root]
head_bid = head_block.body.signed_execution_payload_bid.message
builds_on_parent_block = bid.parent_block_root == head_block.parent_root
builds_on_parent_payload = bid.parent_block_hash == head_bid.parent_block_hash
if builds_on_parent_block and builds_on_parent_payload:
return True
if bid.parent_block_root != head_node.root:
return False
builds_on_head_payload = bid.parent_block_hash == head_bid.block_hash
if should_build_on_full(store, head_node, bid.slot):
return builds_on_head_payload
return builds_on_parent_payload
|
New is_valid_dependent_root
| def is_valid_dependent_root(store: Store, root: Root, epoch: Epoch) -> bool:
"""
Check if the block with the given ``root`` is a possible dependent block
for the given ``epoch``, meaning that on some branch it is, or could
become, the latest block prior to the start of the epoch.
"""
epoch_start_slot = compute_start_slot_at_epoch(epoch)
for block in store.blocks.values():
if block.parent_root == root:
if block.slot >= epoch_start_slot:
return True
if root == get_head(store).root:
return True
return False
|
New verify_attestation_payload_status
| def verify_attestation_payload_status(
store: Store,
data: AttestationData,
block_payload_statuses: Dict[Root, PayloadValidationStatus],
) -> None:
"""
Verify that the attested payload status is consistent with the block's payload.
Raises GossipIgnore or GossipReject on validation failure.
"""
block_root = data.beacon_block_root
block = store.blocks[block_root]
# [REJECT] For same-slot attestations, the payload cannot yet be present
if block.slot == data.slot and data.index != 0:
raise GossipReject("same-slot attestation must attest with index 0")
if data.index != 1:
return
# [IGNORE] The corresponding execution payload envelope has been seen and verified
# (MAY queue attestations for processing once the payload is retrieved and
# SHOULD request the payload envelope via ExecutionPayloadEnvelopesByRoot
# using data.beacon_block_root)
if not is_payload_verified(store, block_root):
raise GossipIgnore("execution payload envelope has not been seen")
# [IGNORE] The attested execution payload is optimistic
payload_status = block_payload_statuses.get(block_root, PAYLOAD_STATUS_NOT_VALIDATED)
if payload_status == PAYLOAD_STATUS_NOT_VALIDATED:
raise GossipIgnore("attested payload is optimistic")
# [REJECT] The attested execution payload is processed and invalid
if payload_status == PAYLOAD_STATUS_INVALIDATED:
raise GossipReject("attested payload is invalid")
|
New verify_block_body_operation_limits
| def verify_block_body_operation_limits(body: BeaconBlockBody) -> None:
"""
Verify that each block body operation count is within its limit.
Raises GossipReject on validation failure.
"""
# [REJECT] The proposer slashing count is within the limit
if len(body.proposer_slashings) > MAX_PROPOSER_SLASHINGS:
raise GossipReject("too many proposer slashings")
# [REJECT] The attester slashing count is within the limit
if len(body.attester_slashings) > MAX_ATTESTER_SLASHINGS_ELECTRA:
raise GossipReject("too many attester slashings")
# [REJECT] The attestation count is within the limit
if len(body.attestations) > MAX_ATTESTATIONS_ELECTRA:
raise GossipReject("too many attestations")
# [REJECT] The block contains no deposits
if len(body.deposits) != 0:
raise GossipReject("block must not contain deposits")
# [REJECT] The voluntary exit count is within the limit
if len(body.voluntary_exits) > MAX_VOLUNTARY_EXITS:
raise GossipReject("too many voluntary exits")
# [REJECT] The BLS to execution change count is within the limit
if len(body.bls_to_execution_changes) > MAX_BLS_TO_EXECUTION_CHANGES:
raise GossipReject("too many bls to execution changes")
# [REJECT] The payload attestation count is within the limit
if len(body.payload_attestations) > MAX_PAYLOAD_ATTESTATIONS:
raise GossipReject("too many payload attestations")
|
New verify_execution_requests_limits
| def verify_execution_requests_limits(execution_requests: ExecutionRequests) -> None:
"""
Verify that each execution request count is within its limit.
Raises GossipReject on validation failure.
"""
# [REJECT] The withdrawal request count is within the limit
if len(execution_requests.withdrawals) > MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD:
raise GossipReject("too many withdrawal requests")
# [REJECT] The consolidation request count is within the limit
if len(execution_requests.consolidations) > MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD:
raise GossipReject("too many consolidation requests")
# [REJECT] The builder deposit request count is within the limit
if len(execution_requests.builder_deposits) > MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD:
raise GossipReject("too many builder deposit requests")
# [REJECT] The builder exit request count is within the limit
if len(execution_requests.builder_exits) > MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD:
raise GossipReject("too many builder exit requests")
|
The gossip domain: gossipsub
Some gossip meshes are upgraded in Gloas to support upgraded types.
Topics and messages
Topics follow the same specification as in prior upgrades.
The beacon_block topic is updated to support the modified type
| Name |
Message Type |
beacon_block |
SignedBeaconBlock |
The new topics along with the type of the data field of a gossipsub message
are given in this table:
| Name |
Message Type |
execution_payload_bid |
SignedExecutionPayloadBid |
execution_payload |
SignedExecutionPayloadEnvelope |
payload_attestation_message |
PayloadAttestationMessage |
proposer_preferences |
SignedProposerPreferences |
Global topics
Modified beacon_block
Note: This function is modified per EIP-7732. The execution payload is no
longer carried inside BeaconBlock. As a result, all validations referring to
block.body.execution_payload are removed and replaced with validations of the
bid carried at block.body.signed_execution_payload_bid.message.
| def validate_beacon_block_gossip(
seen: Seen,
store: Store,
state: BeaconState,
signed_beacon_block: SignedBeaconBlock,
current_time_ms: Uint64,
# [Modified in Gloas:EIP7732]
# Removed `block_payload_statuses`
) -> None:
"""
Validate a SignedBeaconBlock for gossip propagation.
Raises GossipIgnore or GossipReject on validation failure.
"""
block = signed_beacon_block.message
bid = block.body.signed_execution_payload_bid.message
# [IGNORE] The block is not from a future slot
# (MAY be queued for processing at the appropriate slot)
if is_future_slot(store, block.slot, current_time_ms):
raise GossipIgnore("block is from a future slot")
# [IGNORE] The block is from a slot greater than the latest finalized slot
# (MAY choose to validate and store such blocks for additional purposes
# -- e.g. slashing detection, archive nodes, etc)
finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)
if block.slot <= finalized_slot:
raise GossipIgnore("block is not from a slot greater than the latest finalized slot")
# [IGNORE] The block is the first block with valid signature received for the slot and proposer
proposer_slot_key = (block.slot, block.proposer_index)
if proposer_slot_key in seen.proposer_slots:
raise GossipIgnore("block is not the first valid block for this slot and proposer")
# [New in Gloas:EIP7688]
# [REJECT] The block body operation counts are within their limits
verify_block_body_operation_limits(block.body)
# [New in Gloas:EIP7688]
# [REJECT] The parent execution request counts are within their limits
verify_execution_requests_limits(block.body.parent_execution_requests)
# [REJECT] The proposer index is a valid validator index
if block.proposer_index >= len(state.validators):
raise GossipReject("proposer index out of range")
# [REJECT] The proposer signature is valid
proposer = state.validators[block.proposer_index]
domain = get_domain(state, DOMAIN_BEACON_PROPOSER, compute_epoch_at_slot(block.slot))
signing_root = compute_signing_root(block, domain)
if not bls.Verify(proposer.pubkey, signing_root, signed_beacon_block.signature):
raise GossipReject("invalid proposer signature")
# [IGNORE] The block's parent has been seen (via gossip or non-gossip sources)
# (MAY be queued until parent is retrieved)
if block.parent_root not in store.blocks:
raise GossipIgnore("block's parent has not been seen")
# [New in Gloas:EIP7732]
# [IGNORE] If the parent block is full, the parent payload is valid
# (MAY be queued until the parent payload is verified)
if is_parent_node_full(store, block):
if not is_payload_verified(store, block.parent_root):
raise GossipIgnore("parent payload is not verified")
# [REJECT] The block is from a higher slot than its parent
if block.slot <= store.blocks[block.parent_root].slot:
raise GossipReject("block is not from a higher slot than its parent")
# [REJECT] The current finalized checkpoint is an ancestor of the block
finalized_epoch = store.finalized_checkpoint.epoch
finalized_checkpoint_block = get_checkpoint_block(store, block.parent_root, finalized_epoch)
if finalized_checkpoint_block != store.finalized_checkpoint.root:
raise GossipReject("finalized checkpoint is not an ancestor of block")
# [Modified in Gloas:EIP7732]
# [REJECT] The bid's blob KZG commitment count is within the per-epoch limit
max_blobs = get_blob_parameters(get_current_epoch(state)).max_blobs_per_block
if len(bid.blob_kzg_commitments) > max_blobs:
raise GossipReject("too many blob kzg commitments")
# [Modified in Gloas:EIP7732]
# [REJECT] The bid's parent equals the block's parent
if bid.parent_block_root != block.parent_root:
raise GossipReject("bid's parent does not equal block's parent")
# [REJECT] The block's parent passes validation
if block.parent_root not in store.block_states:
raise GossipReject("block's parent is invalid")
# [REJECT] The block is proposed by the expected proposer for the slot
parent_state = store.block_states[block.parent_root].copy()
process_slots(parent_state, block.slot)
expected_proposer = get_beacon_proposer_index(parent_state)
if block.proposer_index != expected_proposer:
raise GossipReject("block proposer_index does not match expected proposer")
# [New in Gloas:EIP7732]
# [REJECT] If the parent is not full, the bid builds on the parent's execution head
if not is_parent_node_full(store, block):
if bid.parent_block_hash != parent_state.latest_block_hash:
raise GossipReject("bid does not build on the parent's execution head")
# Mark this block as seen
seen.proposer_slots.add(proposer_slot_key)
|
Modified beacon_aggregate_and_proof
Note: This function is modified per EIP-7732. aggregate.data.index is now
restricted to {0, 1}, encoding whether the execution payload was present at
the slot. Same-slot aggregates MUST attest with index == 0. Aggregates with
index == 1 require that the corresponding execution payload envelope has been
seen and passes execution-layer validation.
| def validate_beacon_aggregate_and_proof_gossip(
seen: Seen,
store: Store,
state: BeaconState,
signed_aggregate_and_proof: SignedAggregateAndProof,
current_time_ms: Uint64,
# [New in Gloas:EIP7732]
block_payload_statuses: Dict[Root, PayloadValidationStatus],
) -> None:
"""
Validate a SignedAggregateAndProof for gossip propagation.
Raises GossipIgnore or GossipReject on validation failure.
"""
aggregate_and_proof = signed_aggregate_and_proof.message
aggregate = aggregate_and_proof.aggregate
aggregation_bits = aggregate.aggregation_bits
# [New in Gloas:EIP7732]
# [REJECT] The aggregate attestation's data index is 0 or 1
if aggregate.data.index > 1:
raise GossipReject("aggregate data index must be 0 or 1")
# [REJECT] Exactly one committee is specified by the committee bits
committee_indices = get_committee_indices(aggregate.committee_bits)
if len(committee_indices) != 1:
raise GossipReject("aggregate committee bits must specify exactly one committee")
index = committee_indices[0]
# [REJECT] The committee index is within the expected range
committee_count = get_committee_count_per_slot(state, aggregate.data.target.epoch)
if index >= committee_count:
raise GossipReject("committee index out of range")
# [IGNORE] The aggregate attestation's slot is not from a future slot
# (MAY be queued for processing at the appropriate slot)
if is_future_slot(store, aggregate.data.slot, current_time_ms):
raise GossipIgnore("aggregate slot is from a future slot")
# [IGNORE] The aggregate attestation's epoch is either the current or previous epoch
attestation_epoch = compute_epoch_at_slot(aggregate.data.slot)
if not is_current_or_previous_epoch(store, attestation_epoch, current_time_ms):
raise GossipIgnore("aggregate epoch is not current or previous epoch")
# [REJECT] The aggregate attestation's epoch matches its target
if aggregate.data.target.epoch != compute_epoch_at_slot(aggregate.data.slot):
raise GossipReject("attestation epoch does not match target epoch")
# [REJECT] The number of aggregation bits matches the committee size
committee = get_beacon_committee(state, aggregate.data.slot, index)
if len(aggregation_bits) != len(committee):
raise GossipReject("aggregation bits length does not match committee size")
# [REJECT] The aggregate attestation has participants
attesting_indices = get_attesting_indices(state, aggregate)
if len(attesting_indices) < 1:
raise GossipReject("aggregate has no participants")
# [IGNORE] A valid aggregate with a superset of aggregation bits has not already been seen
aggregate_data_root = hash_tree_root(aggregate.data)
aggregate_cache_key = (aggregate_data_root, index)
aggregate_bits = tuple(bool(bit) for bit in aggregation_bits)
seen_bits = seen.aggregate_data_roots.get(aggregate_cache_key, set())
if is_non_strict_superset(seen_bits, aggregate_bits):
raise GossipIgnore("already seen aggregate for this data")
# [IGNORE] This is the first valid aggregate for this epoch and aggregator
aggregator_index = aggregate_and_proof.aggregator_index
target_epoch = aggregate.data.target.epoch
aggregator_epoch_key = (target_epoch, aggregator_index)
if aggregator_epoch_key in seen.aggregator_epochs:
raise GossipIgnore("already seen aggregate for this epoch and aggregator")
# [REJECT] The selection proof selects the validator as an aggregator
if not is_aggregator(state, aggregate.data.slot, index, aggregate_and_proof.selection_proof):
raise GossipReject("validator is not selected as aggregator")
# [REJECT] The aggregator's validator index is within the committee
if aggregator_index not in committee:
raise GossipReject("aggregator index not in committee")
# [REJECT] The selection proof signature is valid
aggregator = state.validators[aggregator_index]
domain = get_domain(state, DOMAIN_SELECTION_PROOF, target_epoch)
signing_root = compute_signing_root(aggregate.data.slot, domain)
if not bls.Verify(aggregator.pubkey, signing_root, aggregate_and_proof.selection_proof):
raise GossipReject("invalid selection proof signature")
# [REJECT] The aggregator signature is valid
domain = get_domain(state, DOMAIN_AGGREGATE_AND_PROOF, target_epoch)
signing_root = compute_signing_root(aggregate_and_proof, domain)
if not bls.Verify(aggregator.pubkey, signing_root, signed_aggregate_and_proof.signature):
raise GossipReject("invalid aggregator signature")
# [REJECT] The aggregate signature is valid
if not is_valid_indexed_attestation(state, get_indexed_attestation(state, aggregate)):
raise GossipReject("invalid aggregate signature")
# [IGNORE] The block being voted for has been seen (via gossip or non-gossip sources)
# (MAY be queued until block is retrieved)
block_root = aggregate.data.beacon_block_root
if block_root not in store.blocks:
raise GossipIgnore("block being voted for has not been seen")
# [REJECT] The block being voted for passes validation
if block_root not in store.block_states:
raise GossipReject("block being voted for failed validation")
# [REJECT] The target block is an ancestor of the LMD vote block
checkpoint_block = get_checkpoint_block(store, block_root, aggregate.data.target.epoch)
if checkpoint_block != aggregate.data.target.root:
raise GossipReject("target block is not an ancestor of LMD vote block")
# [IGNORE] The finalized checkpoint is an ancestor of the block
finalized_epoch = store.finalized_checkpoint.epoch
finalized_checkpoint_block = get_checkpoint_block(store, block_root, finalized_epoch)
if finalized_checkpoint_block != store.finalized_checkpoint.root:
raise GossipIgnore("finalized checkpoint is not an ancestor of block")
# [New in Gloas:EIP7732]
# The attested payload status is consistent with the block's execution payload
verify_attestation_payload_status(store, aggregate.data, block_payload_statuses)
# Mark this aggregate as seen
seen.aggregator_epochs.add(aggregator_epoch_key)
if aggregate_cache_key not in seen.aggregate_data_roots:
seen.aggregate_data_roots[aggregate_cache_key] = set()
seen.aggregate_data_roots[aggregate_cache_key].add(aggregate_bits)
|
New execution_payload
This topic is used to propagate execution payload messages as
SignedExecutionPayloadEnvelope.
| def validate_execution_payload_envelope_gossip(
seen: Seen,
store: Store,
state: BeaconState,
signed_execution_payload_envelope: SignedExecutionPayloadEnvelope,
) -> None:
"""
Validate a SignedExecutionPayloadEnvelope for gossip propagation.
Raises GossipIgnore or GossipReject on validation failure.
"""
envelope = signed_execution_payload_envelope.message
payload = envelope.payload
block_root = envelope.beacon_block_root
# [IGNORE] The envelope's block root has been seen (via gossip or non-gossip sources)
# (MAY be queued until block is retrieved)
if block_root not in store.blocks:
raise GossipIgnore("envelope's block has not been seen")
# [REJECT] The envelope's block passes validation
if block_root not in store.block_states:
raise GossipReject("envelope's block failed validation")
# [IGNORE] The node has not seen another valid envelope for this block root from this builder
envelope_key = (block_root, envelope.builder_index)
if envelope_key in seen.execution_payload_envelopes:
raise GossipIgnore("already seen envelope for this block root from this builder")
# [IGNORE] The envelope is from a slot greater than or equal to the latest finalized slot
finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)
if payload.slot_number < finalized_slot:
raise GossipIgnore("envelope is from a slot before the latest finalized slot")
block = store.blocks[block_root]
bid = block.body.signed_execution_payload_bid.message
# [REJECT] The block's slot matches the payload's slot number
if block.slot != payload.slot_number:
raise GossipReject("block's slot does not match payload's slot number")
# [REJECT] The envelope is from the builder committed to by the bid
if envelope.builder_index != bid.builder_index:
raise GossipReject("envelope's builder index does not match the bid's builder index")
# [REJECT] The payload's block hash matches the bid's block hash
if payload.block_hash != bid.block_hash:
raise GossipReject("payload's block hash does not match the bid's block hash")
# [REJECT] The envelope's execution requests root matches the bid's execution requests root
if hash_tree_root(envelope.execution_requests) != bid.execution_requests_root:
raise GossipReject("envelope's execution requests root does not match the bid's")
# [REJECT] The execution request counts are within their limits
verify_execution_requests_limits(envelope.execution_requests)
# [REJECT] The number of withdrawals is within the limit
if len(payload.withdrawals) > MAX_WITHDRAWALS_PER_PAYLOAD:
raise GossipReject("too many withdrawals")
# [REJECT] The envelope signature is valid
if not verify_execution_payload_envelope_signature(state, signed_execution_payload_envelope):
raise GossipReject("invalid envelope signature")
# Mark this envelope as seen and store its payload
seen.execution_payload_envelopes.add(envelope_key)
seen.execution_payloads[payload.block_hash] = payload
|
New payload_attestation_message
This topic is used to propagate signed payload attestation message.
| def validate_payload_attestation_message_gossip(
seen: Seen,
store: Store,
state: BeaconState,
payload_attestation_message: PayloadAttestationMessage,
current_time_ms: Uint64,
) -> None:
"""
Validate a PayloadAttestationMessage for gossip propagation.
Raises GossipIgnore or GossipReject on validation failure.
"""
data = payload_attestation_message.data
validator_index = payload_attestation_message.validator_index
# [IGNORE] The payload attestation's slot is the current slot
if not is_current_slot(store, data.slot, current_time_ms):
raise GossipIgnore("payload attestation's slot is not the current slot")
# [IGNORE] This is the first valid payload attestation from this validator index
payload_attestation_key = (data.slot, validator_index)
if payload_attestation_key in seen.payload_attestation_validators:
raise GossipIgnore("already seen payload attestation from this validator")
# [IGNORE] The payload attestation's block has been seen (via gossip or non-gossip sources)
# (MAY be queued until block is retrieved)
if data.beacon_block_root not in store.blocks:
raise GossipIgnore("payload attestation's block has not been seen")
# [REJECT] The payload attestation's block passes validation
if data.beacon_block_root not in store.block_states:
raise GossipReject("payload attestation's block failed validation")
# [IGNORE] The payload attestation's block is at the assigned slot
if store.blocks[data.beacon_block_root].slot != data.slot:
raise GossipIgnore("payload attestation's block is not at the assigned slot")
# [REJECT] The validator index is valid
if validator_index >= len(state.validators):
raise GossipReject("validator index out of range")
# [REJECT] The validator is a member of the payload timeliness committee
if validator_index not in get_ptc(state, data.slot):
raise GossipReject("validator is not in the payload timeliness committee")
# [REJECT] The signature is valid with respect to the validator's public key
validator = state.validators[validator_index]
domain = get_domain(state, DOMAIN_PTC_ATTESTER, compute_epoch_at_slot(data.slot))
signing_root = compute_signing_root(data, domain)
if not bls.Verify(validator.pubkey, signing_root, payload_attestation_message.signature):
raise GossipReject("invalid payload attestation signature")
# Mark this payload_attestation as seen
seen.payload_attestation_validators.add(payload_attestation_key)
|
New execution_payload_bid
This topic is used to propagate signed bids as SignedExecutionPayloadBid.
Note: The state passed to validate_execution_payload_bid_gossip is the
bid's parent block post-state. The function advances it to the bid's slot so
that builder checks such as is_active_builder and can_builder_cover_bid are
evaluated at the bid's slot rather than at the parent's slot.
| def validate_execution_payload_bid_gossip(
seen: Seen,
store: Store,
state: BeaconState,
signed_execution_payload_bid: SignedExecutionPayloadBid,
current_time_ms: Uint64,
) -> None:
"""
Validate a SignedExecutionPayloadBid for gossip propagation.
Raises GossipIgnore or GossipReject on validation failure.
"""
bid = signed_execution_payload_bid.message
# [IGNORE] The bid's slot is the current slot or the next slot
if not is_current_or_next_slot(store, bid.slot, current_time_ms):
raise GossipIgnore("bid's slot is not the current or next slot")
# [IGNORE] This is the first bid for this slot, parent, and builder
bid_key = (bid.slot, bid.parent_block_hash, bid.parent_block_root, bid.builder_index)
if bid_key in seen.execution_payload_bids:
raise GossipIgnore("already seen valid bid for this slot, parent, and builder")
# [IGNORE] This is the highest value bid seen for the slot and parent
best_bid_key = (bid.slot, bid.parent_block_hash, bid.parent_block_root)
if best_bid_key in seen.best_execution_payload_bid:
if bid.value <= seen.best_execution_payload_bid[best_bid_key]:
raise GossipIgnore("bid is not the highest value bid seen for this slot and parent")
# [REJECT] The bid is for a higher slot than its parent block
if bid.slot <= state.slot:
raise GossipReject("bid's slot is not higher than its parent's slot")
# [REJECT] The bid's execution payment is zero
if bid.execution_payment != 0:
raise GossipReject("bid's execution payment must be zero")
# [REJECT] The bid's blob KZG commitment count is within the per-epoch limit
proposal_epoch = compute_epoch_at_slot(bid.slot)
max_blobs = get_blob_parameters(proposal_epoch).max_blobs_per_block
if len(bid.blob_kzg_commitments) > max_blobs:
raise GossipReject("too many blob kzg commitments")
# [IGNORE] The bid's parent block root is a known beacon block
# (MAY be queued until parent is retrieved)
if bid.parent_block_root not in store.blocks:
raise GossipIgnore("bid's parent block root is not a known beacon block")
# [IGNORE] The state is the bid's parent block post-state
parent_block = store.blocks[bid.parent_block_root]
header = state.latest_block_header.copy()
header.state_root = parent_block.state_root
if hash_tree_root(header) != bid.parent_block_root:
raise GossipIgnore("state is not the bid's parent block post-state")
# [IGNORE] The bid's slot is within the parent's proposer lookahead
if proposal_epoch > get_current_epoch(state) + Epoch(MIN_SEED_LOOKAHEAD):
raise GossipIgnore("bid's slot is past the parent's proposer lookahead")
# [IGNORE] The matching proposer preferences have been seen
dependent_root = get_shuffling_dependent_root(store, bid.parent_block_root, proposal_epoch)
prefs_key = (dependent_root, bid.slot)
if prefs_key not in seen.proposer_preferences:
raise GossipIgnore("matching proposer preferences have not been seen")
proposer_preferences = seen.proposer_preferences[prefs_key]
# [IGNORE] The bid's fee recipient matches the proposer's preference
if bid.fee_recipient != proposer_preferences.fee_recipient:
raise GossipIgnore("bid's fee recipient does not match the proposer's preference")
# [IGNORE] The bid's parent block hash is the hash of a known execution payload
if bid.parent_block_hash not in seen.execution_payloads:
raise GossipIgnore("bid's parent block hash is not a known execution payload")
# [IGNORE] The bid's gas limit is compatible with the proposer's target gas limit
parent_gas_limit = seen.execution_payloads[bid.parent_block_hash].gas_limit
if not is_gas_limit_target_compatible(
parent_gas_limit, bid.gas_limit, proposer_preferences.target_gas_limit
):
raise GossipIgnore("bid's gas limit is not compatible with the proposer's target")
# [IGNORE] The bid is compatible with the current head branch
if not is_bid_compatible_with_head(store, bid):
raise GossipIgnore("bid is not compatible with the current head branch")
# [REJECT] The bid's previous randao is correct
if bid.prev_randao != get_randao_mix(state, get_current_epoch(state)):
raise GossipReject("bid's previous randao is incorrect")
# Advance state
state = state.copy()
process_slots(state, bid.slot)
# [REJECT] The builder index is valid
if bid.builder_index >= len(state.builders):
raise GossipReject("builder index out of range")
# [IGNORE] The builder can cover the bid
if not can_builder_cover_bid(state, bid.builder_index, bid.value):
raise GossipIgnore("builder cannot cover bid value")
# [REJECT] The builder is active
if not is_active_builder(state, bid.builder_index):
raise GossipReject("builder is not active")
# [REJECT] The builder is a payload builder
if state.builders[bid.builder_index].version != PAYLOAD_BUILDER_VERSION:
raise GossipReject("builder is not a payload builder")
# [REJECT] The bid signature is valid
if not verify_execution_payload_bid_signature(state, signed_execution_payload_bid):
raise GossipReject("invalid bid signature")
# Mark this bid as seen and update the highest-value bid for this slot/parent
seen.execution_payload_bids.add(bid_key)
seen.best_execution_payload_bid[best_bid_key] = bid.value
|
Note: Implementations SHOULD include DoS prevention measures to mitigate spam
from malicious builders submitting numerous bids with minimal value increments.
Possible strategies include: (1) only forwarding bids that exceed the current
highest bid by a minimum threshold, or (2) forwarding only the highest observed
bid at regular time intervals.
New proposer_preferences
This topic is used to propagate signed proposer preferences as
SignedProposerPreferences. These messages allow validators to communicate
their preferred fee_recipient and target_gas_limit to builders.
Note: Nodes SHOULD subscribe to this topic at least one epoch before the fork
activation. Proposers SHOULD broadcast their preferences in the epoch before the
fork.
| def validate_proposer_preferences_gossip(
seen: Seen,
store: Store,
signed_proposer_preferences: SignedProposerPreferences,
current_time_ms: Uint64,
) -> None:
"""
Validate a SignedProposerPreferences for gossip propagation.
Raises GossipIgnore or GossipReject on validation failure.
"""
preferences = signed_proposer_preferences.message
proposal_epoch = compute_epoch_at_slot(preferences.proposal_slot)
# [IGNORE] The proposal slot has not started yet
if is_past_slot(store, preferences.proposal_slot, current_time_ms):
raise GossipIgnore("proposal slot has already started")
# [IGNORE] The proposer for the proposal slot is known
lookahead_epoch = Epoch(proposal_epoch - MIN_SEED_LOOKAHEAD)
lookahead_epoch_start_slot = compute_start_slot_at_epoch(lookahead_epoch)
if is_future_slot(store, lookahead_epoch_start_slot, current_time_ms):
raise GossipIgnore("proposer for the proposal slot is not yet known")
# [IGNORE] The dependent block has been seen (via gossip or non-gossip sources)
# (MAY be queued until block is retrieved)
if preferences.dependent_root not in store.blocks:
raise GossipIgnore("dependent block has not been seen")
# [IGNORE] These are the first valid preferences seen for this dependent root and slot
prefs_key = (preferences.dependent_root, preferences.proposal_slot)
if prefs_key in seen.proposer_preferences:
raise GossipIgnore("already seen preferences for this dependent root and proposal slot")
# [IGNORE] The dependent block passes validation
if preferences.dependent_root not in store.block_states:
raise GossipIgnore("dependent block failed validation")
# [REJECT] The dependent root is a valid dependent block for the proposal slot
if store.blocks[preferences.dependent_root].slot >= lookahead_epoch_start_slot:
raise GossipReject("dependent root is not before the proposer lookahead epoch")
# [IGNORE] The dependent root is a possible dependent block for the lookahead epoch
if not is_valid_dependent_root(store, preferences.dependent_root, lookahead_epoch):
raise GossipIgnore("dependent root is not a possible dependent block")
# [REJECT] The validator is the proposer for the given slot in the proposer lookahead
lookahead_state = store.block_states[preferences.dependent_root].copy()
process_slots(lookahead_state, lookahead_epoch_start_slot)
lookahead_index = preferences.proposal_slot - lookahead_epoch_start_slot
if lookahead_state.proposer_lookahead[lookahead_index] != preferences.validator_index:
raise GossipReject("validator is not the proposer for the given slot")
# [REJECT] The signature is valid with respect to the validator's public key
validator = lookahead_state.validators[preferences.validator_index]
domain = get_domain(lookahead_state, DOMAIN_PROPOSER_PREFERENCES, proposal_epoch)
signing_root = compute_signing_root(preferences, domain)
if not bls.Verify(validator.pubkey, signing_root, signed_proposer_preferences.signature):
raise GossipReject("invalid proposer preferences signature")
# Mark these preferences as seen
seen.proposer_preferences[prefs_key] = preferences
|
Attestation subnets
Modified beacon_attestation_{subnet_id}
Note: This function is modified per EIP-7732. attestation.data.index is now
restricted to {0, 1}, encoding whether the execution payload was present at
the slot. Same-slot attestations MUST attest with index == 0. Attestations
with index == 1 require that the corresponding execution payload envelope has
been seen and passes execution-layer validation.
| def validate_beacon_attestation_gossip(
seen: Seen,
store: Store,
state: BeaconState,
attestation: SingleAttestation,
current_time_ms: Uint64,
subnet_id: SubnetID,
# [New in Gloas:EIP7732]
block_payload_statuses: Dict[Root, PayloadValidationStatus],
) -> None:
"""
Validate a SingleAttestation for gossip propagation on a subnet.
Raises GossipIgnore or GossipReject on validation failure.
"""
data = attestation.data
committee_index = attestation.committee_index
attester_index = attestation.attester_index
target_epoch = data.target.epoch
# [New in Gloas:EIP7732]
# [REJECT] The attestation's data index is 0 or 1
if data.index > 1:
raise GossipReject("attestation data index must be 0 or 1")
# [REJECT] The committee index is within the expected range
committees_per_slot = get_committee_count_per_slot(state, target_epoch)
if committee_index >= committees_per_slot:
raise GossipReject("committee index out of range")
# [REJECT] The attestation is for the correct subnet
expected_subnet = compute_subnet_for_attestation(
committees_per_slot, data.slot, committee_index
)
if expected_subnet != subnet_id:
raise GossipReject("attestation is for wrong subnet")
# [IGNORE] The attestation's slot is not from a future slot
# (MAY be queued for processing at the appropriate slot)
if is_future_slot(store, data.slot, current_time_ms):
raise GossipIgnore("attestation slot is from a future slot")
# [IGNORE] The attestation's epoch is either the current or previous epoch
attestation_epoch = compute_epoch_at_slot(data.slot)
if not is_current_or_previous_epoch(store, attestation_epoch, current_time_ms):
raise GossipIgnore("attestation epoch is not current or previous epoch")
# [REJECT] The attestation's epoch matches its target
if target_epoch != compute_epoch_at_slot(data.slot):
raise GossipReject("attestation epoch does not match target epoch")
# [REJECT] The attester is a member of the committee
committee = get_beacon_committee(state, data.slot, committee_index)
if attester_index not in committee:
raise GossipReject("attester is not a member of the committee")
# [IGNORE] No other valid attestation seen for this target epoch and validator
attestation_epoch_key = (target_epoch, attester_index)
if attestation_epoch_key in seen.attestation_validator_epochs:
raise GossipIgnore("already seen attestation for this epoch and validator")
# [REJECT] The attestation signature is valid
attester = state.validators[attester_index]
domain = get_domain(state, DOMAIN_BEACON_ATTESTER, target_epoch)
signing_root = compute_signing_root(data, domain)
if not bls.Verify(attester.pubkey, signing_root, attestation.signature):
raise GossipReject("invalid attestation signature")
# [IGNORE] The block being voted for has been seen (via gossip or non-gossip sources)
# (MAY be queued until block is retrieved)
block_root = data.beacon_block_root
if block_root not in store.blocks:
raise GossipIgnore("block being voted for has not been seen")
# [REJECT] The block being voted for passes validation
if block_root not in store.block_states:
raise GossipReject("block being voted for failed validation")
# [REJECT] The attestation's target block is an ancestor of the LMD vote block
target_checkpoint_block = get_checkpoint_block(store, block_root, target_epoch)
if target_checkpoint_block != data.target.root:
raise GossipReject("target block is not an ancestor of LMD vote block")
# [IGNORE] The current finalized checkpoint is an ancestor of the block
finalized_epoch = store.finalized_checkpoint.epoch
finalized_checkpoint_block = get_checkpoint_block(store, block_root, finalized_epoch)
if finalized_checkpoint_block != store.finalized_checkpoint.root:
raise GossipIgnore("finalized checkpoint is not an ancestor of block")
# [New in Gloas:EIP7732]
# The attested payload status is consistent with the block's execution payload
verify_attestation_payload_status(store, data, block_payload_statuses)
# Mark this attestation as seen
seen.attestation_validator_epochs.add(attestation_epoch_key)
|
Blob subnets
Modified data_column_sidecar_{subnet_id}
The KZG commitments needed to verify a sidecar are now carried by the bid at
block.body.signed_execution_payload_bid.message.blob_kzg_commitments, where
block is the BeaconBlock with root sidecar.beacon_block_root.
Note: If the sidecar fails deferred validation, its forwarding peers MUST be
downscored retroactively. If validation succeeds, the client MUST re-broadcast
the sidecar.
| def validate_data_column_sidecar_gossip(
seen: Seen,
store: Store,
# [Modified in Gloas:EIP7732]
# Removed `state`
sidecar: DataColumnSidecar,
current_time_ms: Uint64,
subnet_id: SubnetID,
) -> None:
"""
Validate a DataColumnSidecar for gossip propagation on a subnet.
Raises GossipIgnore or GossipReject on validation failure.
"""
# [IGNORE] This is the first sidecar seen for this block root and column index
sidecar_key = (sidecar.beacon_block_root, sidecar.index)
if sidecar_key in seen.data_column_sidecar_tuples:
raise GossipIgnore("already seen sidecar for this block root and index")
# [REJECT] The sidecar is for the correct subnet
if compute_subnet_for_data_column_sidecar(sidecar.index) != subnet_id:
raise GossipReject("sidecar is for wrong subnet")
# [IGNORE] The sidecar is not from a future slot
# (MAY be queued for processing at the appropriate slot)
if is_future_slot(store, sidecar.slot, current_time_ms):
raise GossipIgnore("sidecar is from a future slot")
# [IGNORE] A block for the sidecar has been seen (via gossip or non-gossip sources)
# (MAY be queued until block is retrieved)
# (SHOULD queue at least one sidecar per peer per subnet)
if sidecar.beacon_block_root not in store.blocks:
raise GossipIgnore("block for sidecar's beacon block root has not been seen")
# [REJECT] The block for the sidecar passes validation
if sidecar.beacon_block_root not in store.block_states:
raise GossipReject("block for sidecar's beacon block root failed validation")
block = store.blocks[sidecar.beacon_block_root]
# [REJECT] The sidecar's slot matches the slot of the block
if sidecar.slot != block.slot:
raise GossipReject("sidecar's slot does not match block's slot")
bid = block.body.signed_execution_payload_bid.message
# [REJECT] The sidecar passes structural validation
if not verify_data_column_sidecar(sidecar, bid.blob_kzg_commitments):
raise GossipReject("invalid sidecar")
# [REJECT] The sidecar's column data passes KZG verification
if not verify_data_column_sidecar_kzg_proofs(sidecar, bid.blob_kzg_commitments):
raise GossipReject("invalid sidecar kzg proofs")
# Mark this data column sidecar as seen
seen.data_column_sidecar_tuples.add(sidecar_key)
|
The Req/Resp domain
Messages
BeaconBlocksByRange v2
Protocol ID: /eth2/beacon_chain/req/beacon_blocks_by_range/2/
The Gloas fork-digest is introduced to the context enum to specify Gloas
beacon block type.
fork_version |
Chunk SSZ type |
GENESIS_FORK_VERSION |
phase0.SignedBeaconBlock |
ALTAIR_FORK_VERSION |
altair.SignedBeaconBlock |
BELLATRIX_FORK_VERSION |
bellatrix.SignedBeaconBlock |
CAPELLA_FORK_VERSION |
capella.SignedBeaconBlock |
DENEB_FORK_VERSION |
deneb.SignedBeaconBlock |
ELECTRA_FORK_VERSION |
electra.SignedBeaconBlock |
FULU_FORK_VERSION |
fulu.SignedBeaconBlock |
GLOAS_FORK_VERSION |
gloas.SignedBeaconBlock |
BeaconBlocksByRoot v2
Protocol ID: /eth2/beacon_chain/req/beacon_blocks_by_root/2/
The Gloas fork-digest is introduced to the context enum to specify Gloas
beacon block type.
fork_version |
Chunk SSZ type |
GENESIS_FORK_VERSION |
phase0.SignedBeaconBlock |
ALTAIR_FORK_VERSION |
altair.SignedBeaconBlock |
BELLATRIX_FORK_VERSION |
bellatrix.SignedBeaconBlock |
CAPELLA_FORK_VERSION |
capella.SignedBeaconBlock |
DENEB_FORK_VERSION |
deneb.SignedBeaconBlock |
ELECTRA_FORK_VERSION |
electra.SignedBeaconBlock |
FULU_FORK_VERSION |
fulu.SignedBeaconBlock |
GLOAS_FORK_VERSION |
gloas.SignedBeaconBlock |
ExecutionPayloadEnvelopesByRange v1
Protocol ID:
/eth2/beacon_chain/req/execution_payload_envelopes_by_range/1/
Request Content:
| (
start_slot: Slot
count: Uint64
)
|
Response Content:
| (
SignedExecutionPayloadEnvelopes
)
|
Specifications of request/response methods are equivalent to
BeaconBlocksByRange v2, with the only difference
being the response content type.
For each successful response_chunk, the ForkDigest context epoch is
determined by compute_epoch_at_slot(beacon_block.slot) based on the
beacon_block referred to by
signed_execution_payload_envelope.message.beacon_block_root.
Per fork_version = compute_fork_version(epoch):
fork_version |
Chunk SSZ type |
GLOAS_FORK_VERSION |
gloas.SignedExecutionPayloadEnvelope |
ExecutionPayloadEnvelopesByRoot v1
Protocol ID: /eth2/beacon_chain/req/execution_payload_envelopes_by_root/1/
Request Content:
| (
ExecutionPayloadEnvelopeRoots
)
|
Response Content:
| (
SignedExecutionPayloadEnvelopes
)
|
Requests execution payload envelopes by
signed_execution_payload_envelope.message.beacon_block_root. The response is a
list of SignedExecutionPayloadEnvelope whose length is less than or equal to
the number of requested execution payload envelopes. It may be less in the case
that the responding peer is missing payload envelopes.
No more than MAX_REQUEST_PAYLOADS may be requested at a time.
ExecutionPayloadEnvelopesByRoot is primarily used to recover recent execution
payload envelopes and attestations (e.g. when receiving a payload attestation or
attestation with revealed status as true but never received a payload).
The request MUST be encoded as an SSZ-field.
The response MUST consist of zero or more response_chunk. Each successful
response_chunk MUST contain a single SignedExecutionPayloadEnvelope payload.
Clients MUST support requesting payload envelopes on the epoch range
[max(GLOAS_FORK_EPOCH, current_epoch - compute_min_epochs_for_block_requests()), current_epoch].
If any root in the request content references a block earlier than this range,
peers MAY respond with error code 3: ResourceUnavailable or not include the
payload envelope in the response.
Clients MUST respond with at least one payload envelope, if they have it.
Clients MAY limit the number of payload envelopes in the response.
For each successful response_chunk, the ForkDigest context epoch is
determined by compute_epoch_at_slot(beacon_block.slot) based on the
beacon_block referred to by
signed_execution_payload_envelope.message.beacon_block_root.
Per fork_version = compute_fork_version(epoch):
fork_version |
Chunk SSZ type |
GLOAS_FORK_VERSION |
gloas.SignedExecutionPayloadEnvelope |