Explaining finality

When interacting with a consensus client via the Beacon API, you’ll get a finalized field as part of the response in some endpoints. I implemented that in Lighthouse, and in this essay I will explain what finality is, and how it works.

Ben Edgington wrote a great definition of finality in Upgrading Ethereum:

Finality is the idea that there are blocks that will never be reverted. When a block has been finalized, all the honest nodes on the network have agreed that the block will forever remain part of the chain's history, and therefore that all of its ancestors will remain in the chain's history. Finality makes your payment for pizza as irrevocable as if you'd handed over cash. It is the ultimate protection against double-spending.

In practice, finality is defined based on the concept of slots and epochs. 

Time in the beacon chain is split in 12 second periods. Each of those periods is a slot, and in each slot a block can be built. An epoch is 32 slots.

The epoch previous to the current epoch is considered to be justified. The epoch previous to the justified epoch is finalized. When an epoch is finalized, it only finalizes the 0th slot of the epoch and all prior slots. If epoch 1 is finalized, then slots <= 32 are finalized, but slot 33 is not.

Slots, Epochs, Justification and Finality
Slots, Epochs, Justification and Finality

When communicating with Lighthouse via the Beacon Node HTTP API, the value of the finalized field for endpoints related to blocks, e.g. /eth/v2/beacon/blocks/{block_id}, is computed by calling is_finalized_block. [1]

The function takes a slot and the root of a block, and returns a bool indicating if the block is finalized. It is implemented for the BeaconChain struct, which:

Represents the "Beacon Chain" component of Ethereum 2.0. Allows import of blocks and block operations and chooses a canonical head.

What is important for finalization is that last part, the canonical head. In Lighthouse, the canonical head stores the finalized_checkpoint, and the checkpoint stores an epoch. Having that epoch, we just have to ask for its first slot to know what the latest finalized slot is.

We could just check if the number of the given slot is less or equal than the number of the latest finalized slot. However, this has a big flaw, how do we know if the given block is actually an ancestor of the block in the finalized slot? We have to check if the given block is part of the chain.

This is done like this: 

  • Ask the beacon chain what the block root is at a given slot.

  • Check if the root given by the beacon chain is the same as the one given in the function call.

With this, we can know if the given block is finalized. Being older than the latest finalized block AND an ancestor of the finalized block. In boolean terms:

block_slot <= finalized_slot && is_canonical

Notes

[1] This field is also returned for endpoints related to States. In that case, is_finalized_state is called. The implementation for states and blocks is almost identical, so I’ll only explain blocks in this essay.

Subscribe to Daniel Ramírez-Chiquillo
Receive the latest updates directly to your inbox.
Nft graphic
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.
More from Daniel Ramírez-Chiquillo

Skeleton

Skeleton

Skeleton