During the past two weeks, team members have aimed to specialize in various areas of the consensus layer with the objective of advancing the implementation of the client and facilitating onboarding for new contributors. My primary responsibility has been to gain a deep understanding of the State Transition function within the consensus layer of Ethereum. Throughout this period, I have achieved two main milestones:
The State Transition function lies at the heart of blockchains, driving their evolution. It transitions an initial state ( S0 ) into a subsequent state ( S1 ), as defined by:
Here, ( S' ) denotes the state after applying the State Transition function ( f ) to a State ( S ) applying a Signed Beacon Block ( B ).
The Ethereum POS Beacon Chain has evolved its state transition function compared to its predecessor in Ethereum POW. Ethereum POW was block-driven, while the Beacon Chain is now slot-driven. This signifies that a state transition occurs every slot (currently every 12 seconds), irrespective of whether a block is received.
The Beacon Chain state transition function is composed of 3 core pieces:
Important
From the Eth2Book:
Although the beacon chain's state transition is conceptually slot-driven, as the spec is written a state transition is triggered by receiving a block to process. That means that we first need to fast-forward from our current slot number in the state (which is the slot at which we last processed a block) to the slot of the block we are processing. We treat intervening slots, if any, as empty. This "fast-forward" is done by process_slots()
, which also triggers epoch processing as required.
In actual client implementations, state updates will usually be time-based, triggered by moving to the next slot if a block has not been received. However, the fast-forward functionality will be used when exploring different forks in the block tree.
The state transition part of a consensus client can be considered as black box, no other parts of the client need to be understood (thoroughly) to be able to make an implementation, furthermore it can be used as a standalone component.
The goal through decomposition is to enable the thorough understanding of the inner workings of the function. Going through each argument and line one by one. To not get too much into the weeds we will only go 1 layer deep into the function.
The state_transition
function is declared as follows:
def state_transition(state: BeaconState, signed_block: SignedBeaconBlock, validate_result: bool=True) -> None:
block = signed_block.message
# Process slots (including those with no blocks) since block
process_slots(state, block.slot)
# Verify signature
if validate_result:
assert verify_block_signature(state, signed_block)
# Process block
process_block(state, block)
# Verify state root
if validate_result:
assert block.state_root == hash_tree_root(state)
Parameter | Type | Description |
---|---|---|
state |
BeaconState |
Describes the current state of the client. |
signed_block |
SignedBeaconBlock |
Represents the newly received block. |
validate_result |
boolean |
If validation is required; default is true. |
Return Value | None |
As it's a pure function, no value is returned. |
Important
In this example the return value is None
because of the use of Python being an OOP language. Elixir being a functional language makes:
Hence here LECC would return a new updated BeaconState
.
Extracting the block message from the SignedBeaconBlock
:
block = signed_block.message
Processing slots:
process_slots(state, block.slot)
If validate_result
is true, the block signature is verified:
assert verify_block_signature(state, signed_block)
The block is then processed:
process_block(state, block)
Lastly, if validate_result
is true, the state root is verified:
assert block.state_root == hash_tree_root(state)
The third core piece of the consensus layer state transition function is contained in the process_slots
function. It's the process_epoch
function. See declaration
The SignedBeaconBlock
is a secure wrapper around the BeaconBlock
container it is one of the two fundamental container of a consensus client with BeaconState
. BeaconBlock
is the container used by the leader to communicate network updates to all other validators. BeaconState
are the states of the chain kept internally by the clients, updated through the application of new BeaconBlock
s.
The validate_result
boolean can be turned to false when the client becomes a proposer (certain specific situations)
The state_transition()
function, as the highest-order function, calls lower-order functions, which in turn utilize helper functions. These helper functions are categorized as:
These functions verify specific conditions and return true
or false
. Typically, they start with is_
. For example, is_active_validator(validator, epoch)
verifies if a validator is active during a particular epoch.
def is_active_validator(validator: Validator, epoch: Epoch) -> bool:
"""
Check if ``validator`` is active.
"""
return validator.activation_epoch <= epoch < validator.exit_epoch
Functions that access and compute data regarding the passed state. They do not perform any mutation. For instance, get_current_epoch(state)
returns the current epoch.
def get_current_epoch(state: BeaconState) -> Epoch:
"""
Return the current epoch.
"""
return compute_epoch_at_slot(state.slot)
Functions that modify the passed state. An example is increase_balance(state, index, delta)
, which augments the balance of a specific validator.
def increase_balance(state: BeaconState, index: ValidatorIndex, delta: Gwei) -> None:
"""
Increase the validator balance at index ``index`` by ``delta``.
"""
state.balances[index] += delta
Other important functions assisting data computations, such as compute_timestamp_at_slot(state, slot)
, which calculates the timestamp for a given slot.
def compute_timestamp_at_slot(state: BeaconState, slot: Slot) -> uint64:
slots_since_genesis = slot - GENESIS_SLOT
return uint64(state.genesis_time + slots_since_genesis * SECONDS_PER_SLOT)
For a holistic view of the relationships and dependencies among all the state transition functions, refer to the provided visual representation:
Important
If there is one thing you should look at it's this 👇.
SVG version:
PNG version:
To explore the tree:
Furthermore the tree is accompanied by a legend enabling the correct understanding of the tree structure.
Most high-level state transition functions are accompanied by EF test vectors. Before submitting your implementation, ensure you pass these tests.
The Visual Function Tree highlights which functions are testable.
For a comprehensive understanding of testing your implementation, explore the consensus tests documentation.
Standing on the shoulders of giants is a prerequisite to innovation. Thank you to all the brilliant minds that contributed to this domain. This work draws inspiration and builds upon: