Skip to content
← Sequencer & Rollup Architecture — From Centralized Block Producer to Shared Sequencers
Reading Real Sequencer Code
Topic: Reth payload builder →
Lesson 3 of 7·CONTENT18 min45 XP
Course
Sequencer & Rollup Architecture — From Centralized Block Producer to Shared Sequencers
Lesson role
CONTENT
Sequence
3 / 7

Lesson 3 — Reading op-rbuilder — the Reth-based OP Stack sequencer

Question

op-rbuilder is Flashbots' Rust block builder for OP Stack chains, built on the Reth SDK and op-reth; a sequencer uses it through rollup-boost. Read the actual block-building loop, see exactly where it integrates with Reth's payload builder, and trace what a sequencer does block-by-block.

Principle (minimum model)

  • op-rbuilder lives at flashbots/op-rbuilder. Built on the Reth SDK plus op-reth's reth-optimism-* crates (from ethereum-optimism/optimism). Its own OpPayloadBuilder (crates/op-rbuilder/src/builder/payload.rs) implements Reth's PayloadBuilder trait in place of op-reth's default builder.
  • Reth SDK integration point. crates/op-rbuilder/src/launcher.rs builds an OpNode with .with_types::<OpNode>() and swaps the payload slot: .payload(FlashblocksServiceBuilder::new(builder_config)). Same pattern as any custom Reth component.
  • OpPayloadBuilder::build_payload is the core function. Receives OpPayloadBuilderAttributes from op-node (via the Engine API); first executes the transactions in the attributes (execute_sequencer_transactions in builder/context.rs), then pool transactions (execute_best_transactions), streaming partial blocks as flashblocks; computes the state root; emits an OpBuiltPayload.
  • Three OP-specific things on top of Reth. (1) Deposit transactions (L1 → L2 messages, at the top of the block). (2) L1 data fee on every non-deposit L2 tx (the cost of posting it to L1). (3) The L1-attributes deposit tx that opens every block and records the L1 origin (the older separate "system transaction" type was removed in the Regolith upgrade).
  • The block-building loop. Pull tx from mempool → simulate with revm → check gas + state validity → include or skip → continue until gas limit or empty mempool → finalize.
  • Bundles. op-rbuilder accepts bundles via eth_sendBundle when started with --builder.enable-revert-protection (see docs/eth_sendBundle.md), the way L1 builders accept Flashbots bundles.
  • Read order. crates/op-rbuilder/src/launcher.rs (wiring) → crates/op-rbuilder/src/builder/payload.rs (try_build / build_payload) → crates/op-rbuilder/src/builder/context.rs (sequencer txs, then pool txs).

Worked example + steps

Reading op-rbuilder — the Reth-based OP Stack sequencer

If you spin up your own OP Stack chain, the sequencer's blocks come from its execution client (op-geth or op-reth), driven by op-node. If you want an external Rust builder, you put flashbots/op-rbuilder — Flashbots' Rust block builder for OP Stack — behind rollup-boost, which forwards the sequencer's Engine API calls to both and uses the builder's block when it is valid. op-rbuilder is the open-source reference for "block building on Reth," and it's the code you'd read if you wanted to understand what a real sequencer does once the marketing diagrams stop.

1. The op-rbuilder architecture

flowchart TB
    EngineAPI["Engine API<br/>(from rollup consensus)"] -->|forkchoiceUpdated| Builder["Block Builder"]
    Mempool["L2 Mempool"] -->|pending txs| Builder
    L1Inbox["L1 Inbox<br/>(deposits + force-includes)"] -->|deposit events| Builder
    Builder -->|build block| EVM["revm<br/>(execution)"]
    EVM -->|state changes| State["State DB"]
    Builder -->|signed block| EngineAPI2["Engine API<br/>(getPayload response)"]
    Builder -->|broadcast| P2P["P2P network<br/>(propagate to nodes)"]

Three input streams:

  1. Engine API — consensus tells us what to build on top of
  2. Mempool — user txs waiting for inclusion
  3. L1 Inbox — deposit txs that must be included

The builder takes these, applies the OP Stack rules for ordering, and produces a block.

2. OP Stack ordering rules

A sequencer building an OP block must respect specific rules:

  1. Deposits first: the L1-attributes deposit and any user deposits from the L1 origin must come at the top of the block
  2. L1 epoch attribution: the block must reference an L1 block (its "L1 origin")
  3. Sequencer signature: blocks gossiped over P2P before they are posted to L1 ("unsafe" blocks) are signed by the sequencer key; nodes deriving the chain from L1 don't check it
  4. Gas limit: set by the chain's system config on L1, not by the sequencer
  5. Force inclusion: L1 deposits are the force-inclusion path. If the sequencer doesn't post batches for an L1 origin within the sequencing window, derivation builds deposit-only blocks for it

op-node chooses the L1 origin and puts the deposits in the payload attributes; the builder (op-reth's default or op-rbuilder) must execute those attribute transactions first (execute_sequencer_transactions). Every node's derivation pipeline then re-checks the rules: that is what enforces them, not the builder.

Sequencer can choose:

  • Which user txs to include
  • Order of user txs
  • L1 epoch to attribute to
  • Block timestamp (within bounds)

Sequencer cannot choose:

  • Whether to include deposits (must)
  • Whether to skip L1 deposits (they come back through derivation)
  • Block validity rules (gas limit, base fee math)

Violating any "cannot" = nodes deriving from L1 reject the batch = your unsafe blocks get reorged out.

3. Reading op-rbuilder source

Key files at commit 4f1bec3 (2026-09-24; paths may shift across versions):

PathRole
crates/op-rbuilder/src/launcher.rsWires the builder into an OpNode (.payload(FlashblocksServiceBuilder::new(...)))
crates/op-rbuilder/src/builder/payload.rsOpPayloadBuilder: try_build → build_payload → build_payload_inner (fallback block, then flashblocks)
crates/op-rbuilder/src/builder/context.rsexecute_sequencer_transactions (attribute txs incl. deposits) and execute_best_transactions (pool txs)
crates/op-rbuilder/src/builder/best_txs.rsIterating the pool's best transactions
crates/op-rbuilder/src/revert_protection.rseth_sendBundle / revert-protected transactions

The core loop, as simplified pseudocode (not the real signatures):

async fn build_payload(
    attributes: PayloadAttributes,
    parent: BlockHash,
    state: StateProvider,
    pool: TxPool,
) -> eyre::Result<ExecutionPayload> {
    let mut block_env = BlockEnv::from(parent, attributes);
    let mut executor = Executor::new(state, &block_env);
    let mut included_txs = Vec::new();

    // 1. Execute the txs op-node put in the attributes (L1-attributes deposit + user deposits)
    for sequencer_tx in attributes.transactions {
        executor.execute(&sequencer_tx)?;
        included_txs.push(sequencer_tx);
    }
    // (if attributes.no_tx_pool is set, op-node wants only these txs: skip step 2)

    // 2. Pull from mempool until gas limit
    let pending = pool.best_pending();
    for tx in pending {
        if executor.gas_used() + tx.gas_limit() > block_env.gas_limit {
            break;
        }
        match executor.execute(&tx) {
            Ok(_) => included_txs.push(tx),
            Err(_) => continue, // skip failed txs
        }
    }

    // 3. Seal block (compute roots)
    let payload = ExecutionPayload {
        header: build_header(&block_env, &executor, &included_txs),
        transactions: included_txs,
    };

    Ok(payload)
}

That's the sequencer loop in ~30 lines. The complexity in production op-rbuilder is in:

  • Flashblocks: building and publishing partial blocks at intervals within one block time
  • Cancellation and rebuilding when a new forkchoice update arrives
  • Bundles and revert protection (reverting bundle txs are dropped instead of included)
  • State-root computation cost (incremental state root options)

🔍 Find in repo. Open op-rbuilder's payload builder source and find the actual build_payload (or equivalent). How does it handle the case where the parent block changes mid-build?

4. The MEV question — what does the sequencer extract?

Whoever picks the tx order picks who profits. For OP Stack chains, three positions on how aggressively the sequencer monetizes that power:

PositionWhat sequencer doesExamples
Vanilla FIFOOrder by submission timeNaive implementations
Priority-fee orderingOrder by gas tip (like Ethereum mainnet)OP Stack default
External block buildingLet an external builder construct the blockOP Stack with rollup-boost + op-rbuilder

rollup-boost and op-rbuilder support the third: the sequencer gets its block from an external builder, and op-rbuilder itself can accept bundles from searchers via eth_sendBundle (with revert protection enabled). How any MEV revenue is shared is a chain-level business arrangement, not something in this code.

This is where Flashbots-style PBS (proposer-builder separation — split who chooses the block from who builds it) comes to L2: builders compete to construct the most profitable block, the sequencer accepts the winning bid.

5. The pre-confirmation game

A single sequencer's killer UX feature is the pre-confirmation: the moment you submit a tx, the sequencer signs back "yes, this will be included in block N at position M." You can show the user "confirmed" in 100 ms — long before any L1 finality.

This trick only works with one sequencer. Multi-party setups have to vote, and voting takes round trips.

In op-rbuilder the closest real feature is flashblocks: partial blocks built and streamed at short intervals within one L2 block time (see docs/flashblocks.md), so a wallet can show a tx as included before the full block is sealed. Users who rely on it trust the sequencer, not L1 finality.

The sequencer commits to inclusion. If the L2 reorgs (which can happen), the tx might not be in the canonical chain anymore. The sequencer might also be slashed in some designs if pre-conf is violated.

6. What about Tempo?

Tempo is not an OP Stack L2 and does not use op-rbuilder. It is an L1: validators run Simplex BFT consensus (via Commonware) over a Reth-based execution node (tempoxyz/tempo). The lesson's ideas that transfer are the builder/validator split and "the block builder is a swappable Reth component"; the OP-specific rules (deposits, L1 origin, sequencing window) do not.

7. Practice

  1. Clone op-rbuilder (or browse online)
  2. Find the deposit tx handling code
  3. Trace what happens when a user submits via the sequencer's RPC
  4. Identify: how does op-rbuilder handle a mempool tx that pays priority fee but reverts in execution?

8. Reading list

Final check: in one sentence, what's the core consensus-enforced constraint on a sequencer that prevents it from arbitrarily reordering or excluding user txs forever? If your answer doesn't reference "L1 deposits + the sequencing window," re-read §2.

Summary (3 lines)

  • op-rbuilder = Flashbots' Rust block builder for OP Stack, on the Reth SDK + op-reth, used by a sequencer through rollup-boost. Its OpPayloadBuilder takes op-reth's payload-builder slot.
  • Integration point: .payload(FlashblocksServiceBuilder::new(...)) in launcher.rs. Core loop: attribute txs (deposits) first → pool txs → seal, streamed as flashblocks. Bundles via eth_sendBundle with revert protection.
  • Read order: launcher.rs → builder/payload.rs → builder/context.rs. Next lesson: fraud proofs vs ZK validity proofs — the two state-root verification models.