Note: This is mostly rough notes from Adrian trying to understand the proposed fault proof changes for interop.
There’s an old draft PR to add interop support to fault proofs: https://github.com/ethereum-optimism/specs/pull/31/files We need to understand the approach it takes and verify that it’s safe based on all the things we’ve learnt while building fault proofs.
The PR also includes a subgame for determining L1 outputs required as a way to solve the large preimage problem. We will try to avoid actually needing that for now as it introduces a lot of complexity plus the risk that L1 makes changes in the future that aren’t fault provable for some reason (plus we’d have to do governance and update the fault proof system ahead of each L1 hard fork).
So the bisection proceeds in multiple stages:
Positional Meaning | Claim Meaning |
---|---|
Timestamp | Superchain root |
Index of chain in dependency set (+ consolidation step) | Accumulation of output roots of chains left of this one |
VM step | Cannon state hash (executing op-program either to process |
This adds a new level of bisection conceptually but the contracts should still only need to track a single split depth - the point at which we switch from posting output roots to posting cannon traces (as is the case today). That point requires the contract to enforce that the cannon execution found the output root valid or invalid as appropriate.
Conceptually that means the additional bisection stage is actually in the top half of the game, not the bottom half as might be the initial intuition given it is implemented in op-program. We bisect timestamps, then bisect chains then execute op-program to either confirm the block processing of an individual chain or confirm the cross-chain message consolidation.
As is done currently, the top half of the bisection game attempts to identify the first block that is disagreed with. Since block times may be different in the superchain, this bisection is done over timestamps rather than L2 block numbers. With fixed block times in the OP stack it is possible to convert a timestamp to a L2 block number for a particular chain.
The claim value is the superchain root:
class SuperSnapshot(Container:
chains: List[OutputRoot, MAX_SUPERCHAIN_SIZE]
timestamp: uint64
So basically combine all the output roots of each chain and hash them.
Note: In the current spec, the output roots for each chain are the safe head ≤ timestamp. We will need to be strict that the output root for a chain MUST be from the block that will ultimately be the safe head at timestamp and MUST NOT be any earlier block. The block for each chain must be safe before a valid proposal can be made. ie if a batcher for any chain is down, no later proposals can be made for any chain. Liveness is preserved only due to the sequencer window which will eventually force the safe head to advance even if the batcher isn’t fixed.
Invariant: The right most node in this bisection (across all chains) must be the superroot. The contracts don’t know about this split level so assumes that we’re “building towards” the root claim which is our super root.
Rather than immediately executing the block for a chain a new level of bisection is added to the game. This occurs above the SPLIT_DEPTH in the contract. At this stage we are effectively iterating through the list of chains to build up the chains
list in the SuperSnapshot
and just assuming that all cross chain messages are valid.