Fork Recovery
Last updated
Last updated
When we think of rollups checkpointing to an L1, one of the first things that comes to mind is allowing the rollups to inherit the security of the L1. This subject is subtle, and discussions of it often lack a clear accounting of the security properties we hope for the rollup to obtain and how exactly they are inherited. This section will detail how rollups that checkpoint to an L1 inherit an extra level of finality from the L1.
First, it is important to note that Espresso, on its own, even without any form of checkpointing, already offers extremely strong finality guarantees. In order to break finality, and adversary must cause a safety violation in the HotShot finality gadget, but HotShot is a secure BFT protocol—violating its security requires an adversary to control over 1/3 of the total stake, a massive investment once HotShot reaches a critical mass of adoption.
Nevertheless, we can consider what happens in the extremely unlikely event that HotShot does suffer a safety violation, and some block which was considered final according to the HotShot protocol is removed from the history of the ledger. In this case, we can maintain finality by using the HotShot checkpoints on L1 to decide on an immutable, canonical chain.
Let's look at how such a scenario could happen. There are two ways an adversary could compromise HotShot's finality.
First, if they control more than 1/3 of the total stake at any given time, they can cause or exploit a failure in the network connecting honest nodes, partitioning honest nodes controlling 1/3 of the stake each into two disjoint sub-networks. The adversary, being Byzantine, can then use their 1/3 of the stake to vote for conflicting blocks in each partition, committing both conflicting blocks with a 2/3 quorum each, and thus creating a fork.
Second, an adversary can create a fork without even controlling 1/3 of the current stake via a long range attack. In a long range attack, an adversary compromises some old private keys which were used to sign a quorum certificate many blocks ago. This may be substantially cheaper than compromising current private keys, as the owners of the old private keys may already have withdrawn their stake and thus may not be very invested in maintaining the security of their keys.
While compromising old keys does not directly enable the adversary to append new invalid blocks, they can build a new fork including signed QCs starting from the block at which they compromised the keys, and unilaterally grow this fork until it is similar in length to the canonical chain.
Once an adversary has created a fork, they can compromise finality by convincing honest nodes to switch from the canonical branch of the fork to the malicious branch. Blocks which were considered finalized on the canonical branch may not have been committed at all on the malicious branch.
The adversary does this by taking advantage of an ambiguity which honest nodes must somehow resolve when they are joining the network for the first time or catching up after being offline for some time. In order to catch up, such nodes will look for signed QCs attesting to HotShot state changes, so they can figure out which state to sync with. Since they cannot tell the difference between honest peers and adversaries, and adversary who can provide QCs from the malicious fork faster than honest peers can provide QCs from the canonical fork can convince honest nodes in catchup to sync to the malicious fork. Over time, the adversary may convince enough honest nodes to switch that the malicious fork becomes the "canonical" one, and blocks which were committed in the old canonical fork are permanently lost.
By leveraging the L1 checkpoints, we can prevent an adversary from tricking honest nodes onto the wrong branch of a fork, even if the adversary can present signed QCs. We simply use the checkpointed state on L1 as a deterministic fork choice rule: when presented with conflicting QCs during catchup, an honest node will choose the one which is compatible with the state checkpointed on L1. In this way, the L1 checkpoint plays for the Espresso blockchain the same role that weak subjectivity checkpoints play for Ethereum.
Choosing a fork consistent with the latest L1 checkpoint only requires a node to check the history between the latest L1 checkpoint and the QCs justifying each branch of the fork, which should be a fairly small, bounded amount of work, assuming checkpoints are posted at a fixed rate and fairly frequently. This is far more practical than storing and validating the entire history, which a node would have to do in order to detect a fork arbitrarily far in the past without using a trusted checkpoint.
In order to ensure that this fork choice rule uniquely specifies a branch, the L1 sequencer contract enforces a linear history. This is easy enough to do by storing the latest block commitment and checking that each new block specifies the previous block commitment as its parent (and has the correct height). This is difficult to do in a node because it requires the node to receive every single block in order, but in practice nodes are not always online, and when they recover from an outage they catch up immediately to the head of the chain without validating consensus for every intermediate block. The contract, on the other hand, does behave as if it is always online and directly validates the QC for every block consecutively, because anyone can drive the contract forward by providing a block and a QC to append, so we only require that some honest node is online for every block in order for the contract to remain live.
By forcing the L1 checkpoints to be linear and ensuring that honest nodes always work on a fork which is compatible with those checkpoints, we gain L1-level finality for any blocks which have been included in an L1 checkpoint. Note that this checkpointing scheme does not prevent an adversary from causing a fork—the L1 HotShot contract will not distinguish between "malicious" and "canonical" forks when appending a new block, so long as that block has a valid QC and follows from its parent. It will take whichever block it sees first. However, it will ensure that once a block is added to the contract, its fork becomes canonical, and the block will forever remain in the canonical chain.