P13: Layer 2 Optimistic Rollup (Simplified)
P13: Layer 2 Optimistic Rollup (Simplified)
Project Overview
| Attribute | Value |
|---|---|
| Main Language | Rust + Solidity |
| Alternative Languages | Go + Solidity |
| Difficulty | Master |
| Coolness Level | Level 5: Pure Magic (Super Cool) |
| Business Potential | Industry Disruptor (L2 Infrastructure) |
| Knowledge Area | Scaling / Layer 2 |
| Main Book | โMastering Ethereumโ by Andreas M. Antonopoulos & Gavin Wood |
Learning Objectives
By completing this project, you will:
- Master the fundamental Layer 2 tradeoff understanding how rollups achieve 10-100x throughput improvement while inheriting Layer 1 security through cryptographic proofs and economic incentives
- Implement off-chain execution with on-chain data availability learning why posting transaction data to L1 is essential for trustless fraud proofs even when computation happens elsewhere
- Build a fraud proof mechanism understanding the game theory of challenge periods, sequencer bonds, and the 1-of-N honest verifier assumption
- Design cross-layer bridges implementing secure deposit/withdrawal flows between L1 and L2 with proper finality guarantees
- Understand sequencer architecture including transaction ordering, batching strategies, state commitment, and the tradeoffs between centralization and performance
- Implement state root commitment schemes using Merkle trees to cryptographically commit to L2 state while enabling efficient proofs
- Apply economic security models designing incentive-compatible systems where honest behavior is economically rational
The Core Question Youโre Answering
โHow can we scale blockchain throughput 10-100x while still inheriting Layer 1 security guarantees?โ
This question is at the heart of blockchainโs scaling trilemma (security, decentralization, scalability). Optimistic rollups solve it by:
- Moving computation off-chain (scalability)
- Keeping data on-chain (decentralization through data availability)
- Using fraud proofs (security through economic incentives)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ THE LAYER 2 SCALING PARADIGM โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ Layer 1 (Ethereum) Layer 2 (Rollup) โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ โ โ โ
โ โ โข Final authority โโโโโโโโโโค โข Fast execution โ โ
โ โ โข Data storage โ Data โ โข Cheap transactionsโ โ
โ โ โข Fraud proofs โ + โ โข Batched commits โ โ
โ โ โข Settlement โ Roots โ โข State management โ โ
โ โ โ โ โ โ
โ โ 15-30 TPS โ โ 230-4000+ TPS โ โ
โ โ $5-50 per tx โ โ $0.01-0.10 per tx โ โ
โ โ โ โ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โฒ โ โ
โ โ Challenge Period โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ (7 days) โ
โ โ
โ Security Model: โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ "Optimistic": Assume all state roots are valid UNLESS proven false โ โ
โ โ "Fraud Proof": Anyone can challenge invalid state with on-chain tx โ โ
โ โ "1-of-N": Only ONE honest verifier needed for security guarantee โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Before building, deeply understand: Why do we need a 7-day challenge period? What happens if we make it shorter? Why canโt we just trust the sequencer? How does posting transaction data to L1 enable trustless fraud proofs? These arenโt implementation detailsโtheyโre the core security model.
Deep Theoretical Foundation
Why Layer 2?
Ethereum mainnet has fundamental constraints:
- Block gas limit: ~30 million gas per block
- Block time: 12 seconds
- Result: ~15-30 transactions per second globally
For context, Visa processes ~24,000 TPS at peak. Ethereum canโt scale by simply increasing block size (centralization risk). Layer 2 solutions process transactions off the main chain while inheriting its security.
The Rollup Taxonomy
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ LAYER 2 SCALING SOLUTIONS โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ ROLLUPS (This Project) โ โ
โ โ โข Execute off-chain, post data on-chain โ โ
โ โ โข Inherit L1 security โ โ
โ โ โ โ
โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ
โ โ โ OPTIMISTIC ROLLUPS โ โ ZK-ROLLUPS โ โ โ
โ โ โ โ โ โ โ โ
โ โ โ โข Fraud proofs โ โ โข Validity proofs โ โ โ
โ โ โ โข 7-day challenge โ โ โข Instant finality โ โ โ
โ โ โ โข Simpler tech โ โ โข Complex cryptography โ โ โ
โ โ โ โข EVM compatible โ โ โข Harder EVM compat โ โ โ
โ โ โ โ โ โ โ โ
โ โ โ Examples: โ โ Examples: โ โ โ
โ โ โ โข Arbitrum โ โ โข zkSync Era โ โ โ
โ โ โ โข Optimism โ โ โข Scroll โ โ โ
โ โ โ โข Base โ โ โข Starknet โ โ โ
โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ OTHER L2 APPROACHES โ โ
โ โ โ โ
โ โ SIDECHAINS PLASMA STATE CHANNELS โ โ
โ โ โข Separate security โข Exit games โข Two-party direct โ โ
โ โ โข Own consensus โข Data on-chain โข Instant finality โ โ
โ โ โข Not true L2 โข Complex exits โข Limited use cases โ โ
โ โ (Polygon PoS) (Deprecated) (Lightning Network) โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Optimistic Rollups: How They Work
The core idea is brilliantly simple:
- Execute transactions off-chain on the L2 sequencer
- Post transaction data to L1 for data availability
- Post state root to L1 as a commitment to the new state
- Assume valid unless someone proves otherwise (optimistic)
- Challenge window allows fraud proof submission (7 days)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ OPTIMISTIC ROLLUP ARCHITECTURE โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ LAYER 1 (ETHEREUM MAINNET) โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ ROLLUP BRIDGE CONTRACT โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค โ
โ โ โ โ
โ โ deposits[] stateRoots[] withdrawals[] โ โ
โ โ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โ โ
โ โ โ user โ โ batch 1 โ โ pending โ โ โ
โ โ โ amount โ โ root โ โ proofs โ โ โ
โ โ โ block โ โ timestampโ โ finality โ โ โ
โ โ โโโโโโโโโโโโ โ challengedโ โโโโโโโโโโโโ โ โ
โ โ โโโโโโโโโโโโ โ โ
โ โ โ โ
โ โ Functions: โ โ
โ โ โข deposit() โ Emit DepositEvent for L2 โ โ
โ โ โข submitBatch() โ Store state root, start challenge period โ โ
โ โ โข challengeBatch() โ Verify fraud proof, slash sequencer โ โ
โ โ โข finalizeWithdraw()โ Transfer after challenge period โ โ
โ โ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โฒ โ
โ โ Batch Submissions โ
โ โ (state root + calldata) โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ LAYER 2 (OFF-CHAIN) โ โ
โ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ SEQUENCER NODE โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค โ
โ โ โ โ
โ โ โโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโ โ โ
โ โ โ MEMPOOL โโโโโบโ EXECUTOR โโโโโบโ STATE TREE โ โ โ
โ โ โ โ โ โ โ โ โ โ
โ โ โ pending txs โ โ apply txs โ โ Merkle trie โ โ โ
โ โ โ ordering โ โ compute new โ โ state root โ โ โ
โ โ โ validation โ โ state โ โ proofs โ โ โ
โ โ โโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโ โ โ
โ โ โฒ โ โ โ
โ โ โ โผ โ โ
โ โ โโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโ โ โ
โ โ โ L1 WATCHER โ โ BATCHER โ โ RPC API โ โ โ
โ โ โ โ โ โ โ โ โ โ
โ โ โ deposits โ โ batch txs โ โ eth_* calls โ โ โ
โ โ โ L1 finality โ โ compress โ โ l2 queries โ โ โ
โ โ โ reorgs โ โ submit to L1 โ โ user access โ โ โ
โ โ โโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโ โ โ
โ โ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ VERIFIER / CHALLENGER โ โ
โ โ โ โ
โ โ โข Independently executes all L2 transactions โ โ
โ โ โข Compares computed state root to posted state root โ โ
โ โ โข If mismatch detected โ submits fraud proof to L1 โ โ
โ โ โข Receives reward if fraud proven, sequencer slashed โ โ
โ โ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Data Availability: The Critical Insight
Why must transaction data be posted to L1, even though execution happens on L2?
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ THE DATA AVAILABILITY PROBLEM โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ SCENARIO: Malicious sequencer posts INVALID state root โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ โ
โ โ State Before: Alice=100 ETH, Bob=50 ETH โ โ
โ โ Transaction: Alice sends 10 ETH to Bob โ โ
โ โ Correct State: Alice=90 ETH, Bob=60 ETH, Root=0xAAA โ โ
โ โ โ โ
โ โ FRAUD: Sequencer posts Root=0xBBB where Bob=1000 ETH โ โ
โ โ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ CASE 1: Transaction data ON L1 (Rollup) โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Challenger can: โ โ
โ โ 1. Read transaction data from L1 calldata โ โ
โ โ 2. Re-execute: "Alice sends 10 to Bob" โ โ
โ โ 3. Compute correct root: 0xAAA โ โ
โ โ 4. Prove 0xAAA != 0xBBB โ FRAUD PROVEN โ โ
โ โ โ โ
โ โ Result: Sequencer SLASHED, state REVERTED โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ CASE 2: Transaction data OFF L1 (Validium/External DA) โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ If sequencer withholds data: โ โ
โ โ 1. Challenger cannot access transaction data โ โ
โ โ 2. Cannot re-execute transactions โ โ
โ โ 3. Cannot prove fraud even if fraud exists โ โ
โ โ โ โ
โ โ Result: FRAUD SUCCEEDS, users lose funds โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ CONCLUSION: Data availability on L1 is NON-NEGOTIABLE for security โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Fraud Proof Mechanics
Fraud proofs are the security backbone of optimistic rollups. There are two main approaches:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ FRAUD PROOF APPROACHES โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ NON-INTERACTIVE (Single-Round) โ โ INTERACTIVE (Multi-Round) โ โ
โ โ (Optimism style) โ โ (Arbitrum style) โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค โ
โ โ โ โ โ โ
โ โ Re-execute entire batch โ โ Binary search for bad step โ โ
โ โ on L1 in ONE transaction โ โ through challenge game โ โ
โ โ โ โ โ โ
โ โ Pros: โ โ Pros: โ โ
โ โ โข Simple implementation โ โ โข Gas efficient โ โ
โ โ โข Fast dispute resolution โ โ โข Can handle large batches โ โ
โ โ โ โ โข More flexible โ โ
โ โ Cons: โ โ โ โ
โ โ โข Gas limited by L1 block โ โ Cons: โ โ
โ โ โข Limits batch size โ โ โข Complex implementation โ โ
โ โ โข More expensive โ โ โข Longer dispute time โ โ
โ โ โ โ โข Multiple rounds needed โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ This project: We implement SIMPLIFIED non-interactive fraud proofs โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Interactive Fraud Proof (Arbitrum-style) Flow:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ INTERACTIVE FRAUD PROOF BINARY SEARCH โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ Batch: 1000 transactions โ
โ Sequencer claims: Stateโ โ Stateโโโโ โ
โ Challenger says: Stateโโโโ is wrong! โ
โ โ
โ Round 1: Where did state diverge? โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ Sequencer: Stateโ
โโ = 0xABC โ
โ Challenger: I agree Stateโ
โโ = 0xABC โ โ
โ โ Fraud is in txs 501-1000 โ
โ โ
โ Round 2: โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ Sequencer: Stateโโ
โ = 0xDEF โ
โ Challenger: I disagree! Stateโโ
โ = 0xGHI โ โ
โ โ Fraud is in txs 501-750 โ
โ โ
โ ... continue binary search ... โ
โ โ
โ Final Round: Single instruction dispute โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ Isolated: tx 623 execution โ
โ Re-execute ON L1: just this one instruction โ
โ Compute correct state โ
โ Compare with sequencer's claim โ
โ โ FRAUD PROVEN at tx 623 โ
โ โ
โ Gas cost: O(log n) instead of O(n) โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
The Challenge Period: Game Theory
Why 7 days? This is a carefully chosen duration:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ CHALLENGE PERIOD GAME THEORY โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ 7 DAYS BALANCES: โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ TOO SHORT (1 hour) โ โ TOO LONG (30 days) โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโค โโโโโโโโโโโโโโโโโโโโโโโโโโโค โ
โ โ โ โ โ โ
โ โ โข Verifiers may miss โ โ โข Terrible UX โ โ
โ โ fraud during outage โ โ โข Capital locked too โ โ
โ โ โข Network attacks can โ โ long โ โ
โ โ delay challenges โ โ โข Users won't use L2 โ โ
โ โ โข Insufficient review โ โ โข Economic inefficiency โ โ
โ โ time โ โ โ โ
โ โ โ โ โ โ
โ โ RISK: Fraud succeeds โ โ RISK: User abandonment โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ 7 DAYS PROVIDES: โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โข Enough time for verifiers to detect fraud (even with downtime) โ โ
โ โ โข Weekend coverage (fraud detected regardless of day submitted) โ โ
โ โ โข Time to coordinate response to attacks โ โ
โ โ โข Acceptable UX tradeoff for security โ โ
โ โ โข Matches typical audit/review cycles โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ FAST WITHDRAWALS (Workaround): โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Third-party liquidity providers offer instant withdrawals โ โ
โ โ โ โ
โ โ User: "I want my 10 ETH now, not in 7 days" โ โ
โ โ LP: "I'll give you 9.95 ETH now, claim your 10 ETH in 7 days" โ โ
โ โ โ โ
โ โ LP takes risk of fraud (unlikely) for 0.5% fee โ โ
โ โ User gets instant liquidity โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
The 1-of-N Security Model
Optimistic rollups have a powerful security property:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 1-OF-N HONEST VERIFIER โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ Traditional systems: Need majority honest (N/2 of N) โ
โ Optimistic rollups: Need ONLY ONE honest verifier (1 of N) โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ โ
โ โ Verifiers: โณ โณ โณ โณ โณ โณ โณ โณ โณ โ โณ โณ โณ โณ โณ โ โ
โ โ โฒ โ โ
โ โ โ โ โ
โ โ ONE honest verifier โ โ
โ โ is sufficient! โ โ
โ โ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ WHY THIS WORKS: โ
โ 1. Fraud proof is publicly verifiable on L1 โ
โ 2. Anyone can submit it (permissionless) โ
โ 3. If fraud exists, ONE honest party can prove it โ
โ 4. L1 contract executes regardless of who submitted โ
โ โ
โ WHO RUNS VERIFIERS? โ
โ โข Rollup team (self-preservation) โ
โ โข Major L2 users (protecting their funds) โ
โ โข MEV searchers (profitable fraud detection) โ
โ โข Altruistic community members โ
โ โข Cross-chain bridges (protecting locked assets) โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Concepts You Must Understand First
Stop and research these before coding:
1. Data Availability vs. Execution
- Whatโs the difference between โwhere data is storedโ and โwhere computation happensโ?
- Why must transaction data be posted to L1, even though execution is on L2?
- What is the โdata availability problemโ and how do rollups solve it?
DATA AVAILABILITY LAYER EXECUTION LAYER
โโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ โ โ
โ WHERE: Ethereum L1 โ โ WHERE: L2 Sequencer โ
โ โ โ โ
โ WHAT: Raw tx data โ โ WHAT: State machine โ
โ (calldata) โ โ (EVM/custom) โ
โ โ โ โ
โ WHY: Anyone can โ โ WHY: Fast, cheap โ
โ re-execute โ โ computation โ
โ and verify โ โ โ
โ โ โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ
โโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโ
โ
BOTH NEEDED
FOR SECURITY
Book Reference: โMastering Ethereumโ Ch. 13 - Antonopoulos & Wood Web Reference: Optimistic Rollups on ethereum.org
2. State Roots and Merkle Proofs
- How does a state root cryptographically commit to all account balances?
- How can you prove an accountโs balance without revealing all accounts?
- Why is the state root the โsource of truthโ for L2 state?
STATE ROOT
โโโโโโโโโโโ
โ 0xABC โ
โโโโโโฌโโโโโ
โ
โโโโโโโโโโโโโโโดโโโโโโโโโโโโโโ
โ โ
โโโโโโดโโโโโ โโโโโโดโโโโโ
โ 0xDEF โ โ 0xGHI โ
โโโโโโฌโโโโโ โโโโโโฌโโโโโ
โ โ
โโโโโโโโดโโโโโโโ โโโโโโโโดโโโโโโโ
โ โ โ โ
โโโโโดโโโโ โโโโโดโโโโ โโโโโดโโโโ โโโโโดโโโโ
โAlice: โ โ Bob: โ โCarol: โ โ Dave: โ
โ100 ETHโ โ50 ETH โ โ75 ETH โ โ25 ETH โ
โโโโโโโโโ โโโโโโโโโ โโโโโโโโโ โโโโโโโโโ
MERKLE PROOF for Bob's balance (50 ETH):
[0xDEF, 0xGHI, Hash(Alice)] โ verifies against root 0xABC
Book Reference: โMastering Bitcoinโ Ch. 9 (Merkle Trees) - Antonopoulos Project Reference: You should have built Project 3 (Merkle Tree Library)
3. Fraud Proofs vs. Validity Proofs
- Whatโs the difference between โoptimisticโ (fraud proofs) and โzero-knowledgeโ (validity proofs)?
- Why is it called โoptimisticโ? (Hint: assume valid unless proven otherwise)
- How does a fraud proof work without re-executing all transactions?
OPTIMISTIC ROLLUP ZK-ROLLUP
โโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ โ โ
โ 1. Execute on L2 โ โ 1. Execute on L2 โ
โ 2. Post state root โ โ 2. Generate ZK proof โ
โ 3. Wait 7 days โ โ 3. Post root + proof โ
โ 4. If no challenge โ โ 4. L1 verifies proof โ
โ โ finalized โ โ โ instant finality โ
โ โ โ โ
โ Security: Game theory โ โ Security: Math โ
โ Assumption: 1 honest โ โ Assumption: None โ
โ โ โ โ
โ Cost: Cheaper compute โ โ Cost: Expensive proof โ
โ Time: 7 day withdraw โ โ Time: Instant withdrawโ
โ โ โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโ
Web Reference: Arbitrum vs Optimism fraud proofs
4. Challenge Period Game Theory
- Why 7 days? Why not 7 minutes or 7 months?
- What economic incentives keep sequencers honest?
- What happens if no one monitors for fraud?
- How much should the sequencer bond be?
SEQUENCER INCENTIVES:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ
โ Honest Behavior: โ
โ โข Earn transaction fees: $100,000/day โ
โ โข Keep reputation โ
โ โข Keep bond: 1000 ETH โ
โ โข Sustainable business โ
โ โ
โ Fraudulent Behavior: โ
โ โข Potential theft: $1,000,000 โ
โ โข MINUS probability of detection: ~99.99% (1 honest verifier) โ
โ โข MINUS bond slashing: 1000 ETH ($3,000,000) โ
โ โข MINUS legal consequences โ
โ โข MINUS reputation destruction โ
โ โ
โ Expected Value of Fraud = $1M ร 0.01% - $3M = -$2,999,900 โ
โ โ FRAUD IS ECONOMICALLY IRRATIONAL โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Book Reference: โDesigning Data-Intensive Applicationsโ Ch. 9 (Consistency) - Kleppmann
5. Sequencer Centralization Tradeoff
- Why do most rollups use a single sequencer?
- What are the risks of a centralized sequencer?
- How can you make sequencer selection decentralized?
CENTRALIZATION SPECTRUM:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ
โ FULLY CENTRALIZED HYBRID FULLY DECENTRALIZED โ
โ (Single Sequencer) (Rotating) (Shared Sequencing) โ
โ โ โ โ โ
โ โผ โผ โผ โ
โ โโโโโโโโโโโโโ โโโโโโโโโโโโโ โโโโโโโโโโโโโ โ
โ โโข Fast โ โโข Moderate โ โโข Slow โ โ
โ โโข Cheap โ โโข Resilientโ โโข Expensiveโ โ
โ โโข Censorable โโข Partiallyโ โโข Censorshipโ โ
โ โโข Single โ โ censorship โ resistant โ โ
โ โ point of โ โ resistantโ โโข Complex โ โ
โ โ failure โ โ โ โ โ โ
โ โโโโโโโโโโโโโ โโโโโโโโโโโโโ โโโโโโโโโโโโโ โ
โ โ
โ Current state: Most L2s use centralized sequencer with "training wheels" โ
โ Roadmap: All major L2s plan decentralized sequencing โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Web Reference: L2Beat Risks Dashboard
6. Cross-Layer Messaging (L1 <-> L2)
- How do deposits from L1 reach L2?
- How do withdrawals from L2 reach L1?
- Why can deposits be fast but withdrawals must wait 7 days?
DEPOSIT FLOW (L1 โ L2): FAST
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ
โ User L1 Bridge Sequencer L2 โ
โ โ โ โ โ โ
โ โโโโ deposit() โโโโโโโโบโ โ โ โ
โ โ โโโโ DepositEvent โโโโโบโ โ โ
โ โ โ โโโโ credit() โโโโโบโ โ
โ โ โ โ โ โ
โ โ โ (10 L1 blocks) โ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโผโโโโ L2 funds โโโโโ โ
โ โ
โ Time: ~2-12 minutes (wait for L1 finality) โ
โ Why fast: L1 deposit is final, L2 just mirrors it โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
WITHDRAWAL FLOW (L2 โ L1): SLOW
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ
โ User L2 Bridge Sequencer L1 Bridge Challenger โ
โ โ โ โ โ โ โ
โ โโwithdraw()โโบโ โ โ โ โ
โ โ โโโโ burn โโโโโบโ โ โ โ
โ โ โ โโโโ batch โโโโโโบโ โ โ
โ โ โ โ โ โ โ
โ โ โ โ 7 DAYS CHALLENGE PERIOD โ โ
โ โ โ โ โโโโ verify โโโโโ โ
โ โ โ โ โ (no challenge) โ โ
โ โ โ โ โ โ โ
โ โโfinalize()โโผโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโบโ โ โ
โ โโโโโโโโโโโโโโผโโโโโ L1 funds โโโโโโโโโโโโโโโโ โ โ
โ โ
โ Time: 7 days + finality time โ
โ Why slow: Must wait to ensure no fraud in the batch โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Web Reference: Optimism Bridge Architecture
7. EIP-4844 and Blob Transactions
- What are โblobsโ and why do they reduce rollup costs?
- How did EIP-4844 (March 2024) change rollup economics?
- Whatโs the difference between calldata and blob data?
BEFORE EIP-4844:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Transaction data stored in CALLDATA โ
โ โข 16 gas per non-zero byte โ
โ โข Data stored FOREVER on chain โ
โ โข Cost: $0.05-0.50 per L2 tx โ
โ โข Expensive! Data storage is biggest L2 cost โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
AFTER EIP-4844:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Transaction data stored in BLOBS โ
โ โข ~1 gas per byte (16x cheaper!) โ
โ โข Data pruned after ~18 days โ
โ โข Cost: $0.001-0.01 per L2 tx โ
โ โข 90% cost reduction for rollups โ
โ โ
โ Why 18 days is enough: โ
โ โข Challenge period is 7 days โ
โ โข Fraud proofs only need data during challenge โ
โ โข After finalization, state root is canonical โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Web Reference: EIP-4844: Shard Blob Transactions
Questions to Guide Your Design
Before implementing, think through these:
1. L1 Contract Architecture
- What functions does the L1 bridge contract need? (deposit, submitBatch, challengeBatch, finalizeWithdrawal)
- How do you store pending state roots and their challenge status?
- What data must be included in each batch submission?
- How do you prevent replay attacks on deposits/withdrawals?
// Minimal L1 Contract Interface
interface IRollupBridge {
// User deposits ETH/tokens to L2
function deposit() external payable;
// Sequencer submits batch with state root
function submitBatch(
bytes32 stateRoot,
bytes calldata txData,
bytes32 prevStateRoot
) external;
// Anyone can challenge invalid state
function challengeBatch(
uint256 batchId,
bytes calldata fraudProof
) external;
// User finalizes withdrawal after challenge period
function finalizeWithdrawal(
bytes32[] calldata merkleProof,
uint256 amount
) external;
}
2. L2 Sequencer Design
- How does the sequencer maintain L2 state (accounts, balances)?
- How do you decide when to batch transactions? (time-based, size-based, or both?)
- How do you compress transaction data before posting to L1?
- What happens if the sequencer crashes mid-batch?
3. State Transition Function
- How do you execute L2 transactions? (EVM? Custom VM?)
- How do you compute the new state root after a batch?
- What validation rules must all transactions pass?
- How do you handle transaction failures on L2?
4. Fraud Proof Construction
- What data is needed to prove a state transition is invalid?
- How can you prove fraud in a single L1 transaction (gas limits)?
- Interactive vs. non-interactive fraud proofsโwhich to implement?
- How do you slash the malicious sequencerโs bond?
5. Withdrawal Security
- How do you prove a withdrawal occurred on L2?
- What prevents users from withdrawing more than they have?
- Why must withdrawals wait for the challenge period?
- How do you handle withdrawals during a fraud proof dispute?
Thinking Exercise
Trace a Transaction Through the Rollup
Before coding, trace this scenario on paper:
1. Alice has 10 ETH on L1
2. Alice deposits 5 ETH to L2
3. On L2, Alice sends 1 ETH to Bob
4. Bob sends 0.5 ETH to Charlie
5. Sequencer batches these L2 transactions
6. Sequencer submits batch to L1
7. Eve (malicious) tries to challenge with false fraud proof
8. Alice initiates withdrawal of 3 ETH
9. After 7 days, Alice finalizes withdrawal
Questions while tracing:
- Draw the state roots at each step (what changes?)
- What data is posted to L1 in step 6? (full transaction data)
- How does Eveโs challenge fail in step 7? (her recomputed state root matches)
- Why must Alice wait in step 9? (challenge period for batch containing her withdrawal)
- What are the gas costs at each L1 interaction?
State Root Progression:
Step 1: L1 State
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ L1: Alice = 10 ETH โ
โ L2: [empty] โ
โ L2 State Root: 0x0000 (genesis) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Step 2: After Deposit
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ L1: Alice = 5 ETH (5 locked in bridge) โ
โ L2: Alice = 5 ETH โ
โ L2 State Root: 0xAAA1 โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Step 3-4: After L2 Transfers
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ L1: Alice = 5 ETH (5 locked in bridge) โ
โ L2: Alice = 4 ETH, Bob = 0.5 ETH, Charlie = 0.5 ETH โ
โ L2 State Root: 0xAAA3 โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Step 5-6: Batch Submitted
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ L1 Bridge Contract: โ
โ batches[1] = { โ
โ stateRoot: 0xAAA3, โ
โ timestamp: now, โ
โ challenged: false, โ
โ txData: [compressed L2 txs] โ
โ } โ
โ Challenge period: blocks 100 - 604900 โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Step 8: Withdrawal Initiated
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ L2: Alice = 1 ETH, Bob = 0.5 ETH, Charlie = 0.5 ETH โ
โ + pending_withdrawal: {Alice, 3 ETH, batchId: 2} โ
โ L2 State Root: 0xAAA4 โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Step 9: After 7 Days
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ L1: Alice = 8 ETH (5 original + 3 withdrawn) โ
โ L1 Bridge: 2 ETH remaining locked โ
โ L2: Alice = 1 ETH, Bob = 0.5 ETH, Charlie = 0.5 ETH โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Design a Fraud Proof
Given:
- L2 state root before:
0xAAA... - L2 transaction: โAlice sends 100 ETH to Bobโ (but Alice only has 50 ETH)
- Sequencer posted state root after:
0xBBB...(claiming transaction succeeded)
How do you prove fraud on L1?
- What data do you need from L2? (Aliceโs balance proof, transaction, state root)
- How do you re-execute the transaction on L1? (in a single tx? off-chain then verify?)
- What should the correct state root be? (transaction should revert, state unchanged)
- How do you convince L1 the sequencer lied? (show mismatch)
FRAUD PROOF STRUCTURE:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ
โ FraudProof { โ
โ batchId: 5, โ
โ preStateRoot: 0xAAA, // Agreed starting state โ
โ postStateRoot: 0xBBB, // Claimed by sequencer (WRONG) โ
โ transaction: { โ
โ from: Alice, โ
โ to: Bob, โ
โ amount: 100 ETH โ
โ }, โ
โ accountProof: [ // Merkle proof of Alice's balance โ
โ 0x..., 0x..., 0x... โ
โ ], โ
โ aliceBalance: 50 ETH // Alice's actual balance (not 100) โ
โ } โ
โ โ
โ VERIFICATION ON L1: โ
โ 1. Verify accountProof against preStateRoot โ
โ 2. Check: aliceBalance < transaction.amount (50 < 100) โ
โ 3. Therefore: transaction should FAIL โ
โ 4. postStateRoot should equal preStateRoot โ
โ 5. But sequencer claimed 0xBBB != 0xAAA โ
โ 6. FRAUD PROVEN! โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
The Interview Questions Theyโll Ask
Prepare to answer these:
Fundamental Understanding
- โExplain optimistic rollups to me like Iโm five.โ
- Expected: โRollups do math homework off-chain, show their work on-chain, and if someone catches a mistake within 7 days, they get rewarded and the wrong answer is erased.โ
- โWhy is the challenge period 7 days? Isnโt that too long for users waiting to withdraw?โ
- Expected: Discuss honest challenger liveness assumptions, coordination time, and tradeoff with security. Mention fast withdrawal services as workaround.
- โWhatโs the difference between Arbitrum and Optimism?โ
- Expected: Multi-round vs. single-round fraud proofs, EVM equivalence differences, governance models. Arbitrum uses interactive bisection; Optimism re-executes entire batch.
- โWhat happens if no one runs a verifier to check for fraud?โ
- Expected: Security depends on at least one honest verifier (1-of-N trust model). Incentives via MEV and altruism. Rollup teams always run verifiers for self-preservation.
- โHow do optimistic rollups compare to ZK-rollups?โ
- Expected: Optimistic = fraud proofs, 7-day withdrawals, simpler tech, EVM compatible. ZK = validity proofs, instant withdrawals, complex cryptography, harder EVM compat.
- โCan you explain the data availability problem?โ
- Expected: If transaction data isnโt public, challengers canโt compute fraud proofs. Thatโs why calldata/blobs must be on L1. Without DA, sequencer could commit fraud and withhold evidence.
- โWhatโs the worst-case scenario for an optimistic rollup?โ
- Expected: Sequencer is malicious AND no honest challenger exists AND users trust the bad state root. Also: sequencer censorship, prolonged downtime.
- โWhy not just use a sidechain instead of a rollup?โ
- Expected: Sidechains have separate security (can be attacked independently). Rollups inherit L1 security via fraud proofs. Sidechains require trusting their validators; rollups only need 1 honest verifier.
Technical Deep Dives
- โHow does transaction compression work in rollups?โ
- Expected: Remove signatures (replace with aggregate), use shorter addresses, encode amounts efficiently, batch headers. Can achieve 10-100x compression.
- โWhat is MEV in the context of L2?โ
- Expected: Sequencer can reorder transactions for profit. Discuss sequencer auction, fair ordering, encryption schemes (threshold encryption, time-lock).
- โHow do you handle L1 reorganizations on L2?โ
- Expected: L2 must wait for L1 finality before crediting deposits. If L1 reorgs, L2 must also reorg. Discuss safe confirmation depths.
- โWhat is the escape hatch mechanism?โ
- Expected: If sequencer censors, users can force-include transactions via L1 or exit directly to L1. Essential for censorship resistance.
- โHow do smart contracts work across L1 and L2?โ
- Expected: Same address can have different contracts on L1/L2. Cross-layer calls use message passing with async callbacks. Not atomic across layers.
- โWhat happens during a dispute/challenge?โ
- Expected: Challenger posts bond, dispute enters resolution phase, losing party loses bond. During dispute, affected batch is pending (canโt finalize withdrawals).
- โHow would you design a decentralized sequencer?โ
- Expected: Options include rotating sequencer (based on stake/auction), shared sequencing (multiple rollups), PBS-style separation. Discuss tradeoffs of each.
System Design
- โDesign a rollup that handles 10,000 TPS.โ
- Expected: Discuss parallel execution, state sharding, batch sizing, DA layer choice (blobs vs. external DA), hardware requirements.
- โHow would you implement atomic cross-rollup transactions?โ
- Expected: Shared sequencer, fast messaging with bridges, escrow patterns. Discuss latency and security tradeoffs.
- โWhat monitoring and alerting would you set up for a production rollup?โ
- Expected: State root verification, batch submission monitoring, challenge detection, sequencer health, gas price tracking, bridge balance monitoring.
- โHow do rollups handle contract upgrades?โ
- Expected: Proxy patterns, timelock delays, governance votes. Security considerations around upgrade authority.
- โExplain the economics of running a rollup.โ
- Expected: Revenue (tx fees, MEV), costs (L1 data posting, compute), margins. How EIP-4844 changed the economics dramatically.
Real World Outcome
When your simplified optimistic rollup is complete, youโll have a working Layer 2 system that demonstrates Ethereum scaling in action. Hereโs exactly what youโll see:
# Terminal 1 - Start L1 (local Ethereum node)
$ anvil --chain-id 1
[Anvil] Listening on http://127.0.0.1:8545
[Anvil] Block time: 12 seconds
# Terminal 2 - Deploy L1 Bridge Contract
$ forge create RollupBridge --rpc-url http://127.0.0.1:8545
Deployed RollupBridge at: 0x5FbDB2315678afecb367f032d93F642f64180aa3
# Terminal 3 - Start L2 Sequencer
$ ./rollup-sequencer --l1-rpc http://127.0.0.1:8545 --bridge 0x5FbDB...
[Sequencer] Connected to L1 at block 100
[Sequencer] L2 chain initialized with genesis state
[Sequencer] Listening for L2 transactions on http://127.0.0.1:8546
[Sequencer] Batch interval: 10 L2 blocks or 60 seconds
[Sequencer] Challenge period: 7 days (604800 blocks on L1)
# Terminal 4 - User deposits ETH from L1 to L2
$ cast send 0x5FbDB... "deposit()" --value 1ether --rpc-url http://127.0.0.1:8545
Transaction: 0xabc123...
[L1] DepositEvent emitted: user=0xf39Fd..., amount=1000000000000000000
# Watch L2 Sequencer process the deposit
[Sequencer] L1 deposit detected at block 101
[Sequencer] Crediting 1 ETH to 0xf39Fd... on L2
[L2 State] 0xf39Fd... balance: 0 -> 1000000000000000000
# Terminal 5 - Send fast L2 transactions
$ cast send 0xRecipient --value 0.1ether --rpc-url http://127.0.0.1:8546
[L2] Transaction confirmed in 0.2 seconds
[L2] Gas cost: $0.0001 (vs $5 on L1)
$ cast send 0xRecipient2 --value 0.2ether --rpc-url http://127.0.0.1:8546
[L2] Transaction confirmed in 0.2 seconds
# Watch sequencer batch and submit to L1
[Sequencer] Batch ready: 47 transactions
[Sequencer] Computing new state root: 0x7f3a2b...
[Sequencer] Compressing transaction data (47 txs -> 3.2 KB)
[Sequencer] Submitting batch to L1...
[L1] Transaction cost: 0.002 ETH (split across 47 users = $0.15 each)
[L1] StateRootSubmitted: batchId=1, stateRoot=0x7f3a2b..., blockNumber=102
[Sequencer] Challenge period starts: block 102 -> block 604902
# Terminal 6 - Honest challenger verifies (all is good)
$ ./rollup-verifier --l1-rpc http://127.0.0.1:8545 --l2-rpc http://127.0.0.1:8546
[Verifier] Checking batch 1...
[Verifier] Re-executing 47 transactions...
[Verifier] Computed state root: 0x7f3a2b...
[Verifier] State root matches! No fraud detected.
# Simulate malicious sequencer (optional test)
[Sequencer] TESTING: Submitting INVALID state root 0xBADBEEF...
[L1] StateRootSubmitted: batchId=2, stateRoot=0xBADBEEF..., blockNumber=103
[Verifier] Checking batch 2...
[Verifier] Re-executing 23 transactions...
[Verifier] Computed state root: 0x9a4c1d... (expected)
[Verifier] Posted state root: 0xBADBEEF... (actual)
[Verifier] FRAUD DETECTED! Submitting fraud proof...
[L1] Challenge submitted by 0xChallenger...
[L1] Fraud proof verified
[L1] Batch 2 REVERTED
[L1] Sequencer slashed: 100 ETH burned
[L1] Challenger rewarded: 10 ETH
# Terminal 7 - User withdraws from L2 to L1 (7-day delay)
$ cast send --rpc-url http://127.0.0.1:8546 $BRIDGE "initiateWithdrawal(uint256)" 0.5ether
[L2] Withdrawal initiated: 0.5 ETH
[L2] Withdrawal included in batch 3
[Sequencer] Batch 3 submitted to L1 at block 110
[L1] Challenge period: blocks 110-604910 (7 days)
# Wait 7 days... (in test mode, fast-forward)
$ cast rpc anvil_mine 604800
# Finalize withdrawal
$ cast send $BRIDGE "finalizeWithdrawal(bytes32)" $WITHDRAWAL_PROOF --rpc-url http://127.0.0.1:8545
[L1] Withdrawal proof verified
[L1] 0.5 ETH transferred to user
[L1] User balance: 10 ETH -> 10.5 ETH
# Performance comparison dashboard
$ ./rollup-stats
+================================================+
| L2 Rollup Statistics |
+================================================+
| Total L2 transactions: 1,247 |
| Total L1 batches: 27 |
| Average batch size: 46 txs |
| L2 block time: 0.2s |
| L1 block time: 12s |
| |
| Cost Savings: |
| L2 tx cost: $0.0001 |
| L1 tx cost: $5.00 |
| Savings per tx: 99.998% |
| |
| Throughput: |
| L2 TPS: ~230 tps |
| L1 TPS (if no rollup): ~15 tps |
| Scalability factor: 15x |
| |
| Security: |
| Fraud proofs submitted: 1 |
| Invalid batches reverted: 1 |
| Uptime: 99.97% |
+================================================+
Youโre witnessing Ethereum scaling through computation off-chain, security on-chain. Your L2 processes thousands of transactions for pennies, while maintaining Ethereumโs security guarantees through fraud proofs.
Complete Project Specification
Functional Requirements
- L1 Bridge Contract
- Accept ETH deposits and emit events for L2 pickup
- Store sequencer-submitted state roots with timestamps
- Process fraud proof challenges and slash malicious sequencers
- Handle withdrawal finalization after challenge period
- L2 Sequencer
- Maintain L2 state (balances, nonces) in Merkle tree
- Execute L2 transactions and update state
- Watch L1 for deposit events and credit L2 accounts
- Batch transactions and submit to L1
- State Management
- Merkle tree for efficient state root computation
- Proof generation for individual account states
- State rollback capability for fraud proof testing
- Fraud Proof System
- Verifier that re-executes all batches
- Fraud proof generation when mismatch detected
- L1 verification of fraud proofs
- Withdrawal Bridge
- L2 withdrawal initiation with state commitment
- Merkle proof generation for withdrawals
- L1 finalization after challenge period
Command-Line Interface
# Initialize and deploy
$ rollup init --l1-rpc http://localhost:8545
$ rollup deploy-bridge
# Start sequencer
$ rollup sequencer start --port 8546
# Start verifier
$ rollup verifier start
# User operations
$ rollup deposit --amount 1.0 --l1-key $PRIVATE_KEY
$ rollup transfer --to 0x... --amount 0.5 --l2-key $PRIVATE_KEY
$ rollup withdraw --amount 0.3 --l2-key $PRIVATE_KEY
$ rollup finalize-withdrawal --proof $PROOF
# Admin operations
$ rollup status
$ rollup batches list
$ rollup verify-batch --id 5
Solution Architecture
Module Structure
rollup/
+-- Cargo.toml
+-- contracts/ # Solidity L1 contracts
| +-- RollupBridge.sol
| +-- FraudProver.sol
| +-- test/
| +-- RollupBridge.t.sol
+-- src/
| +-- main.rs # CLI entry point
| +-- lib.rs # Library exports
| +-- sequencer/
| | +-- mod.rs # Sequencer coordinator
| | +-- executor.rs # Transaction execution
| | +-- batcher.rs # Batch creation and submission
| | +-- l1_watcher.rs # L1 event monitoring
| +-- state/
| | +-- mod.rs # State management
| | +-- merkle.rs # Merkle tree implementation
| | +-- account.rs # Account data structures
| | +-- storage.rs # Persistent storage (RocksDB)
| +-- verifier/
| | +-- mod.rs # Verifier coordinator
| | +-- executor.rs # Re-execution engine
| | +-- fraud_proof.rs # Fraud proof generation
| +-- bridge/
| | +-- mod.rs # Bridge coordination
| | +-- deposit.rs # Deposit handling
| | +-- withdrawal.rs # Withdrawal handling
| +-- types/
| | +-- mod.rs # Common types
| | +-- transaction.rs # L2 transaction
| | +-- batch.rs # Batch structure
| | +-- proof.rs # Proof structures
| +-- rpc/
| +-- mod.rs # JSON-RPC server
| +-- handlers.rs # RPC method handlers
+-- tests/
+-- integration/
+-- deposit_flow.rs
+-- withdrawal_flow.rs
+-- fraud_proof.rs
Core Data Structures
use primitive_types::{H256, U256};
use std::collections::HashMap;
/// L2 Account state
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Account {
pub balance: U256,
pub nonce: u64,
}
/// L2 Transaction
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Transaction {
pub from: Address,
pub to: Address,
pub value: U256,
pub nonce: u64,
pub signature: Signature,
}
/// L2 State (Merkle tree of accounts)
pub struct L2State {
accounts: HashMap<Address, Account>,
merkle_tree: MerkleTree,
}
impl L2State {
pub fn new() -> Self {
Self {
accounts: HashMap::new(),
merkle_tree: MerkleTree::new(),
}
}
pub fn apply_transaction(&mut self, tx: &Transaction) -> Result<(), Error> {
// 1. Verify signature
let recovered = tx.recover_signer()?;
if recovered != tx.from {
return Err(Error::InvalidSignature);
}
// 2. Check nonce
let account = self.accounts.entry(tx.from).or_default();
if account.nonce != tx.nonce {
return Err(Error::InvalidNonce);
}
// 3. Check balance
if account.balance < tx.value {
return Err(Error::InsufficientBalance);
}
// 4. Execute transfer
account.balance -= tx.value;
account.nonce += 1;
let recipient = self.accounts.entry(tx.to).or_default();
recipient.balance += tx.value;
// 5. Update Merkle tree
self.merkle_tree.update(tx.from, account.hash());
self.merkle_tree.update(tx.to, recipient.hash());
Ok(())
}
pub fn state_root(&self) -> H256 {
self.merkle_tree.root()
}
pub fn get_proof(&self, address: Address) -> MerkleProof {
self.merkle_tree.get_proof(address)
}
}
/// Batch submitted to L1
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Batch {
pub id: u64,
pub prev_state_root: H256,
pub post_state_root: H256,
pub transactions: Vec<Transaction>,
pub timestamp: u64,
}
/// State commitment stored on L1
#[derive(Clone, Debug)]
pub struct StateCommitment {
pub state_root: H256,
pub timestamp: u64,
pub challenged: bool,
pub finalized: bool,
}
/// Fraud proof structure
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct FraudProof {
pub batch_id: u64,
pub pre_state_root: H256,
pub post_state_root: H256, // What sequencer claimed
pub correct_state_root: H256, // What it should be
pub transaction_index: usize, // Which tx was invalid
pub account_proof: MerkleProof, // Proof of account state
pub transaction: Transaction, // The invalid transaction
}
Key Algorithms
Transaction Execution
impl Sequencer {
pub async fn execute_transaction(&mut self, tx: Transaction) -> Result<TxReceipt> {
// 1. Validate transaction
self.validate_transaction(&tx)?;
// 2. Apply to state
self.state.apply_transaction(&tx)?;
// 3. Add to pending batch
self.pending_batch.push(tx.clone());
// 4. Check if batch should be submitted
if self.should_submit_batch() {
self.submit_batch().await?;
}
Ok(TxReceipt {
tx_hash: tx.hash(),
status: true,
state_root: self.state.state_root(),
})
}
fn should_submit_batch(&self) -> bool {
self.pending_batch.len() >= MAX_BATCH_SIZE
|| self.time_since_last_batch() > MAX_BATCH_TIME
}
async fn submit_batch(&mut self) -> Result<()> {
let batch = Batch {
id: self.next_batch_id,
prev_state_root: self.last_submitted_root,
post_state_root: self.state.state_root(),
transactions: std::mem::take(&mut self.pending_batch),
timestamp: current_timestamp(),
};
// Compress transaction data
let compressed = self.compress_transactions(&batch.transactions);
// Submit to L1
let tx = self.l1_contract
.submit_batch(batch.post_state_root, compressed)
.send()
.await?;
self.last_submitted_root = batch.post_state_root;
self.next_batch_id += 1;
Ok(())
}
}
Fraud Proof Verification
impl Verifier {
pub async fn verify_batch(&self, batch_id: u64) -> Result<bool> {
// 1. Get batch data from L1
let batch_data = self.l1_contract.get_batch(batch_id).await?;
let transactions = self.decompress_transactions(&batch_data.tx_data);
// 2. Get pre-state root
let pre_state = if batch_id == 0 {
H256::zero()
} else {
self.l1_contract.get_batch(batch_id - 1).await?.state_root
};
// 3. Re-execute all transactions
let mut state = self.state_at_root(pre_state)?;
for tx in &transactions {
if let Err(e) = state.apply_transaction(tx) {
// Transaction should have failed but didn't
// This could be fraud if sequencer claimed success
}
}
// 4. Compare state roots
let computed_root = state.state_root();
let claimed_root = batch_data.state_root;
if computed_root != claimed_root {
// FRAUD DETECTED!
let fraud_proof = self.generate_fraud_proof(
batch_id,
pre_state,
computed_root,
claimed_root,
&transactions,
)?;
self.submit_fraud_proof(fraud_proof).await?;
return Ok(false);
}
Ok(true)
}
fn generate_fraud_proof(
&self,
batch_id: u64,
pre_state: H256,
correct_root: H256,
claimed_root: H256,
transactions: &[Transaction],
) -> Result<FraudProof> {
// Find the first transaction that causes divergence
let mut state = self.state_at_root(pre_state)?;
for (i, tx) in transactions.iter().enumerate() {
let pre_tx_root = state.state_root();
state.apply_transaction(tx)?;
let post_tx_root = state.state_root();
// Check if this tx causes the divergence
// (simplified - real impl would compare intermediate roots)
}
// Generate proof
Ok(FraudProof {
batch_id,
pre_state_root: pre_state,
post_state_root: claimed_root,
correct_state_root: correct_root,
transaction_index: 0, // Would be computed
account_proof: state.get_proof(Address::zero()),
transaction: transactions[0].clone(),
})
}
}
Phased Implementation Guide
Phase 1: L2 State Management (Days 1-5)
Goal: Implement the core L2 state with Merkle tree.
Tasks:
- Implement Account struct with balance and nonce
- Build Merkle tree for state root computation
- Implement state transitions (credit, debit, transfer)
- Add Merkle proof generation and verification
Validation:
#[test]
fn test_state_transitions() {
let mut state = L2State::new();
// Credit Alice
state.credit(alice, U256::from(100));
assert_eq!(state.balance(alice), U256::from(100));
// Transfer to Bob
state.transfer(alice, bob, U256::from(30)).unwrap();
assert_eq!(state.balance(alice), U256::from(70));
assert_eq!(state.balance(bob), U256::from(30));
// State root changes
let root1 = state.state_root();
state.transfer(alice, bob, U256::from(10)).unwrap();
let root2 = state.state_root();
assert_ne!(root1, root2);
}
#[test]
fn test_merkle_proofs() {
let mut state = L2State::new();
state.credit(alice, U256::from(100));
let proof = state.get_proof(alice);
let root = state.state_root();
assert!(verify_proof(proof, root, alice, U256::from(100)));
}
Phase 2: L1 Bridge Contract (Days 6-12)
Goal: Deploy and test the L1 Solidity contract.
Tasks:
- Implement deposit() with event emission
- Implement submitBatch() with state root storage
- Implement challengeBatch() with fraud proof verification
- Implement finalizeWithdrawal() with proof verification
- Add sequencer bond and slashing
Validation:
function test_deposit() public {
vm.deal(alice, 10 ether);
vm.prank(alice);
bridge.deposit{value: 1 ether}();
// Check event emitted
assertEq(address(bridge).balance, 1 ether);
}
function test_submit_batch() public {
vm.prank(sequencer);
bridge.submitBatch(
bytes32(uint256(1)), // state root
hex"00" // tx data
);
(bytes32 root, uint256 timestamp, bool challenged) = bridge.batches(0);
assertEq(root, bytes32(uint256(1)));
assertFalse(challenged);
}
function test_challenge_period() public {
// Submit batch
vm.prank(sequencer);
bridge.submitBatch(bytes32(uint256(1)), hex"00");
// Try to finalize immediately - should fail
vm.expectRevert("Challenge period not over");
bridge.finalizeWithdrawal(proof);
// Wait 7 days
vm.warp(block.timestamp + 7 days);
// Now should succeed
bridge.finalizeWithdrawal(proof);
}
Phase 3: Sequencer Node (Days 13-20)
Goal: Build the L2 sequencer that processes transactions.
Tasks:
- Implement L1 event watcher for deposits
- Build transaction mempool and ordering
- Implement transaction execution engine
- Create batch assembler and submitter
- Add JSON-RPC API for users
Validation:
#[tokio::test]
async fn test_sequencer_deposit_flow() {
let sequencer = TestSequencer::new().await;
// Simulate L1 deposit
sequencer.l1.deposit(alice, U256::from(100)).await;
// Wait for sequencer to process
tokio::time::sleep(Duration::from_secs(5)).await;
// Check L2 balance
let balance = sequencer.get_balance(alice).await;
assert_eq!(balance, U256::from(100));
}
#[tokio::test]
async fn test_batch_submission() {
let sequencer = TestSequencer::new().await;
// Add transactions
for i in 0..50 {
sequencer.submit_tx(test_tx(i)).await.unwrap();
}
// Force batch submission
sequencer.force_batch().await;
// Check L1 has the batch
let batch = sequencer.l1.get_batch(0).await;
assert!(batch.is_some());
}
Phase 4: Verifier and Fraud Proofs (Days 21-30)
Goal: Implement the fraud detection and proof system.
Tasks:
- Build verifier that re-executes all batches
- Implement state divergence detection
- Create fraud proof generator
- Test fraud proof submission to L1
- Implement sequencer slashing
Validation:
#[tokio::test]
async fn test_fraud_detection() {
let mut test_env = TestEnv::new().await;
// Submit valid batch
test_env.sequencer.submit_batch(valid_batch).await;
assert!(test_env.verifier.verify_batch(0).await.is_ok());
// Submit invalid batch (manually craft bad state root)
test_env.sequencer.submit_invalid_batch().await;
// Verifier should detect fraud
let result = test_env.verifier.verify_batch(1).await;
assert!(result.is_err());
// Check fraud proof was submitted
let batch = test_env.l1.get_batch(1).await;
assert!(batch.challenged);
}
#[tokio::test]
async fn test_slashing() {
let mut test_env = TestEnv::new().await;
let initial_bond = test_env.l1.sequencer_bond().await;
// Submit fraud and prove it
test_env.sequencer.submit_invalid_batch().await;
test_env.verifier.verify_batch(0).await.unwrap_err();
// Sequencer should be slashed
let final_bond = test_env.l1.sequencer_bond().await;
assert!(final_bond < initial_bond);
}
Phase 5: Withdrawal Bridge (Days 31-38)
Goal: Complete the withdrawal flow.
Tasks:
- Implement L2 withdrawal initiation
- Build withdrawal Merkle proof generation
- Implement L1 withdrawal finalization
- Add challenge period enforcement
- Test full deposit->withdraw cycle
Validation:
#[tokio::test]
async fn test_full_cycle() {
let mut test_env = TestEnv::new().await;
// 1. Deposit
test_env.l1.deposit(alice, U256::from(100)).await;
test_env.wait_for_l2_sync().await;
assert_eq!(test_env.l2_balance(alice).await, U256::from(100));
// 2. L2 transfers
test_env.l2_transfer(alice, bob, U256::from(30)).await;
assert_eq!(test_env.l2_balance(alice).await, U256::from(70));
// 3. Initiate withdrawal
test_env.initiate_withdrawal(alice, U256::from(50)).await;
// 4. Wait for batch submission
test_env.force_batch().await;
// 5. Wait for challenge period (in test, fast-forward)
test_env.fast_forward(7 * 24 * 60 * 60).await;
// 6. Finalize withdrawal
let proof = test_env.get_withdrawal_proof(alice).await;
test_env.l1.finalize_withdrawal(proof).await;
// 7. Check final balances
assert_eq!(test_env.l1_balance(alice).await, U256::from(50));
assert_eq!(test_env.l2_balance(alice).await, U256::from(20));
}
Phase 6: Production Hardening (Days 39-45)
Goal: Make the system robust for real use.
Tasks:
- Add comprehensive error handling
- Implement state persistence (RocksDB)
- Add monitoring and metrics
- Implement graceful shutdown and recovery
- Add configuration management
- Write documentation
Hints in Layers
Hint 1: Start with a Simple L1 Contract
Your L1 bridge contract needs these minimal functions:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract RollupBridge {
struct StateCommitment {
bytes32 stateRoot;
uint256 timestamp;
bool challenged;
}
mapping(uint256 => StateCommitment) public batches;
uint256 public nextBatchId;
address public sequencer;
uint256 public constant CHALLENGE_PERIOD = 7 days;
event Deposit(address indexed user, uint256 amount);
event BatchSubmitted(uint256 indexed batchId, bytes32 stateRoot);
event BatchChallenged(uint256 indexed batchId, address challenger);
event WithdrawalFinalized(address indexed user, uint256 amount);
function deposit() external payable {
emit Deposit(msg.sender, msg.value);
}
function submitBatch(bytes32 stateRoot, bytes calldata txData) external {
require(msg.sender == sequencer, "Only sequencer");
batches[nextBatchId] = StateCommitment({
stateRoot: stateRoot,
timestamp: block.timestamp,
challenged: false
});
emit BatchSubmitted(nextBatchId, stateRoot);
nextBatchId++;
}
function challengeBatch(uint256 batchId, bytes calldata fraudProof) external {
StateCommitment storage batch = batches[batchId];
require(!batch.challenged, "Already challenged");
require(block.timestamp < batch.timestamp + CHALLENGE_PERIOD, "Too late");
// Verify fraud proof (simplified)
require(verifyFraudProof(batchId, fraudProof), "Invalid proof");
batch.challenged = true;
emit BatchChallenged(batchId, msg.sender);
// Slash sequencer, reward challenger
}
function finalizeWithdrawal(bytes32[] calldata proof) external {
// Verify withdrawal was included in finalized batch
// Transfer funds to user
}
function verifyFraudProof(uint256 batchId, bytes calldata proof) internal returns (bool) {
// Re-execute and compare state roots
return true;
}
}
Hint 2: L2 State as a Simple Mapping
Donโt overcomplicate L2 state initially:
pub struct L2State {
balances: HashMap<Address, U256>,
nonces: HashMap<Address, u64>,
// Use a simple Patricia-Merkle trie for state root
state_trie: PatriciaTrie,
}
impl L2State {
pub fn apply_transaction(&mut self, tx: &Transaction) -> Result<(), Error> {
// 1. Check nonce
let current_nonce = self.nonces.get(&tx.from).copied().unwrap_or(0);
if tx.nonce != current_nonce {
return Err(Error::InvalidNonce);
}
// 2. Check balance
let balance = self.balances.get(&tx.from).copied().unwrap_or(U256::zero());
if balance < tx.value {
return Err(Error::InsufficientBalance);
}
// 3. Update state
*self.balances.entry(tx.from).or_default() -= tx.value;
*self.balances.entry(tx.to).or_default() += tx.value;
*self.nonces.entry(tx.from).or_default() += 1;
// 4. Update trie
self.update_trie(&tx.from);
self.update_trie(&tx.to);
Ok(())
}
pub fn state_root(&self) -> H256 {
self.state_trie.root()
}
}
Hint 3: Batching Strategy
Batch when either condition is met:
const MAX_BATCH_SIZE: usize = 100;
const MAX_BATCH_TIME: Duration = Duration::from_secs(60);
impl Sequencer {
fn should_submit_batch(&self) -> bool {
self.pending_transactions.len() >= MAX_BATCH_SIZE
|| self.time_since_last_batch() > MAX_BATCH_TIME
}
async fn batch_loop(&mut self) {
loop {
if self.should_submit_batch() && !self.pending_transactions.is_empty() {
if let Err(e) = self.submit_batch().await {
log::error!("Failed to submit batch: {}", e);
}
}
tokio::time::sleep(Duration::from_secs(1)).await;
}
}
}
Hint 4: Simplified Fraud Proof
For a minimal version, fraud proofs can be simple:
function challengeBatch(
uint256 batchId,
bytes calldata txData,
bytes32 claimedStateRoot,
bytes32 correctStateRoot,
bytes calldata accountProofs
) external {
StateCommitment storage batch = batches[batchId];
require(batch.stateRoot == claimedStateRoot, "Wrong batch");
// Re-execute transactions on L1
// This is gas-intensive but simple
bytes32 computed = reExecuteTransactions(txData, accountProofs);
require(computed != claimedStateRoot, "No fraud");
// Fraud proven!
batch.challenged = true;
// Slash sequencer, reward challenger
slashSequencer();
payable(msg.sender).transfer(CHALLENGER_REWARD);
}
function reExecuteTransactions(bytes calldata txData, bytes calldata proofs)
internal
returns (bytes32)
{
// Decode and execute each transaction
// Return final state root
}
Hint 5: Testing Fraud Detection
Create a test where the sequencer is malicious:
#[test]
fn test_fraud_proof() {
// Honest execution
let mut state = L2State::new();
state.credit(alice, U256::from(100));
state.apply_transaction(&Transfer {
from: alice,
to: bob,
amount: U256::from(50)
}).unwrap();
let honest_root = state.state_root();
// Malicious execution (ignore balance check)
let mut evil_state = L2State::new();
evil_state.credit(alice, U256::from(100));
evil_state.force_credit(bob, U256::from(1_000_000)); // FRAUD!
let evil_root = evil_state.state_root();
// Roots should differ
assert_ne!(honest_root, evil_root);
// Submit evil root to L1
l1_bridge.submit_batch(evil_root, tx_data);
// Challenger proves fraud
let fraud_proof = generate_fraud_proof(
honest_root,
evil_root,
&state,
);
l1_bridge.challenge_batch(batch_id, fraud_proof);
// Batch should be challenged
assert!(l1_bridge.batches(batch_id).challenged);
}
Hint 6: Use Existing Tools
Donโt build everything from scratch:
- Use ethers-rs for L1 interaction
- Use serde for transaction serialization
- Use RocksDB or sled for L2 state storage
- Use your Merkle tree from Project 3 for state roots
- Use Foundry for Solidity testing
- Use alloy for modern Ethereum types
Common Pitfalls & Debugging
Pitfall 1: State Root Mismatch After Deposit
Problem: L2 state root doesnโt match after processing deposit.
Symptom: Fraud proofs fail even for honest batches.
Solution:
// Ensure deposits are processed deterministically
impl L2State {
fn process_deposit(&mut self, deposit: &Deposit) {
// Always process in same order (by L1 block, then log index)
let key = (deposit.l1_block, deposit.log_index);
if self.processed_deposits.contains(&key) {
return; // Already processed
}
self.credit(deposit.user, deposit.amount);
self.processed_deposits.insert(key);
}
}
Pitfall 2: Nonce Gaps
Problem: Transactions fail due to nonce gaps.
Symptom: Valid transactions rejected with โinvalid nonceโ.
Solution:
// Handle nonces carefully
impl Sequencer {
fn validate_transaction(&self, tx: &Transaction) -> Result<()> {
let expected_nonce = self.state.nonce(tx.from);
if tx.nonce < expected_nonce {
return Err(Error::NonceTooLow);
}
if tx.nonce > expected_nonce {
// Could queue for later, or reject
return Err(Error::NonceTooHigh);
}
Ok(())
}
}
Pitfall 3: L1 Reorg Handling
Problem: L1 reorganization causes L2 inconsistency.
Symptom: Deposits credited twice or missed after L1 reorg.
Solution:
impl L1Watcher {
async fn handle_reorg(&mut self, new_head: BlockNumber) {
// Identify reorged blocks
let reorged_deposits = self.deposits_after(new_head);
// Rollback L2 state
for deposit in reorged_deposits.rev() {
self.l2_state.debit(deposit.user, deposit.amount);
}
// Re-sync from new head
self.sync_from(new_head).await;
}
async fn watch_l1(&mut self) {
loop {
let new_head = self.l1.block_number().await?;
if new_head < self.last_processed {
// Reorg detected!
self.handle_reorg(new_head).await;
} else {
self.process_new_blocks(self.last_processed, new_head).await;
}
self.last_processed = new_head;
tokio::time::sleep(POLL_INTERVAL).await;
}
}
}
Pitfall 4: Gas Limit in Fraud Proofs
Problem: Fraud proof transaction runs out of gas.
Symptom: Challenge transaction reverts with โout of gasโ.
Solution:
// Limit batch size or use interactive proofs
function challengeBatch(
uint256 batchId,
uint256 txIndex, // Challenge specific transaction
bytes calldata proof
) external {
// Only re-execute ONE transaction, not entire batch
// This bounds gas usage
bytes32 preState = getStateBeforeTx(batchId, txIndex, proof);
bytes32 postState = executeOneTx(batchId, txIndex, proof);
bytes32 claimed = getClaimedPostState(batchId, txIndex);
require(postState != claimed, "No fraud");
// ...
}
Pitfall 5: Withdrawal Proof Verification
Problem: Valid withdrawals rejected.
Symptom: Users canโt finalize withdrawals.
Solution:
// Ensure proof matches exactly what L1 expects
impl WithdrawalProof {
fn generate(
state: &L2State,
batch_id: u64,
withdrawal: &Withdrawal,
) -> Self {
// The leaf must match exactly what L1 hashes
let leaf = keccak256(abi.encode(
withdrawal.user,
withdrawal.amount,
withdrawal.nonce,
batch_id,
));
let proof = state.merkle_tree.get_proof(leaf);
Self {
user: withdrawal.user,
amount: withdrawal.amount,
nonce: withdrawal.nonce,
batch_id,
merkle_proof: proof,
}
}
}
Extensions and Challenges
Challenge 1: EVM Execution
Replace the simple transfer model with full EVM execution:
- Integrate revm for EVM execution
- Support smart contract deployment on L2
- Handle CREATE/CREATE2 addresses
- Implement EVM-compatible fraud proofs
Challenge 2: Decentralized Sequencer
Implement a rotating sequencer system:
- Sequencer auction (highest bidder wins next slot)
- Proof-of-stake based selection
- Fallback to forced transaction inclusion via L1
Challenge 3: Fast Withdrawals
Build a liquidity provider network for instant withdrawals:
- LP stakes capital on L1
- User requests fast withdrawal, LP provides liquidity
- LP claims userโs withdrawal after challenge period
- Fee based on risk and capital lock-up
Challenge 4: Cross-Rollup Communication
Implement messaging between two L2s:
- Shared sequencer approach
- Message passing via L1
- Atomic cross-rollup swaps
Challenge 5: Data Compression
Optimize data posted to L1:
- Signature aggregation (BLS signatures)
- State diff compression
- Custom encoding for common patterns
- Blob transactions (EIP-4844)
Books That Will Help
| Topic | Book | Chapter |
|---|---|---|
| Optimistic rollups overview | โMastering Ethereumโ by Antonopoulos & Wood | Ch. 13: EVM & Ch. 14: Consensus |
| Merkle trees and proofs | โMastering Bitcoinโ by Antonopoulos | Ch. 9: The Blockchain |
| State management | โDesigning Data-Intensive Applicationsโ by Kleppmann | Ch. 3: Storage Engines & Ch. 9: Consistency |
| Fraud proof game theory | โGame Theoryโ by Osborne | Ch. 1-3: Strategic Games |
| P2P networking | โComputer Networksโ by Tanenbaum | Ch. 2: The Application Layer |
| Distributed consensus | โDesigning Data-Intensive Applicationsโ by Kleppmann | Ch. 9: Consistency and Consensus |
| EVM execution | โMastering Ethereumโ by Antonopoulos & Wood | Ch. 13: The Ethereum Virtual Machine |
| Cryptographic commitments | โSerious Cryptographyโ by Aumasson | Ch. 6: Hash Functions |
| Smart contract security | โMastering Ethereumโ by Antonopoulos & Wood | Ch. 9: Smart Contract Security |
Additional Resources
Official Documentation
- Optimistic Rollups - ethereum.org
- Arbitrum Documentation
- Optimism Specifications
- EIP-4844: Blob Transactions
Research & Analysis
- L2Beat: Rollup Risk Analysis
- Vitalik: An Incomplete Guide to Rollups
- Arbitrum Fraud Proofs Deep Dive
Code References
- Optimism Monorepo
- Arbitrum Nitro
- Fuel Labs - Rust rollup implementation
- Mini Rollup (Educational)
Videos & Talks
Self-Assessment Checklist
Before moving to the next project, verify:
Conceptual Understanding
- I can explain why rollups need to post data to L1 for security
- I understand the difference between optimistic and ZK rollups
- I can describe the fraud proof mechanism and its game theory
- I understand why the challenge period is 7 days
- I can explain the 1-of-N honest verifier security model
- I understand how EIP-4844 reduces rollup costs
Implementation Verification
- Deposits from L1 are correctly credited on L2
- L2 transactions correctly update state and nonces
- State roots are correctly computed via Merkle tree
- Batches are correctly submitted to L1 with calldata
- Fraud proofs can detect invalid state transitions
- Challenge mechanism correctly slashes malicious sequencer
- Withdrawals work after challenge period
Security
- No double-deposit vulnerability
- No withdrawal amount manipulation
- Challenge period correctly enforced
- Sequencer bond correctly slashed on fraud
- L1 reorgs handled correctly
Production Readiness
- State persists across restarts
- Graceful shutdown preserves pending transactions
- Monitoring metrics exposed
- Error handling is comprehensive
- Documentation is complete
Whatโs Next?
With a working optimistic rollup, you understand how Ethereum scales. In Project 14: Smart Contract Security Scanner, youโll apply your deep knowledge of EVM execution and smart contract patterns to build a static analysis tool that detects vulnerabilities before theyโre exploited.
Your rollup implementation demonstrates:
- Layer 2 scaling architecture
- Fraud proof security models
- Cross-layer bridge design
- State commitment schemes
This knowledge is directly applicable to:
- L2 protocol development (Arbitrum, Optimism, Base)
- Bridge security auditing
- MEV and sequencer design
- ZK-rollup architecture (next evolution)
The future of Ethereum is Layer 2. Now you understand why and how.