Fault Proof Deep-Dive Part 2: Cannon

Part two of the Fault Proof Deep-Dive Series with Coinbase concludes the overview of Cannon, the OP Stack’s first Fault Proof Virtual Machine (FPVM) used as part of the Dispute Game.

Fault Proof Deep-Dive Part 2: Cannon

The Fault Proof Deep-Dive Series is a collaboration between Coinbase’s Blockchain Security (BlockSec) team and OP Labs to provide in-depth information on all major components of Fault Proofs. By sharing this information, we hope to encourage others to learn more about the architecture and technical aspects of Fault Proofs. Together, we can move towards the decentralized future of OP Stack L2 blockchains.

In this blog post, we’ll be covering the offchain Cannon Fault Proof Virtual Machine (FPVM) implementation. Note that both the onchain and offchain FPVM implementations together make Cannon. However, we will delineate the two by having Cannon refer to only the offchain implementation for this blog post.

What is Cannon?

In the previous blog post, we introduced MIPS.sol, the onchain FPVM. Cannon is the offchain counterpart to MIPS.sol, written in Golang. However, unlike MIPS.sol, the offchain Cannon implementation is responsible for far more steps in a dispute game. Additionally, Cannon is the sole FPVM used for Fault Dispute Games in the OP Stack.

Fault Proof Control Flow

Once again, let’s review the Fault Proof process to understand where Cannon resides.

In the above diagram recreated from the Fault Proof Walkthrough video by Clabby, we can see that Cannon interacts with OP-Challenger and OP-Program. OP-Challenger is the component that handles initiating challenges and interacting with a dispute game, and OP-Program handles the derivation of L2 output from L1 inputs. However, the diagram is a simplification of the interactions between OP-Challenger, Cannon, and OP-Program.

During the course of an active dispute game, the bisection game will move from pivoting over L2 output roots to state witness hashes. Cannon is responsible for generating state witness hashes, which are the commitment to the results of the MIPS instructions’ computation within the FPVM. Cannon will only be run once the dispute game reaches that point. This portion of the bisection game is known as the execution trace. Up until this point, OP-Challenger has been consulting OP-Node for state output roots, and has not made use of OP-Program or Cannon. However, once it is time to run Cannon for the dispute game, the already compiled OP-Program will be loaded into the VM in Cannon which will then begin to run MIPS instructions.

During the execution trace portion of the bisection game, Cannon will run many MIPS instructions, and initially will provide state witness hashes to OP-Challenger. Eventually, a single MIPS instruction will be identified as the root of disagreement between participants in the active dispute. Now, Cannon will generate the witness proof, which contains all the information required to run the MIPS instruction onchain in MIPS.sol. The definitive post-state of the MIPS instruction will then be used to resolve the fault dispute game.

Cannon Component Overview

Cannon is made up of several Golang files, which together have much more functionality than the onchain MIPS.sol. This is necessary, as Cannon is responsible for more parts of the dispute game than MIPS.sol. These Golang files can be grouped by core components of Cannon that they implement, which include: the Executable and Linkable Format (ELF) loader, memory and state management, MIPSEVM, and witness proof generation.

ELF Loader

OP-Program is the code that will be compiled into an ELF file which in turn contains MIPS instructions to run within Cannon. In order to get the contents of the ELF file into Cannon to run, it needs to be loaded into the MIPSEVM. This process involves iterating over the ELF headers to determine all of the programs that need to be loaded into the 32-bit memory space, locating the initial values for the Program Counter (PC) and NextPC, instantiating the stack, heap, and data segment pointers in memory, and patching out any incompatible functions. Patching out an incompatible function simply writes over the first instructions within a function with a return back to the original pointer, and then a no-operation (NOP). Patching the binary loaded into the MIPSEVM is important as the FPVM is unable to perform many system calls, and does not support features such as concurrency.

Memory and State Management

Cannon stores and maintains the entire 32-bit memory address space available to the MIPSEVM. There is no restriction on the size of memory stored offchain, but for MIPS.sol it is infeasible to store the entire 32-bit memory address space. Therefore, memory is stored in a binary Merkle tree data structure within Cannon. To the MIPSEVM, this data structure is abstracted away through the use of GetMemory(), ReadMemoryRange(), and SetMemory() functions. When it is time to generate the witness proof, Cannon will encode up to two memory Merkle proofs for MIPS.sol to use when running a MIPS instruction.

MIPSEVM

Within Cannon is the MIPSEVM, which implements the 32-bit, Big-Endian, MIPS III Instruction Set Architecture (ISA). Unlike MIPS.sol, the MIPSEVM will run many MIPS instructions, and is also responsible for tracking memory access which will be used to encode the second memory proof. However, both the offchain and onchain VM implementations must produce exactly the same results given the same instruction, memory, and register state. This is critical to ensuring that the expected post-state from the MIPSEVM is the same as the actual post-state generated by MIPS.sol, which will be used to resolve the dispute game.

Witness Proof Generation

Once a single MIPS instruction has been identified as the root of disagreement between participants in a fault dispute game, Cannon will generate the witness proof so that the same instruction can be run onchain in MIPS.sol. Encoded in the witness proof is the following information: the VM execution state, the memory proof for the address of the instruction to run, and an additional memory proof required only for load, store, or certain system call instructions. Additionally, should the MIPS instruction need a Pre-image, Cannon will also communicate a Pre-image key and offset to OP-Challenger so that it can be posted onchain to PreimageOracle.sol.

Documentation For Cannon

This concludes our deep-dive of Cannon. In addition to this blog post, Coinbase has created in-depth documentation that provides more details on each of the components that make up Cannon. Check it out at Fault Proof VM - Cannon | Optimism Docs, and if you’re interested in Optimism’s official technical specification of Cannon, you can view that at Cannon Technical Specification.