Part 3: Annotated Specification


Beacon blocks


class BeaconBlockBody(Container):
    randao_reveal: BLSSignature
    eth1_data: Eth1Data  # Eth1 data vote
    graffiti: Bytes32  # Arbitrary data
    # Operations
    proposer_slashings: List[ProposerSlashing, MAX_PROPOSER_SLASHINGS]
    attester_slashings: List[AttesterSlashing, MAX_ATTESTER_SLASHINGS]
    attestations: List[Attestation, MAX_ATTESTATIONS]
    deposits: List[Deposit, MAX_DEPOSITS]
    voluntary_exits: List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS]
    sync_aggregate: SyncAggregate  # [New in Altair]

The two fundamental data structures for nodes are the BeaconBlock and the BeaconState. The BeaconBlock is how the leader (the chosen proposer in a slot) communicates network updates to all the other validators, and those validators update their own BeaconState by applying BeaconBlocks. The idea is that (eventually) all validators on the network come to agree on the same BeaconState.

Validators are randomly selected to propose beacon blocks, and there ought to be exactly one beacon block per slot if things are running correctly. If a validator is offline, or misses its slot, or proposes an invalid block, or has its block orphaned, then a slot can be empty.

The following objects are always present in a valid beacon block.

  • randao_reveal: the block is invalid if the RANDAO reveal does not verify correctly against the proposer's public key. This is the block proposer's contribution to the beacon chain's randomness. The proposer generates it by signing the current epoch number (combined with DOMAIN_RANDAO) with its private key. To the best of anyone's knowledge, the result is indistinguishable from random. This gets mixed into the beacon state RANDAO.
  • See Eth1Data for eth1_data. In principle, this is mandatory, but it is not checked, and there is no penalty for making it up.
  • graffiti is left free for the proposer to insert whatever data it wishes. It has no protocol level significance. It can be left as zero; most clients set the client name and version string as their own default graffiti value.
  • sync_aggregate is a record of which validators in the current sync committee voted for the chain head in the previous slot.

Deposits are a special case. They are mandatory only if there are pending deposits to be processed. There is no explicit reward for including deposits, except that a block is invalid without any that ought to be there.

  • deposits: if the block does not contain either all the outstanding Deposits, or MAX_DEPOSITS of them in deposit order, then it is invalid.

Including any of the remaining objects is optional. They are handled, if present, in the process_operations() function.

The proposer earns rewards for including any of the following. Rewards for attestations and sync aggregates are available every slot. Slashings, however, are very rare.

  • proposer_slashings: up to MAX_PROPOSER_SLASHINGS ProposerSlashing objects may be included.
  • attester_slashings: up to MAX_ATTESTER_SLASHINGS AttesterSlashing objects may be included.
  • attestations: up to MAX_ATTESTATIONS (aggregated) Attestation objects may be included. The block proposer is incentivised to include well-packed aggregate attestations, as it receives a micro reward for each unique attestation. In a perfect world, with perfectly aggregated attestations, MAX_ATTESTATIONS would be equal to MAX_COMMITTEES_PER_SLOT; in our configuration it is double. This provides capacity in blocks to catch up with attestations after skip slots, and also room to include some imperfectly aggregated attestations.

Including voluntary exits is optional, and there are no explicit rewards for including them.


class BeaconBlock(Container):
    slot: Slot
    proposer_index: ValidatorIndex
    parent_root: Root
    state_root: Root
    body: BeaconBlockBody

BeaconBlock just adds some blockchain paraphernalia to BeaconBlockBody. It is identical to BeaconBlockHeader, except that the body_root is replaced by the actual block body.

slot is the slot the block is proposed for.

proposer_index was added to avoid a potential DoS vector, and to allow clients without full access to the state to still know useful things.

parent_root is used to make sure that this block is a direct child of the last block we processed.

In order to calculate state_root, the proposer is expected to run the state transition with the block before propagating it. After the beacon node has processed the block, the state roots are compared to ensure they match. This is the mechanism for tying the whole system together and making sure that all validators and beacon nodes are always working off the same version of state (absent any short-term forks).

If any of these is incorrect, then the block is invalid with respect to the current beacon state and will be ignored.

Created by Ben Edgington. Licensed under CC BY-SA 4.0. Published 2022-05-12 12:26 UTC. Commit 0cc9f0b.