FABRKNT
Sequencer & Rollup Architecture — From Centralized Block Producer to Shared Sequencers
Reading Real Sequencer Code
Lesson 3 of 7·CONTENT18 min45 XP

Treat this page as a workbench, not a blog post. The goal is to extract a reusable mental model from the source and carry it into the rest of the Fabrknt stack.

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 the Rust sequencer that powers OP-Reth nodes — written by Flashbots, built on Reth SDK. 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 Reth SDK; replaces Reth's default EthereumPayloadBuilder with a customised OpPayloadBuilder that handles OP-specific deposit transactions and L1 cost calculation.
  • Reth SDK integration point. NodeBuilder::launch_with_op_attributes registers OpPayloadBuilder as the payload builder service. Same pattern as Reth's default — just a different builder type.
  • OpPayloadBuilder::build_payload is the core function. Receives OpPayloadBuilderAttributes from the consensus layer; iterates the mempool; executes transactions with revm; computes the state root; emits an ExecutionPayload.
  • Three OP-specific things on top of Reth. (1) Deposit transactions (L1 → L2 messages, prepended at the top of every block). (2) L1 cost on every L2 tx (the L1 calldata cost for posting the batch). (3) System transactions (sequencer fee, L1 info, etc.).
  • 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.
  • MEV-aware builders. Flashbots' op-rbuilder can also accept bundles via a private RPC, the way L1 builders accept Flashbots bundles. Sequencers can monetise MEV directly.
  • Read order. crates/payload/builder/src/builder.rs (entry point) → crates/payload/builder/src/execute.rs (revm integration) → crates/payload/builder/src/cost.rs (L1 cost). ~3 K lines of Rust to read top to bottom.

Worked example + steps

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

If you spin up your own OP Stack chain today, the binary producing blocks is almost certainly flashbots/op-rbuilder — Paradigm's Rust block builder for OP-derived rollups. Every Reth-based L2 either runs it directly or forks from it. It's the production reference for "sequencer 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: any deposit txs from the L1 inbox 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: the block must be signed by the active sequencer key
  4. Gas limit: must be within OP-specific bounds (different from mainnet)
  5. Force inclusions: if any L1-inboxed txs have aged past deadline, must include them

These are the OP-specific block validity rules. op-rbuilder enforces all of them.

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 force-included txs (must include)
  • Block validity rules (gas limit, base fee math)

Violating any "cannot" = L1 verification fails = your block gets reorged.

3. Reading op-rbuilder source

Key files (paths may shift across versions; navigate via search):

PathRole
crates/builder/src/payload.rsThe core block-building loop
crates/builder/src/ordering.rsTx ordering strategy
crates/builder/src/deposit.rsDeposit tx handling
crates/builder/src/seal.rsBlock sealing + signing

The main building function looks roughly like:

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. Force-include deposit txs
    for deposit_tx in attributes.l1_deposits {
        executor.execute(&deposit_tx)?;
        included_txs.push(deposit_tx);
    }

    // 2. Include force-inclusion txs (aged inbox)
    for force_tx in attributes.force_included {
        executor.execute(&force_tx)?;
        included_txs.push(force_tx);
    }

    // 3. 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
        }
    }

    // 4. Seal block (compute roots, sign)
    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:

  • Async execution (build while accepting more txs)
  • Reorg handling (when the parent changes mid-build)
  • MEV-aware ordering (prefer high-fee txs, sandwich-resistant ordering)
  • Gas estimation accuracy

🔍 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
MEV-aware with builder marketAccept external bids for block constructionOP Stack with op-rbuilder + bundle markets

op-rbuilder supports the third — chains can configure whether to accept external bundles from builders/searchers (third-party block constructors that compete to build the most valuable block). The bundle market pays the sequencer for blockspace.

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 mempool admission step is where pre-confirmations would be issued. If the sequencer signs "I commit to including this tx," users can treat it as final without waiting for 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. For Tempo's sequencer

Tempo's sequencer (operated by Paradigm) almost certainly:

  • Uses op-rbuilder or a similar Rust block builder
  • Implements OP-Stack-like ordering rules (deposits first, force inclusions, signature)
  • Issues pre-confirmations to merchants (sub-second UX)
  • Supports priority-fee ordering with merchant-priority bumps
  • Has emergency halt authority for fraud detection

The architectural pattern is the same as any OP Stack L2; the business logic on top (merchant priority, fraud detection) is specific.

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 force-inclusion + deadline," re-read §2.

Summary (3 lines)

  • op-rbuilder = Flashbots-built Rust sequencer on top of Reth SDK. Replaces Reth's default EthereumPayloadBuilder with OpPayloadBuilder for OP-specific deposit txs + L1 cost + system txs.
  • Integration point: NodeBuilder::launch_with_op_attributes. Core loop: pull mempool → simulate revm → include/skip → finalize. MEV-aware via Flashbots-style private RPC.
  • Read order: builder.rsexecute.rscost.rs (~3 K lines). Next lesson: fraud proofs vs ZK validity proofs — the two state-root verification models.