Skip to content
← Reth Expert — Production Engineering
Reth-based Chains — Reading the Extension Pattern
Lesson 21 of 25·CONTENT18 min45 XP
Course
Reth Expert — Production Engineering
Lesson role
CONTENT
Sequence
21 / 25

Lesson 20 — Custom executor — swapping the execution layer

Question

Custom Executor = override how Reth processes txs. Standard = revm with mainnet rules. Custom = OP fee handling, custom precompiles, etc.

Principle (minimum model)

  • Executor trait. execute_block(block, state) -> ExecutionResult. Reth calls this per block.
  • Standard impl. EthExecutor runs revm. Used by default.
  • Custom impl. Override execute_block to use revm with custom precompiles + custom fee handling.
  • Wire via NodeBuilder. NodeBuilder::executor(YourExecutor::default()).
  • Test. Run a tx through; assert behaviour differs from default Eth.
  • Common customisations. OP L1 fee handling, Tempo's payment precompiles (TIP-20 tokens, TIP-403 policy registry, fee manager in tempoxyz/tempo crates/precompiles/), Berachain PoL.
  • Composes with other components. Custom Executor + Custom Pool + Custom Consensus = a fully custom L1.
  • Production parallel. All Reth-based L1s/L2s ship custom executors.

Worked example + steps

Custom executor — swapping the execution layer

The executor is "what actually runs the transactions and produces the post-state." For Ethereum mainnet, this is vanilla revm. For Optimism, it's revm plus deposit-tx handling, plus L1 cost computation, plus a slightly different precompile list. This lesson is about how Reth lets you swap that in.

1. The trait surface

The relevant traits (names may drift slightly across reth versions; verify in source):

  • ConfigureEvm — given a block context, produce a configured revm instance (with the right precompile set, gas schedule, etc.)
  • BlockExecutionStrategy (or similar) — the loop that pulls txs from a block and feeds them to revm, accumulating receipts and state changes
  • ExecutorBuilder — the NodeBuilder slot that produces an executor for the running node

A chain customizes the first two via trait impls in its own crate, then registers them via the third in its NodeBuilder.

2. What Optimism overrides

Reading op-reth's EVM crate (rust/op-reth/crates/evm/ in ethereum-optimism/optimism; it was reth's crates/optimism/evm/ until Feb 2026) together with rust/op-revm/ will show you roughly:

OverrideWhy
Custom precompile listOP changes the precompile set per hardfork (rust/op-revm/src/precompiles.rs): Fjord adds RIP-7212 P256VERIFY, Granite caps bn254 pairing input size, Isthmus/Jovian add BLS12-381 with OP input limits
Deposit transaction handlingDeposit txs skip signature verification (they're authenticated by L1)
L1 cost calculationEvery OP tx pays both L2 gas AND an L1 data cost (calldata posting)
L1 info depositThe first tx of every block is the L1-attributes deposit that updates the L1Block predeploy; the executor extracts the L1 fee params from it (extract_l1_info in crates/evm/src/l1.rs)

The first one is config. The other three are execution-strategy-level — they live in the block executor's main loop.

3. The custom-precompile story

You wrote a custom precompile earlier. Now ask: where does that precompile get plugged into a chain?

Answer: ConfigureEvm impls hand revm a precompile set. A chain's ConfigureEvm impl extends the default set with its custom precompiles, gated by the chain's hardfork schedule.

So the wiring is:

ChainSpec  ──[which fork is active?]──▶  EVM config  ──[active precompile set]──▶  revm

The EVM config crate is where the precompile registration code physically lives.

4. The L1 cost computation (and why it's a great example)

OP Stack charges every transaction an L1 data cost — the amortized cost of posting the transaction's calldata to L1. This is a hard requirement: every node must compute the exact same L1 cost or block validation fails.

It's implemented inside the executor by:

  1. Before each tx, look up the current L1 base fee and blob gas price from a known storage slot
  2. Compute l1_cost = calldata_gas * l1_base_fee + blob_overhead
  3. Deduct from the sender's balance in addition to the L2 gas charge
  4. Credit it to the fee vault

This is a clean example of consensus-critical logic that you cannot put in a precompile — it has to be in the executor itself.

5. The execution loop, in pseudo-code

for tx in block.body:
    if is_deposit_tx(tx) and current_fork.allows_deposits():
        skip_signature_verify()
    else:
        verify_signature(tx)?

    db = state_provider.load_relevant_accounts(tx)
    cfg = configure_evm(chainspec, block, db)   // sets precompiles, gas schedule
    result = revm.transact(cfg, tx)
    apply_l1_cost(tx, result, db)               // L2-specific
    state.commit(result.state_changes)
    receipts.push(result.receipt)
return post_state_root(state), receipts

For Ethereum mainnet, lines 3 and 9 disappear. Everything else is identical. That's the whole point of the extension model.

6. What Tempo's executor actually does

Tempo is an L1, so:

  • No "deposit tx" concept (no parent chain to be deposited from)
  • No L1 cost charge

What tempoxyz/tempo shows instead:

  • Payment precompiles in crates/precompiles/: TIP-20 tokens (tip20, tip20_factory), the TIP-403 policy registry (tip403_registry), the fee manager (tip_fee_manager), a stablecoin DEX (stablecoin_dex), plus nonce, account-keychain and validator-config precompiles. There is no FX-rate oracle or settlement-attestation precompile — the README lists on-chain FX as "coming soon".
  • A wrapping block executor in crates/evm/src/block.rs: it wraps EthBlockExecutor and adds section-based tx ordering (BlockSection), start- and end-of-block system transactions, and shared/non-shared gas accounting.
  • A different fee market: users pay gas in USD stablecoins, and the fee manager + Fee AMM convert to the validator's preferred stablecoin (README).

Read block.rs against the OP executor above: same trait slots, different chain rules.

7. Practice

In op-reth's rust/op-reth/crates/evm/ (and rust/op-revm/ for precompiles):

  1. Find the ConfigureEvm impl for OP (OpEvmConfig)
  2. List how OP's precompile set differs from Ethereum's at each OP hardfork
  3. Find the function that adds the L1 cost charge
  4. Trace how a deposit transaction bypasses signature verification

Final check: name the two things that must be in the executor (not in a precompile, not in the mempool) for a Reth-based chain, and explain why each must live there. If you can't, re-read sections 4 and 5.

Summary (3 lines)

  • Custom Executor = override Reth's tx-processing. Trait impl + NodeBuilder wiring.
  • Common customisations: OP L1 fee, Tempo payment precompiles (TIP-20 / TIP-403 / fee manager), Berachain PoL.
  • Composes with custom Pool + Consensus. Production: all Reth-based L1/L2s ship custom executors.