Antler by Autoflux

Execution Engine

op-geth — the go-ethereum fork that executes L2 transactions, deposit transactions, the L1Block system contract, and the fee economics.

Path: execution

Third-party documentation. This is independently authored analysis of the public Optimism (OP Stack) codebase — not the official docs, and not reviewed or endorsed by the Optimism (OP Stack) team.

Execution Engine

The execution layer of an OP Stack chain is op-geth, a fork of go-ethereum. For an application developer the differences are deliberately small — the JSON-RPC API is the same Ethereum API, most tooling works unchanged, and Solidity contracts run unmodified. The differences live in how blocks are produced and how L1↔L2 interactions are expressed.

Block production: the engine API

In Ethereum, miners/validators produce blocks via consensus and hand them to the execution layer. In the OP Stack, the sequencer's op-node plays the role of consensus, and op-geth the role of execution. The contract between them is the Engine API (the same namespace go-ethereum uses for PoS), with a rollup-flavored payload:

  1. op-node calls engine_forkchoiceUpdated with a PayloadAttributes describing the next block (parent hash, timestamp, transactions from the batch, L1 origin info).
  2. op-geth builds the payload and returns it via engine_getPayload.
  3. op-node broadcasts the executed block hash over P2P; other nodes replay the same attributes to reach the same state.

Because attributes are fully deterministic (transactions + L1 origin), every node executing them computes the identical block — no PoW, no uncle selection, no proposer randomness.

Deposit transactions (type 0x7E)

The one transaction type that does not exist on L1. Deposits enter the L2 state from the L1 portal and are injected into the chain by the engine, not submitted by users. Their defining properties:

  • Gas is prepaid on L1 — the portal deducts the L2 gas cost (plus an L1 data fee) from the depositor's L1 funds, so a deposit can never be dropped for lack of L2 balance.
  • They bypass the mempool — they are forced into the block at their position in the batch.
  • They cannot be reordered by the sequencer relative to each other — their order is fixed by the L1 deposit event order in the batch.
  • Their sender is 0xDeadDeAd...0001 at the protocol level, though the actual depositor is recorded in the transaction's from field.

The L1Block system contract

Every L2 block references an L1 origin block. The protocol injects an L1Block system contract (at a well-known predeploy address) whose storage records the origin's number, timestamp, basefee, hash, and a sequence number. Applications read it to know "what L1 block is this L2 block anchored to" and, importantly, to compute L1-based prices and timestamps without a separate oracle.

Fee economics

  • L2 execution fee — the ordinary EIP-1559-style base fee on the L2, but with a 25× gas elasticity (the target is 1/25 of capacity), which smooths fee spikes compared with L1.
  • L1 data fee — each L2 transaction must pay for its share of the batch bytes eventually posted to L1. The fee is estimated from the tx's calldata size, the current L1 basefee, and the chain's configured fee scalar. This is what makes running an L2 sustainable: users, not the chain operator, pay for data availability.
  • Priority fee / tip — like L1, users can tip to influence ordering by the sequencer.

Verification on the L2 side

Because op-geth is a full go-ethereum fork, all standard EVM semantics hold — state roots, receipts, logs, storage, precompiles, and opcodes behave identically. This is a core design goal: an L2 that behaves like Ethereum makes existing tooling and contracts usable with zero porting work.

Edge cases

  • A deposit whose execution runs out of gas is not reverted on L1 — it still consumes the prepaid L2 gas and records a failed deposit transaction in the L2 chain.
  • Estimate gas on the L2 must include the L1 data fee; an estimate that ignores it can undercount for data-heavy calls.
  • The L1Block values change only at L1 block boundaries — many L2 blocks share the same L1 origin, so "current L1 block number" is not monotonically increasing per L2 block.
  • Unsafe (P2P) blocks can temporarily diverge from safe (derived) blocks; wallet UIs that show only the unsafe head can display balances that change once derivation catches up — always prefer the safe head for anything that affects assets.

Interface

Interface
go
// op-geth is a fork of go-ethereum. The key behavioural deltas:
//
// 1. Deposit transactions (type 0x7E) are the only way state enters from L1.
// They carry a gaslimit, and the L1 cost is paid by the depositor on L1.
//
// 2. The engine API "rollup v2" drives block production. The sequencer
// calls ForkchoiceUpdate with a PayloadAttributes; op-geth returns a
// payload that the sequencer then executes.
//
// 3. Block 0 is the genesis; every subsequent L2 block's parent is the
// previous L2 block. There is no PoW, no uncle, no re-org from consensus.
//
// 4. EIP-1559 base fee on L2 is scaled by 25x (gas elasticity), and the
// chain spends at most 5% of its gas capacity on L1 data fees.
 
// L1Block contract: injected by the engine into every block.
// Provides block.number, block.timestamp, basefee, and the L1 origin.
contract L1Block {
uint256 public number; // L1 block number this L2 block is anchored to
uint256 public timestamp; // L1 timestamp of the origin
uint256 public basefee; // L1 base fee in wei
bytes32 public hash; // L1 block hash
uint256 public sequenceNumber; // increments every L2 block
}
 
STATUSinterface