Part 3: Annotated Specification

Safe Block


The Fork Choice Safe Block spec is not really part of the beacon chain's fork choice and is located in a different document in the consensus repo. It is an heuristic for using the fork choice's Store data to identify a block that will not be reverted, under some reasonable assumptions. It could be used, for example, by applications to implement a settlement period for transactions. There is an analogy with the assumption that, under proof of work, in the absence of a 51% attack, a block becomes safe from reorgs after a certain number of blocks (say, fifteen) have been built on top of it.

Under honest majority and certain network synchronicity assumptions there exist a block that is safe from re-orgs. Normally this block is pretty close to the head of canonical chain which makes it valuable to expose a safe block to users.

This section describes an algorithm to find a safe block.

Of course, the ultimate safe block is the last finalised checkpoint. But that could be several minutes in the past, even under ideal network conditions. If we assume (a) that there is an honest majority of validators, and (b) that their messages are received in a timely fashion, then we can in principle identify a more recent block that will not be at risk of reversion.


def get_safe_beacon_block_root(store: Store) -> Root:
    # Use most recent justified block as a stopgap
    return store.justified_checkpoint.root

Note: Currently safe block algorithm simply returns store.justified_checkpoint.root and is meant to be improved in the future.

Since the protocol handles many attestations per slot, it ought to be possible to use the Store's latest_messages table to identify a very recent block as safe. Some work has been done in this direction, but is incomplete. Meanwhile, the algorithm simply returns the most recently justified checkpoint. This is certainly safe under the assumptions above, but we could probably do better in finding a more recent safe block.


def get_safe_execution_payload_hash(store: Store) -> Hash32:
    safe_block_root = get_safe_beacon_block_root(store)
    safe_block = store.blocks[safe_block_root]

    # Return Hash32() if no payload is yet justified
    if compute_epoch_at_slot(safe_block.slot) >= BELLATRIX_FORK_EPOCH:
        return safe_block.body.execution_payload.block_hash
        return Hash32()

Note: This helper uses beacon block container extended in Bellatrix.

Bellatrix was the pre-Merge upgrade that added the execution payload hash to beacon blocks in readiness for the Merge itself. Applications on Ethereum are largely unaware of the beacon chain and will use the execution payload hash rather than the beacon block root as their reference point in the Eth1 blockchain.

Created by Ben Edgington. Licensed under CC BY-SA 4.0. Published 2023-07-01 13:14 UTC. Commit d859d30.