Skip to content

Add L1SLOAD (RIP-7728) proof collection and ZK verification for Taiko L2#50

Draft
jmadibekov wants to merge 13 commits intomasterfrom
feat/l1sload-raiko-v2
Draft

Add L1SLOAD (RIP-7728) proof collection and ZK verification for Taiko L2#50
jmadibekov wants to merge 13 commits intomasterfrom
feat/l1sload-raiko-v2

Conversation

@jmadibekov
Copy link

@jmadibekov jmadibekov commented Feb 23, 2026

What is L1SLOAD?

L1SLOAD (RIP-7728) is a precompile at address 0x10001 that lets L2 smart contracts read L1 Ethereum storage. An L2 transaction calls the precompile with 84 bytes — an L1 contract address (20 bytes), a storage key (32 bytes), and an L1 block number (32 bytes) — and gets back the 32-byte storage value from that slot at that block.

This enables cross-layer composability: L2 contracts can read L1 oracle prices, verify L1 token balances, or check L1 governance state without a separate bridge.

What this PR does

Adds end-to-end L1SLOAD support to Raiko's preflight (witness collection) and proving (ZK verification) phases.

Preflight phase (with network access)

  1. Direct call detection — Scans block transactions for calls to 0x10001 with 84-byte input and extracts the requested (address, key, block_number) tuples.
  2. Indirect call detection — Executes the block with an RPC fallback callback. When a smart contract internally calls the L1SLOAD precompile (not visible from tx scanning), the callback fetches the value from L1, caches it, and records the call.
  3. Proof collection — Fetches EIP-1186 Merkle proofs (eth_getProof) for all detected calls, batched by (block_number, address).
  4. Header chain collection — Fetches L1 block headers for the backward chain (anchor → anchor-256) and forward chain (anchor → l1origin) needed for state root verification.

Proving phase (no network, inside ZK/SGX)

  1. Header chain verification — Verifies parent_hash linkage in both directions from the trusted anchor block to build a verified block_number → state_root map.
  2. MPT proof verification — For each L1SLOAD call, verifies the account proof against the state root and the storage proof against the account's storage root, using alloy_trie::verify_proof.
  3. Cache population — Populates the L1SLOAD precompile cache with verified values only.
  4. Re-execution — The block re-executes deterministically; all L1SLOAD reads come from the pre-populated cache.

Trust model

The anchor block's state root (committed in the L2 anchor transaction) is the cryptographic trust root. All L1 state is verified against it:

Anchor tx → anchor_state_root (trusted)
                    |
    ┌───────────────┴───────────────┐
    ▼                               ▼
  Backward (parent_hash)      Forward (parent_hash)
  anchor-1 ... anchor-256    anchor+1 ... l1origin
    ▼                               ▼
  State root per block        State root per block
    ▼                               ▼
  Account proof (MPT)         Account proof (MPT)
    ▼                               ▼
  Storage proof (MPT)         Storage proof (MPT)
    ▼                               ▼
  Verified value              Verified value

Block range

  • Backward: up to 256 blocks before the anchor block
  • Forward: from anchor block up to the L1 origin block
  • Valid range: [anchor - 256, l1origin]

Key implementation details

  • Concurrency safety: A global execution lock serializes the cache clear → verify → populate → execute cycle across concurrent proving tasks, since the L1SLOAD precompile uses process-global state.
  • Anchor extraction: get_anchor_tx_info_by_fork() handles all Taiko fork variants (Shasta, Pacaya, Ontake, default).
  • Non-existence proofs: MPT verification correctly handles branch nodes (17 RLP elements), extension nodes (HP flag < 2), and leaf nodes (HP flag ≥ 2).
  • Backward compatibility: New GuestInput fields use #[serde(default)] so pre-L1SLOAD inputs continue to work.

Example: what gets detected

Call pattern Detection method
EOA → 0x10001 (direct) Transaction input scanning
EOA → Contract → 0x10001 (indirect) RPC fallback callback during execution
EOA → Proxy → Implementation → 0x10001 RPC fallback callback during execution

Files changed

Area Files What
Data types lib/src/input.rs, lib/src/anchor.rs L1StorageProof struct, anchor extraction
Verification lib/src/l1_precompiles/l1sload.rs MPT verification, header chain validation
Proving core/src/lib.rs prepare_l1sload_for_execution() orchestration
Preflight core/src/preflight/mod.rs, core/src/preflight/util.rs Call detection, proof collection, RPC fallback
RPC core/src/provider/rpc.rs, core/src/provider/mod.rs L1 storage proof fetching
Build lib/src/builder/mod.rs Cache population during block building

Cross references

jmadibekov and others added 10 commits February 24, 2026 15:43
The L1SloadInspector and related infrastructure (inspect flag,
create_block_executor_with_inspector, trace_l1sload_calls stub) are
dead code. NMC handles L1SLOAD autonomously via direct L1 RPC calls,
so no proposer/driver tracing is needed. Raiko detects L1SLOAD calls
via direct transaction scanning in collect_l1_storage_proofs().

Removed:
- lib/src/builder/l1sload_inspector.rs (entire file)
- inspect field from TaikoWithOptimisticBlockExecutor
- create_block_executor_with_inspector method
- trace_l1sload_calls stub from preflight/util.rs
- Unused parameters from collect_l1_storage_proofs
Update all alethia-reth dependencies to the feat/l1sload-precompile-nmc
branch which is based on NMC's main and includes the L1SLOAD precompile
with the correct 84-byte API (address + storage key + block number).

Also fixes compilation issues from cherry-pick conflicts:
- RpcBlockDataProvider::new() signature (1 arg, not 2)
- Remove block_numbers assertion (field doesn't exist on master)
- Use TaikoBlock type in collect_l1_storage_proofs signature
…hasta mode

In Shasta mode, l1_header is set to l1_inclusion_block - 1 which differs
from the anchor block referenced by the anchor transaction. The L1SLOAD
code incorrectly compared anchor_state_root against l1_header.state_root,
causing "Anchor state root mismatch" errors for every batch.

Changes:
- Remove broken anchor state root validation in preflight (both single-block
  and batch paths) that compared two different L1 blocks' state roots
- Fix execute_transactions() and execute_transaction_batch() to extract
  the actual anchor block info from the anchor tx instead of using l1_header
- Re-export get_anchor_tx_info_by_fork for use in the execution phase

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- PR #2: Add L1SLOAD execution lock to serialize concurrent block
  executions that use L1SLOAD, preventing global cache races.
- PR #4: Verify newest ancestor header immediately precedes anchor block
  in build_verified_state_root_map.
- PR #5: Optimize get_and_verify_value to single verification pass
  instead of double-verifying for existing keys.
- PR #6: Distinguish extension vs leaf nodes in get_leaf_value via HP
  flag check, preventing misparse of extension nodes.
- PR #7: Include l1_storage_proofs in single-block GuestInput (was
  missing due to ..Default::default()).
- PR #9: Replace .expect() with proper ? error propagation in L1SLOAD
  verification paths.
- PR #10: Batch storage key proof collection by address to reduce RPC
  calls from one-per-key to one-per-block-number.
- PR #14: Create l1_provider once before spawn loop and clone into
  each chunk task instead of creating per-chunk.
Update alethia-reth dependency to 136e51a0 which replaces eprintln!
with tracing::trace! in the L1SLOAD precompile.
…oofs

When the MPT proof terminates at a branch node (non-existent account),
get_leaf_value was misinterpreting the branch's child hashes as leaf
path+value because it only checked the HP flag without first verifying
the node type via element count. A branch node's first child hash
starting with 0x3X (75% chance) passes the HP flag >= 2 check,
causing garbage value extraction and proof verification failure.

Fix: count RLP list elements before HP flag check (17 = branch,
2 = leaf/extension), matching alloy-trie's TrieNode::decode pattern.
@jmadibekov jmadibekov changed the title Add L1SLOAD prover support Add L1SLOAD (RIP-7728) proof collection and ZK verification for Taiko L2 Mar 3, 2026
Add [jmadibekov] tags to:
- preflight: scan detection, proof collection, indirect calls
- proving: verify/populate cycle, state root map, MPT verification
- cache: populate/clear operations
The log line at preflight/mod.rs:168 referenced `input.l1_storage_proofs`
etc., but `input` (GuestInput) isn't constructed until a few lines later.
The correct references are the local tuple bindings: `l1_storage_proofs`,
`l1_ancestor_headers`, `l1_successor_headers`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants