Skip to content
This repository has been archived by the owner on Jan 24, 2024. It is now read-only.

Cannon High Level Overview

norswap edited this page May 18, 2022 · 5 revisions

This page now lives on the Cannon wiki


This is a high-level overview of Cannon, originally written in support of the Cannon introductory article.

For a deeper dive of the nitty gritty of Cannon, see Cannon Overview


In our new design (Optimism Bedrock), a L2 validator simply follows the L1 chain, monitoring it for transaction batches and output commitments (which commit to the chain state after a specific block). Both of these things are submitted by the sequencer.

An L2 validator simply re-run the batches locally, and verify that the state they obtain matches the sequencer’s output commitments. If they don’t, they can challenge the results on L1. This is where Cannon enters the scene.

The goal of the whole affair is to prove, on the L1 chain, that the sequencer lied about the results of the block transition. But we can’t rerun the whole block on L1 — that would be too expensive.

Instead we decompose the problem into two steps, by leveraging minigeth, a minimally modified subset of geth (no JSON-RPC, no mining), which we compile to MIPS — a minimal RISC instruction set.

  1. Find a single MIPS instruction such that the challenger and the defender (i.e. the sequencer) agree on the memory (RAM & registries) of the minigeth program before that step, but disagree on the memory after that step.
  2. Run that single instruction on the L1 chain. minigeth is compiled to MIPS because it's easy to write a simple on-chain MIPS interpreter (only 400 lines!).

The first step is what we call the challenge game (aka bisection game or binary search), it is interactive, both parties taking turns in order to isolate the first instruction they disagree on. If a party stops replying until the end of the challenge period, the other party wins by default. Both parties always agree on the initial memory, which is a combination of the initial MIPS state (a commitment to which is stored on L1) and some inputs (e.g. L2 block hash, L2 block number).

In order to run the challenge game, both parties need to submit commitments (Merkle roots) of the minigeth MIPS memory to the L1 Challenge contract. Cannon has built-in support for running minigeth as MIPS and spitting out a commitment to the memory at a given step of the execution.

The second step is as easy as running the single instruction through our on-chain MIPS interpreter. The only caveat is that that instruction may need access to some data, to which we only have a commitment. That data is of two kinds:

  1. The MIPS memory. Since we have a commitment to it, we can simply provide the required bytes in advance and prove them against the commitment.
  2. The EVM state. This is where the preimage oracle comes into play.

The preimage oracle is a simple component that accepts a hash and returns its preimage. The preimage Oracle is part of minigeth, and so must be able to run off-chain, but also on-chain (i.e. the single instruction must be able to read memory retrieved from the preimage Oracle *).

* If you want more details, see here, but it suffices to say that the only thing that must be handled on-chain is reading the preimage from a specific address, given that the hash is written at another specific address. This is handled in our on-chain MIPS interpreter implementation.

Normally geth reads from the EVM state via an on-disk key-value database. The keys are mostly hashes (Merkle tree nodes), but not exlcusively. minigeth replaces the database with the preimage oracle and converts everything to preimage requests.

minigeth can be run off-chain to generate these preimages in advance (prefetching). The trick is that in this case, the preimage oracle is implemented by forwarding queries to another node. For prefetching, minigeth doesn't even have to be compiled to MIPS (which allows it to run faster).