Skip to content

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.

Modification in Gloas

Configuration

Name Value Description
MAX_REQUEST_PAYLOADS 2**7 (= 128) Maximum number of execution payload envelopes in a single request

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
    column: List[Cell, MAX_BLOB_COMMITMENTS_PER_BLOCK]
    # [Modified in Gloas:EIP7732]
    # Removed `kzg_commitments`
    kzg_proofs: List[KZGProof, MAX_BLOB_COMMITMENTS_PER_BLOCK]
    # [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]

1
2
3
4
5
6
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]

1
2
3
class SignedProposerPreferences(Container):
    message: ProposerPreferences
    signature: BLSSignature

Helpers

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: List[KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK],
) -> 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(
        # [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: List[KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK],
) -> 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/proofs
    if len(sidecar.column) != len(kzg_commitments) or len(sidecar.column) != len(
        sidecar.kzg_proofs
    ):
        return False

    return True

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

Gloas introduces new global topics for execution bid, execution payload and payload attestation.

beacon_aggregate_and_proof

Let block be the beacon block corresponding to aggregate.data.beacon_block_root.

The following validations are added:

  • [REJECT] aggregate.data.index < 2.
  • [REJECT] aggregate.data.index == 0 if block.slot == aggregate.data.slot.
  • [REJECT] If aggregate.data.index == 1 (payload present for a past block) the corresponding execution payload for block passes validation.
  • [IGNORE] When aggregate.data.index == 1 (payload present for a past block), the corresponding execution payload for block has been seen (a client MAY queue attestations for processing once the payload is retrieved and SHOULD request the payload envelope via ExecutionPayloadEnvelopesByRoot using aggregate.data.beacon_block_root).

The following validations are removed:

  • [REJECT] aggregate.data.index == 0.
beacon_block

[Modified in Gloas:EIP7732]

The type of the payload of this topic changes to the (modified) SignedBeaconBlock found in the beacon-chain changes.

There are no new validations for this topic. However, all validations with regards to the ExecutionPayload are removed:

  • [REJECT] The block's execution payload timestamp is correct with respect to the slot -- i.e. execution_payload.timestamp == compute_time_at_slot(state, block.slot).
  • If execution_payload verification of block's parent by an execution node is not complete:
  • [REJECT] The block's parent (defined by block.parent_root) passes all validation (excluding execution node verification of the block.body.execution_payload).
  • otherwise:
  • [IGNORE] The block's parent (defined by block.parent_root) passes all validation (including execution node verification of the block.body.execution_payload).

And instead the following validations are set in place with the alias bid = block.body.signed_execution_payload_bid.message:

  • [REJECT] The length of KZG commitments is less than or equal to the limitation defined in the consensus layer -- i.e. validate that len(bid.blob_kzg_commitments) <= get_blob_parameters(get_current_epoch(state)).max_blobs_per_block
  • [IGNORE] The block's parent execution payload (defined by bid.parent_block_hash) has been seen (via gossip or non-gossip sources) (a client MAY queue blocks for processing once the parent payload is retrieved).
  • If execution_payload verification of block's execution payload parent by an execution node is complete:
  • [REJECT] The block's execution payload parent (defined by bid.parent_block_hash) passes all validation.
  • [REJECT] The bid's parent (defined by bid.parent_block_root) equals the block's parent (defined by block.parent_root).
execution_payload

This topic is used to propagate execution payload messages as SignedExecutionPayloadEnvelope.

The following validations MUST pass before forwarding the signed_execution_payload_envelope on the network, assuming the alias envelope = signed_execution_payload_envelope.message, payload = envelope.payload:

  • [IGNORE] The envelope's block root envelope.beacon_block_root has been seen (via gossip or non-gossip sources) (a client MAY queue payload for processing once the block is retrieved).
  • [IGNORE] The node has not seen another valid SignedExecutionPayloadEnvelope for this block root from this builder.
  • [IGNORE] The envelope is from a slot greater than or equal to the latest finalized slot -- i.e. validate that envelope.payload.slot_number >= compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)

Let block be the block with envelope.beacon_block_root. Let bid alias block.body.signed_execution_payload_bid.message (notice that this can be obtained from the state.latest_execution_payload_bid)

  • [REJECT] block passes validation.
  • [REJECT] block.slot equals envelope.payload.slot_number.
  • [REJECT] envelope.builder_index == bid.builder_index
  • [REJECT] payload.block_hash == bid.block_hash
  • [REJECT] hash_tree_root(envelope.execution_requests) == bid.execution_requests_root
  • [REJECT] signed_execution_payload_envelope.signature is valid as verified by verify_execution_payload_envelope_signature.
payload_attestation_message

This topic is used to propagate signed payload attestation message.

The following validations MUST pass before forwarding the payload_attestation_message on the network, assuming the alias data = payload_attestation_message.data:

  • [IGNORE] The message's slot is for the current slot (with a MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance), i.e. data.slot == current_slot.
  • [IGNORE] The payload_attestation_message is the first valid message received from the validator with index payload_attestation_message.validator_index.
  • [IGNORE] The message's block data.beacon_block_root has been seen (via gossip or non-gossip sources) (a client MAY queue attestation for processing once the block is retrieved. Note a client might want to request payload after).
  • [REJECT] The message's block data.beacon_block_root passes validation.
  • [REJECT] The message's validator index is within the payload committee in get_ptc(state, data.slot). The state is the head state corresponding to processing the block up to the current slot as determined by the fork choice.
  • [REJECT] payload_attestation_message.signature is valid with respect to the validator's public key.
execution_payload_bid

This topic is used to propagate signed bids as SignedExecutionPayloadBid.

The following validations MUST pass before forwarding the signed_execution_payload_bid on the network, assuming the alias bid = signed_execution_payload_bid.message, the alias signed_proposer_preferences for the validated SignedProposerPreferences whose message.proposal_slot is bid.slot and message.dependent_root is get_proposer_dependent_root(parent_state, compute_epoch_at_slot(bid.slot)), where parent_state is the post-state of bid.parent_block_root, and the alias proposer_preferences = signed_proposer_preferences.message:

  • [IGNORE] bid.slot is the current slot or the next slot.
  • [IGNORE] The matching signed_proposer_preferences has been seen.
  • [REJECT] bid.builder_index is a valid/active builder index -- i.e. is_active_builder(state, bid.builder_index) returns True.
  • [REJECT] bid.execution_payment == 0.
  • [REJECT] bid.fee_recipient == proposer_preferences.fee_recipient.
  • [REJECT] The length of KZG commitments is less than or equal to the limitation defined in the consensus layer -- i.e. validate that len(bid.blob_kzg_commitments) <= get_blob_parameters(compute_epoch_at_slot(bid.slot)).max_blobs_per_block.
  • [IGNORE] this is the first signed bid seen with a valid signature from the given builder for this slot.
  • [IGNORE] this bid is the highest value bid seen for the tuple (bid.slot, bid.parent_block_hash, bid.parent_block_root).
  • [IGNORE] bid.value is less or equal than the builder's excess balance -- i.e. can_builder_cover_bid(state, builder_index, amount) returns True.
  • [IGNORE] bid.parent_block_hash is the block hash of a known execution payload in fork choice and is_gas_limit_target_compatible(parent_gas_limit, bid.gas_limit, proposer_preferences.target_gas_limit) is True where parent_gas_limit is the gas_limit of that execution payload.
  • [IGNORE] bid.parent_block_root is the hash tree root of a known beacon block in fork choice.
  • [REJECT] signed_execution_payload_bid.signature is valid with respect to the bid.builder_index.
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 and target_gas_limit <= max_gas_limit:
        return gas_limit == target_gas_limit
    if target_gas_limit > max_gas_limit:
        return gas_limit == max_gas_limit
    return gas_limit == min_gas_limit

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.

proposer_preferences

[New in Gloas:EIP7732]

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.

The following validations MUST pass before forwarding the signed_proposer_preferences on the network, assuming the alias preferences = signed_proposer_preferences.message:

  • [IGNORE] preferences.proposal_slot is within the proposer lookahead -- i.e. compute_epoch_at_slot(preferences.proposal_slot) is in the range [compute_epoch_at_slot(current_slot), compute_epoch_at_slot(current_slot) + MIN_SEED_LOOKAHEAD].
  • [IGNORE] preferences.proposal_slot has not already passed -- i.e. preferences.proposal_slot > current_slot.
  • [IGNORE] The block with root preferences.dependent_root has been seen (via gossip or non-gossip sources) (a client MAY queue the message for re-processing once the block is retrieved).
  • [REJECT] is_valid_proposal_slot(state, preferences) returns True, where state is the checkpoint state at the epoch compute_epoch_at_slot(preferences.proposal_slot) - MIN_SEED_LOOKAHEAD and the root preferences.dependent_root.
  • [IGNORE] The signed_proposer_preferences is the first valid message seen for the tuple (preferences.dependent_root, preferences.proposal_slot, preferences.validator_index).
  • [REJECT] signed_proposer_preferences.signature is valid with respect to the validator's public key.
def is_valid_proposal_slot(state: BeaconState, preferences: ProposerPreferences) -> bool:
    """
    Check if the validator is the proposer for the given slot within the
    proposer lookahead.
    """
    current_epoch = get_current_epoch(state)
    proposal_epoch = compute_epoch_at_slot(preferences.proposal_slot)
    if proposal_epoch < current_epoch:
        return False
    if proposal_epoch > current_epoch + Epoch(MIN_SEED_LOOKAHEAD):
        return False

    index = (proposal_epoch - current_epoch) * SLOTS_PER_EPOCH
    index += preferences.proposal_slot % SLOTS_PER_EPOCH
    return state.proposer_lookahead[index] == preferences.validator_index
1
2
3
4
5
6
7
def get_proposer_dependent_root(state: BeaconState, epoch: Epoch) -> Root:
    """
    Return the dependent root for the proposer lookahead at ``epoch``.
    """
    return get_block_root_at_slot(
        state, Slot(compute_start_slot_at_epoch(Epoch(epoch - MIN_SEED_LOOKAHEAD)) - 1)
    )

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.

Blob subnets
data_column_sidecar_{subnet_id}

[Modified in Gloas:EIP7732]

The following validations MUST pass before forwarding the sidecar: DataColumnSidecar on the network, assuming the alias bid = block.body.signed_execution_payload_bid.message where block is the BeaconBlock associated with sidecar.beacon_block_root:

  • [IGNORE] A valid block for the sidecar's slot has been seen (via gossip or non-gossip sources). If not yet seen, a client SHOULD queue the sidecar for deferred validation and possible processing once the block is received or retrieved. A client SHOULD queue at least one sidecar per peer per subnet.
  • [REJECT] The sidecar's slot matches the slot of the block with root beacon_block_root.
  • [REJECT] The sidecar is valid as verified by verify_data_column_sidecar(sidecar, bid.blob_kzg_commitments).
  • [REJECT] The sidecar is for the correct subnet -- i.e. compute_subnet_for_data_column_sidecar(sidecar.index) == subnet_id.
  • [REJECT] The sidecar's column data is valid as verified by verify_data_column_sidecar_kzg_proofs(sidecar, bid.blob_kzg_commitments).
  • [IGNORE] The sidecar is the first sidecar for the tuple (sidecar.beacon_block_root, sidecar.index) with valid kzg proof.

Note: If the sidecar fails deferred validation, its forwarding peers MUST be downscored retroactively. If validation succeeds, the client MUST re-broadcast the sidecar.

Attestation subnets
beacon_attestation_{subnet_id}

Let block be the beacon block corresponding to attestation.data.beacon_block_root.

The following validations are added:

  • [REJECT] attestation.data.index < 2.
  • [REJECT] attestation.data.index == 0 if block.slot == attestation.data.slot.
  • [REJECT] If attestation.data.index == 1 (payload present for a past block), the execution payload for block passes validation.
  • [IGNORE] When attestation.data.index == 1 (payload present for a past block), the execution payload for block has been seen (a client MAY queue attestations for processing once the payload is retrieved and SHOULD request the payload envelope via ExecutionPayloadEnvelopesByRoot using attestation.data.beacon_block_root).

The following validations are removed:

  • [REJECT] attestation.data.index == 0.

The Req/Resp domain

Messages

BeaconBlocksByRange v2

Protocol ID: /eth2/beacon_chain/req/beacon_blocks_by_range/2/

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/

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:

1
2
3
4
(
  start_slot: Slot
  count: uint64
)

Response Content:

1
2
3
(
  List[SignedExecutionPayloadEnvelope, MAX_REQUEST_BLOCKS_DENEB]
)

Specifications of req\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/

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

Request Content:

1
2
3
(
  List[Root, MAX_REQUEST_PAYLOADS]
)

Response Content:

1
2
3
(
  List[SignedExecutionPayloadEnvelope, MAX_REQUEST_PAYLOADS]
)

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.