Part 3: Annotated Specification
Containers
Beacon state
BeaconState
class BeaconState(Container):
# Versioning
genesis_time: uint64
genesis_validators_root: Root
slot: Slot
fork: Fork
# History
latest_block_header: BeaconBlockHeader
block_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT]
state_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT]
historical_roots: List[Root, HISTORICAL_ROOTS_LIMIT] # Frozen in Capella, replaced by historical_summaries
# Eth1
eth1_data: Eth1Data
eth1_data_votes: List[Eth1Data, EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH]
eth1_deposit_index: uint64
# Registry
validators: List[Validator, VALIDATOR_REGISTRY_LIMIT]
balances: List[Gwei, VALIDATOR_REGISTRY_LIMIT]
# Randomness
randao_mixes: Vector[Bytes32, EPOCHS_PER_HISTORICAL_VECTOR]
# Slashings
slashings: Vector[Gwei, EPOCHS_PER_SLASHINGS_VECTOR] # Per-epoch sums of slashed effective balances
# Participation
previous_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] # [Modified in Altair]
current_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] # [Modified in Altair]
# Finality
justification_bits: Bitvector[JUSTIFICATION_BITS_LENGTH] # Bit set for every recent justified epoch
previous_justified_checkpoint: Checkpoint
current_justified_checkpoint: Checkpoint
finalized_checkpoint: Checkpoint
# Inactivity
inactivity_scores: List[uint64, VALIDATOR_REGISTRY_LIMIT] # [New in Altair]
# Sync
current_sync_committee: SyncCommittee # [New in Altair]
next_sync_committee: SyncCommittee # [New in Altair]
# Execution
latest_execution_payload_header: ExecutionPayloadHeader # [New in Bellatrix]
# Withdrawals
next_withdrawal_index: WithdrawalIndex # [New in Capella]
next_withdrawal_validator_index: ValidatorIndex # [New in Capella]
# Deep history valid from Capella onwards
historical_summaries: List[HistoricalSummary, HISTORICAL_ROOTS_LIMIT] # [New in Capella]
All roads lead to the BeaconState
. Maintaining this data structure is the sole purpose of all the apparatus in all the spec documents. This state is the focus of consensus among the beacon nodes; it is what everybody, eventually, must agree on.
The beacon chain's state is monolithic: everything is bundled into a single state object (sometimes referred to as the "God object"). Some have argued for more granular approaches that might be more efficient, but at least the current approach is simple.
Let's break this thing down.
# Versioning
genesis_time: uint64
genesis_validators_root: Root
slot: Slot
fork: Fork
How do we know which chain we're on, and where we are on it? The information here ought to be sufficient. A continuous path back to the genesis block would also suffice.
genesis_validators_root
is calculated at Genesis time (when the chain starts) and is fixed for the life of the chain. This, combined with the fork
identifier, should serve to uniquely identify the chain that we are on.
genesis_time
is used by the fork choice rule to work out what slot we're in, and (since The Merge) to validate execution payloads.
The values of these two fields is fixed for the life of the chain. For the mainnet beacon chain they have the following values:
genesis_time |
1606824023 |
genesis_validators_root |
0x4b363db9 4e286120 d76eb905 340fdd4e 54bfe9f0 6bf33ff6 cf5ad27f 511bfe95 |
The fork
object is manually updated as part of beacon chain upgrades, also called hard forks. This invalidates blocks and attestations from validators not following the new fork.
Since the Capella fork, the fork
field has contained the following values:
previous_version |
0x02000000 |
current_version |
0x03000000 |
epoch |
194048 |
Historical info on fork versions and upgrade timing is in the Upgrade History chapter.
# History
latest_block_header: BeaconBlockHeader
block_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT]
state_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT]
historical_roots: List[Root, HISTORICAL_ROOTS_LIMIT] # Frozen in Capella, replaced by historical_summaries
latest_block_header
is only used to make sure that the next block we process is a direct descendent of the previous block. It's a blockchain thing.
Past block_roots
and state_roots
are stored in the lists here until the lists are full. Before the Capella upgrade, once the lists were full, they were Merkleized together and the root appended to historical_roots
. Since Capella, they are now Merkleized separately and appended to historical_summaries
(see below). The historical_roots
list is now frozen and continues to exist only to allow proofs to be made against pre-Capella blocks and states.
# Eth1
eth1_data: Eth1Data
eth1_data_votes: List[Eth1Data, EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH]
eth1_deposit_index: uint64
eth1_data
is the latest agreed upon state of the Eth1 chain and deposit contract. eth1_data_votes
accumulates Eth1Data
from blocks until there is an overall majority in favour of one Eth1 state. If a majority is not achieved by the time the list is full then it is cleared down and voting starts again from scratch. eth1_deposit_index
is the total number of deposits that have been processed by the beacon chain (which is greater than or equal to the number of validators, as a deposit can top up the balance of an existing validator).
# Registry
validators: List[Validator, VALIDATOR_REGISTRY_LIMIT]
balances: List[Gwei, VALIDATOR_REGISTRY_LIMIT]
The registry of Validator
s and their balances. The balances
list is separated out as it changes much more frequently than the validators
list. Roughly speaking, balances of active validators are updated at least once per epoch, while the validators
list has only minor updates per epoch. When combined with SSZ tree hashing, this results in a big saving in the amount of data to be rehashed on registry updates. See also validator inactivity scores under Inactivity which we treat similarly.
# Randomness
randao_mixes: Vector[Bytes32, EPOCHS_PER_HISTORICAL_VECTOR]
Past randao mixes are stored in a fixed-size circular list for EPOCHS_PER_HISTORICAL_VECTOR
epochs (~290 days). These can be used to recalculate past committees, which allows slashing of historical attestations. See EPOCHS_PER_HISTORICAL_VECTOR
for more information.
# Slashings
slashings: Vector[Gwei, EPOCHS_PER_SLASHINGS_VECTOR]
A fixed-size circular list of past slashed amounts. Each epoch, the total effective balance of all validators slashed in that epoch is stored as an entry in this list. When the final slashing penalty for a slashed validator is calculated, it is weighted with the sum of this list. This mechanism is designed to less heavily penalise one-off slashings that are most likely accidental, and more heavily penalise mass slashings during a window of time, which are more likely to be a coordinated attack.
# Participation
previous_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] # [Modified in Altair]
current_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] # [Modified in Altair]
These lists record which validators participated in attesting during the current and previous epochs by recording flags for timely votes for the correct source, the correct target and the correct head. We store two epochs' worth since Validators have up to 32 slots to include a correct target vote. The flags are used to calculate finality and to assign rewards at the end of epochs.
Previously, during Phase 0, we stored two epochs' worth of actual attestations in the state and processed them en masse at the end of epochs. This was slow, and was thought to be contributing to observed late block production in the first slots of epochs. The change to the new scheme was implemented in the Altair upgrade under the title of Accounting Reforms.
# Finality
justification_bits: Bitvector[JUSTIFICATION_BITS_LENGTH]
previous_justified_checkpoint: Checkpoint
current_justified_checkpoint: Checkpoint
finalized_checkpoint: Checkpoint
Ethereum 2.0 uses the Casper FFG finality mechanism, with a k-finality optimisation, where k = 2. The above objects in the state are the data that need to be tracked in order to apply the finality rules.
justification_bits
is only four bits long. It tracks the justification status of the last four epochs: 1 if justified, 0 if not. This is used when calculating whether we can finalise an epoch.- Outside the finality calculations,
previous_justified_checkpoint
andcurrent_justified_checkpoint
are used to filter attestations: valid blocks include only attestations with a source checkpoint that matches the justified checkpoint in the state for the attestation's epoch. finalized_checkpoint
: the network has agreed that the beacon chain state at or before that epoch will never be reverted. So, for one thing, the fork choice rule doesn't need to go back any further than this. The Casper FFG mechanism is specifically constructed so that two conflicting finalized checkpoints cannot be created without at least one third of validators being slashed.
# Inactivity
inactivity_scores: List[uint64, VALIDATOR_REGISTRY_LIMIT] # [New in Altair]
This is logically part of "Registry", above, and would be better placed there. It is a per-validator record of inactivity scores that is updated every epoch. This list is stored outside the main list of Validator objects since it is updated very frequently. See the Registry for more explanation.
# Sync
current_sync_committee: SyncCommittee # [New in Altair]
next_sync_committee: SyncCommittee # [New in Altair]
Sync committees were introduced in the Altair upgrade. The next sync committee is calculated and stored so that participating validators can prepare in advance by subscribing to the required p2p subnets.
# Execution
latest_execution_payload_header: ExecutionPayloadHeader # [New in Bellatrix]
Since the Merge, the header of the most recent execution payload is cached in the beacon state. This serves two functions for now, though possibly more in future. First, it allows the chain to check whether the Merge has been completed or not. See is_merge_transition_complete()
. Second, it allows the beacon chain to check that the execution chain is unbroken when processing a new execution payload. See process_execution_payload()
.
# Withdrawals
next_withdrawal_index: WithdrawalIndex # [New in Capella]
next_withdrawal_validator_index: ValidatorIndex # [New in Capella]
Automatic validator balance withdrawals were added in the Capella upgrade. The next_withdrawal_index
maintains a count of the total number of withdrawal transactions performed so far, while next_withdrawal_validator_index
cycles through the validator registry to keep track of which validator should be considered for a withdrawal next. Validators are considered for withdrawals consecutively in order of their validator indices, and the withdrawals sweep wraps round to zero after considering the highest-numbered validator. A maximum of MAX_WITHDRAWALS_PER_PAYLOAD
withdrawals may be made per block.
# Deep history valid from Capella onwards
historical_summaries: List[HistoricalSummary, HISTORICAL_ROOTS_LIMIT] # [New in Capella]
Hash tree roots of state.block_roots
and state.state_roots
are periodically added to historical_summaries
every SLOTS_PER_HISTORICAL_ROOT
slots as part of the protocol's double batched accumulator. The work is done by process_historical_summaries_update()
.
The state.historical_summaries
list was introduced in the Capella upgrade and functionally replaces the state.historical_roots
list that's now frozen (see above). It uses the HistoricalSummary
container, which is twice as big as a Root
type (64 bytes per item rather than 32). The list will effectively grow without bound (HISTORICAL_ROOTS_LIMIT
is large), but at a rate of only 20 KB per year. Keeping this data is useful for light clients, and also allows Merkle proofs to be created against past states, for example historical deposit data.
Historical Note
There was a period during which beacon state was split into "crystallized state" and "active state". The active state was constantly changing; the crystallized state changed only once per epoch (or what passed for epochs back then). Separating out the fast-changing state from the slower-changing state was an attempt to avoid having to constantly rehash the whole state every slot. With the introduction of SSZ tree hashing, this was no longer necessary, as the roots of the slower changing parts could simply be cached, which was a nice simplification. There remains an echo of this approach, however, in the splitting out of validator balances and inactivity scores into different structures withing the beacon state.