FP System Components: FPP, FPVM & the Pre-image Oracle
OP Stack chains today secure roughly $5 billion in user assets by publishing an output root to Ethereum every few minutes. Fault proofs make the output root falsifiable. But a proof only works if the verifying VM can replay the exact same inputs the rollup saw. Delivering those bytes – securely and deterministically – takes three coordinated parts:
- Fault-Proof Program (FPP) – a stateless re-execution of the rollup logic.
- Fault-Proof VM (FPVM) – the deterministic machine that runs the program. Cannon is the reference implementation today, but any VM that follows the spec can step in.
- Pre-image Oracle – a hash-locked data feed that serves every byte the FPP and FPVM need.
Wire any one of them wrong and a malicious root can slip through – or an honest one can be bricked.
Fault Proof Program (FPP)
The Fault Proof Program (FPP) operates by simulating the L2 rollup’s state transition function using only Layer 1 (L1) inputs. This simulation reproduces the L2 state, enabling verification of disputed outputs without relying on the internal state of the L2 chain. The program is provided with a claim – an output root submitted during a dispute – which it attempts to re-derive using the known L1 inputs. The derived result is then compared against the claimed output root to determine validity. The FPP achieves this through:
- Stateless Execution: The program runs without preserving internal state between executions. Instead, it queries the Pre-image Oracle to retrieve any required data – such as account state or block contents – on-demand, using pre-image keys derived from input claims.
- Deterministic Output: Given the same inputs, the program consistently produces the same final output and execution trace – an essential requirement for resolving disputes on L1.
During execution, the FPP interacts with the Pre-image Oracle to retrieve all required inputs. These include bootstrap data, L1 block headers, transactions, receipts, and L2-specific elements such as block headers, transactions, and state trie nodes.
The op-program is the reference implementation of the FPP. It is derived from the execution logic of op-node and op-geth, and compiled into a MIPS binary that runs within the FPVM (e.g., Cannon). The program is structured into three main sections:
- Prologue: Initializes the execution environment and fetches the pre-images needed to begin execution.
- Main Content: Applies L1-derived inputs to the known L2 state to reproduce the disputed state transition logic.
- Epilogue: Finalizes execution and emits the resulting, indicating whether the claimed output root is valid or not.
These sections are orchestrated by the RunProgram function:
// RunProgram executes the Program, while attached to an IO based pre-image oracle, to be served by a host.
func RunProgram(logger log.Logger, preimageOracle io.ReadWriter, preimageHinter io.ReadWriter, cfg Config) error {
pClient := preimage.NewOracleClient(preimageOracle)
hClient := preimage.NewHintWriter(preimageHinter)
l1PreimageOracle := l1.NewCachingOracle(l1.NewPreimageOracle(pClient, hClient))
l2PreimageOracle := l2.NewCachingOracle(l2.NewPreimageOracle(pClient, hClient, cfg.InteropEnabled))
if cfg.InteropEnabled {
bootInfo := boot.BootstrapInterop(pClient)
return interop.RunInteropProgram(logger, bootInfo, l1PreimageOracle, l2PreimageOracle, !cfg.SkipValidation)
}
if cfg.DB == nil {
return errors.New("db config is required")
}
bootInfo := boot.NewBootstrapClient(pClient).BootInfo()
derivationOptions := tasks.DerivationOptions{StoreBlockData: cfg.StoreBlockData}
return RunPreInteropProgram(logger, bootInfo, l1PreimageOracle, l2PreimageOracle, cfg.DB, derivationOptions)
}
Prologue – bootstrapping
Bootstrapping begins with special input requests made to the host environment, initiating the client through the pre-image oracle. The current explanation follows the non-Interop path, though this may change once Interop is launched.
type BootstrapClient struct {
r oracleClient
}
func NewBootstrapClient(r oracleClient) *BootstrapClient {
return &BootstrapClient{r: r}
}
Inputs are retrieved from the oracle and returned in the form of a BootInfo structure:
type BootInfo struct {
L1Head common.Hash
L2OutputRoot common.Hash
L2Claim common.Hash
L2ClaimBlockNumber uint64
L2ChainID eth.ChainID
L2ChainConfig *params.ChainConfig
RollupConfig *rollup.Config
}
Main phase – deterministic derivation
This stage effectively acts as a deterministic re-execution of the L2 derivation pipeline, replicating how an honest OP Stack node would compute the L2 state transition using only L1 inputs. Unlike full clients, this execution is performed in a stateless and verifiable environment (the Fault Proof VM), and the resulting output root is used to validate or dispute the original L2 commitment.
The goal is to deterministically compute the expected L2 state, given a known L1 head and a disputed output root, and to validate whether the computed state matches the claimed result.
The following function defines the execution entry point:
func RunPreInteropProgram(
logger log.Logger,
bootInfo *boot.BootInfo,
l1PreimageOracle *l1.CachingOracle,
l2PreimageOracle *l2.CachingOracle,
db l2.KeyValueStore,
opts tasks.DerivationOptions,
) error {
logger.Info("Program Bootstrapped", "bootInfo", bootInfo)
result, err := tasks.RunDerivation(
logger,
bootInfo.RollupConfig,
bootInfo.L2ChainConfig,
bootInfo.L1Head,
bootInfo.L2OutputRoot,
bootInfo.L2ClaimBlockNumber,
l1PreimageOracle,
l2PreimageOracle,
db,
opts,
)
if err != nil {
return err
}
return claim.ValidateClaim(logger, eth.Bytes32(bootInfo.L2Claim), result.OutputRoot)
}
This derivation logic is equivalent to that implemented in rollup clients such as op-node, which reconstruct the L2 chain by applying L1-originated data to a known L2 state.
The primary distinction is that instead of retrieving inputs via RPC and persisting state changes to disk, the Fault Proof Program loads all required inputs through the pre-image oracle and accumulates state transitions entirely in memory. This ensures a fully self-contained and deterministic execution environment, suitable for proof generation and on-chain verification.
Epilogue – verdict
Finally, claim.ValidateClaim concludes the dispute by comparing the result of the derivation process with the originally claimed output root.
Cannon Binary for FFP Emulation
Cannon is the reference implementation of the OP Stack’s Fault Proof Virtual Machine (FPVM), designed to emulate and verify L2 state transitions during fault dispute games. It simulates a minimal 32-bit MIPS32 architecture (big-endian), running a stripped-down Linux-like environment. Each step of execution corresponds to a single MIPS instruction, producing a deterministic state transition. This sequence of transitions forms a verifiable execution trace.
Cannon uses a binary Merkle tree to represent memory, with 4-byte-aligned access, and supports a limited syscall set tailored for deterministic operation. This includes basic I/O and pre-image access. However, the FPVM does not enforce the validity of the MIPS binary itself – developers must ensure the program avoids undefined behavior and unsupported features like exceptions or concurrency.
Cannon’s core purpose is to support on-chain resolution of disputes via the FaultDisputeGame. To do this, it produces a complete execution trace of the Fault Proof Program (FPP), where each state transition under a single instruction is called a Step. The ordered sequence of these steps constitutes the full program trace.
Each VM state is encoded by serializing the fields of the State struct into a 226-byte format used in Cannon’s current MIPS-based FPVM.
// STATE_WITNESS_SIZE is the size of the state witness encoding in bytes.
// ignoring 64-bit STATE_WITNESS_SIZE as it's not supported for singlethreaded
const STATE_WITNESS_SIZE = 226
type State struct {
Memory *memory.Memory `json:"memory"`
PreimageKey common.Hash `json:"preimageKey"`
PreimageOffset Word `json:"preimageOffset"` // note that the offset includes the 8-byte length prefix
Cpu mipsevm.CpuScalars `json:"cpu"`
Heap Word `json:"heap"` // to handle mmap growth
ExitCode uint8 `json:"exit"`
Exited bool `json:"exited"`
Step uint64 `json:"step"`
Registers [32]Word `json:"registers"`
// LastHint is optional metadata, and not part of the VM state itself.
LastHint hexutil.Bytes `json:"lastHint,omitempty"`
}
To allow dispute resolution, Cannon must emit witness data for each executed step. The FPVM interface exposes a Step method that returns a structured witness for the current instruction:
type FPVM interface {
…
// Step executes a single instruction and returns the witness for the step
Step(includeProof bool) (*StepWitness, error)
type StepWitness struct {
// encoded state witness
State []byte
StateHash common.Hash
ProofData []byte
PreimageKey [32]byte // zeroed when no pre-image is accessed
PreimageValue []byte // including the 8-byte length prefix
PreimageOffset arch.Word
}
The StepWitness serves as the on-chain proof of correct execution for a single instruction. It includes the following components:
- State Data: The full VM state before executing the instruction.
- Memory Proofs (ProofData): Merkle proofs for memory reads and writes performed by the instruction.
- Pre-image Data: Some MIPS instructions, particularly syscalls like file reads or preimage fetches, require access to external data committed by hash. If the instruction accesses such a preimage, the StepWitness includes the key (i.e., the hash being accessed), the resolved value, and the byte offset within it. This mechanism supports the getPreimage(bytes32) ABI used on-chain, enabling Ethereum to validate external data dependencies against precommitted preimages. (This will be discussed in more detail in the next blog post.)
Together, these witness components ensure that every MIPS instruction in the Fault Proof Program can be independently and verifiably validated on Ethereum without relying on external trust assumptions.
Pre-image Oracle
The Pre-image Oracle is a component in the OP Stack fault proof system that provides data access to the Fault Proof Program (FPP) during execution. Because the FPP runs in a stateless, deterministic environment, it cannot directly query external state (e.g., L1 or L2 chain data, file systems, or in-memory values). Instead, all required data must be requested through the Pre-image Oracle interface, using pre-image keys, which are typically hashes that represent committed inputs.
The Pre-image Oracle acts as a bridge between the FPVM and the world of input data. It provides:
- Access to precommitted inputs, such as L1 and L2 block headers, transactions, receipts, and state nodes.
- A uniform interface for resolving data lookups requested by the FPP, based on hashed identifiers (pre-image keys).
- A trust-minimized mechanism to bind execution to known, verifiable inputs – ensuring the same input always results in the same state.
During execution, the FPP makes syscall requests for data it cannot compute internally – for example, when verifying an L1 transaction or fetching part of the L2 state trie. These requests are routed through syscall instructions like SYSCALL_LOAD_PREIMAGE, which are intercepted by the FPVM and forwarded to the Pre-image Oracle.
The oracle resolves the request using a keyed lookup, returning the associated pre-image value (if available). The key typically encodes:
- A prefix indicating the data type (l1-header, l2-tx, etc.)
- Metadata like block numbers or trie paths
- A trailing hash or fixed-length encoding
Off-chain (e.g., in Cannon), this data is pulled from local files or JSON. On-chain, it’s accessed through the PreimageOracle.sol contract, which provides:
/// @notice Reads a preimage from the oracle.
/// @param _key The key of the preimage to read.
/// @param _offset The offset of the preimage to read.
/// @return dat_ The preimage data.
/// @return datLen_ The length of the preimage data.
function readPreimage(bytes32 _key, uint256 _offset) external view returns (bytes32 dat_, uint256 datLen_)
This allows any on-chain component (e.g., MIPS.sol) to resolve the input deterministically, assuming the pre-image has been published in advance.
The FPP depends on the Pre-image Oracle to fetch every external input:
- At bootstrap, it queries L1 and L2 chain states to initialize the program.
- During execution, it resolves trie nodes, storage slots, and other data needed to simulate the rollup logic.
The FPVM, which runs the FPP, must implement the syscall dispatch and ensure pre-image resolution is correctly wired to the oracle, either off-chain (Cannon host) or on-chain (Solidity interface). This ensures that even a single instruction step has all the required context to produce a valid state transition and witness.
On-Chain Step Verification
A critical yet distinct component of Cannon is its support for on-chain execution of a single MIPS instruction. This functionality is used at the final stage of the FaultDisputeGame, where a dispute is resolved by evaluating a specific instruction in the execution trace. The OP Stack implements this resolution mechanism via the step() function in the FaultDisputeGame contract, which invokes the Cannon VM to verify the disputed state transition on-chain.
To enable this, Cannon includes a set of contracts that emulate MIPS behavior in a verifiable and deterministic way:
- MIPS.sol – A MIPS instruction emulator implemented as a smart contract. It executes a single instruction using a Merkleized memory model, with pre- and post-state roots serving as inputs and outputs for verification. The syscall interface implements a constrained subset of the Linux ABI, supporting only operations needed by the static Go binary of the op-program: memory allocation, I/O to specific file descriptors, and clean program termination. Concurrency and advanced system calls are intentionally unsupported.
- PreimageOracle.sol – Exposes a pre-image oracle interface used by MIPS.sol to retrieve memory and state inputs not explicitly passed on-chain. This allows the system to resolve pre-image requests during MIPS execution using cryptographic commitments.
By leveraging these contracts, the fault proof system ensures that the final disputed instruction in the derivation trace can be verified directly on Ethereum, with no off-chain dependencies – preserving the trust-minimized nature of the OP Stack dispute process.
Fault Proof Execution Components: Off-Chain vs On-Chain
Aspect | Off-Chain Cannon VM (e.g., cannon run) | On-Chain Cannon (MIPS.sol) |
Used by | op-program, Cannon CLI, op-challenger | FaultDisputeGame.step() during final on-chain verification |
Purpose | Executes the full Fault Proof Program (FPP) to derive L2 state and generate a verifiable trace and output root | Verifies the correctness of a single instruction at a disputed trace index |
MIPS Execution | Full ELF binary execution: loads and runs the program from entry to exit() | Executes a single MIPS instruction (step) selected via interactive bisection |
State Representation | Maintains full VM state (PC, memory, registers) in memory and Merkle trees | Accepts Merkle roots and proofs to reconstruct the machine state for step execution |
Preimage Access | Preimages are served locally via JSON or file-backed oracle used during syscall resolution | Preimages must be pre-published on L1 and are retrieved via getPreimage(bytes32) |
Syscall Handling | Simulates minimal Linux syscalls (e.g., read, write, exit) via the host environment | Implements a minimal syscall interface in Solidity; no concurrency or dynamic I/O |
Conclusion
Together, the FPP, FPVM, and Pre-image Oracle form the data-integrity backbone that lets Ethereum verify every OP Stack output root without trusting the rollup operator. Each layer answers a single question: what should be executed, how is it executed, and where do the bytes come from? With those answers locked in, fault proofs turn a multi-billion-dollar claim into something anyone can challenge.
In the next post – “Dispute Games & On-Chain Resolution on OP Mainnet” – we will slice the execution trace down to a single instruction and watch Ethereum settle the score. Stay tuned.
Resources
- https://docs.optimism.io/stack/fault-proofs
- https://github.com/ethereum-optimism/specs/tree/main/specs
- https://www.youtube.com/watch?v=nIN5sNc6nQM&t=3s
- https://www.loom.com/share/1bf3ce3e491848698acb14f3df93d329
- https://blog.oplabs.co/mips-sol/
- https://docs.optimism.io/stack/security/audits-report
- https://blog.oplabs.co/sherlock-audit-roundup
- https://audits.sherlock.xyz/contests/205?ref=blog.oplabs.co
Relevant smart contracts and Golang packages:
- https://github.com/ethereum-optimism/optimism/blob/v1.13.0/packages/contracts-bedrock/src/universal/CrossDomainMessenger.sol
- https://github.com/ethereum-optimism/optimism/tree/v1.13.0/packages/contracts-bedrock/src/L1
- https://github.com/ethereum-optimism/optimism/tree/v1.13.0/packages/contracts-bedrock/src/L2
- https://github.com/ethereum-optimism/optimism/tree/v1.13.0/packages/contracts-bedrock/src/cannon
- https://github.com/ethereum-optimism/optimism/tree/v1.13.0/packages/contracts-bedrock/src/dispute
- https://github.com/ethereum-optimism/optimism/tree/v1.13.0/op-proposer
- https://github.com/ethereum-optimism/optimism/tree/v1.13.0/op-challenger
- https://github.com/ethereum-optimism/optimism/tree/v1.13.0/op-program
- https://github.com/ethereum-optimism/optimism/tree/v1.13.0/cannon
- https://github.com/ethereum-optimism/optimism/tree/v1.13.0/op-preimage
Subscribe
to our
newsletter
Be the first to receive our latest company updates, Web3 security insights, and exclusive content curated for the blockchain enthusiasts.

Table of contents
Tell us about your project
Read next:
More related- Fault Proofs 101: The Backbone of OP Stack Security
15 min read
Discover
- Actionable DeFi Security Lessons from Compound’s Incidents
9 min read
Discover
- Uniswap V2 Core Contracts: Technical Details & Risks
11 min read
Discover