Lesson 14 — Parallel execution — beyond the serial interpreter loop
Question
Can revm execute multiple transactions in parallel? Yes — via block-stm optimistic concurrency. Solana does it differently (deterministic conflict-detection). Each has trade-offs.
Principle (minimum model)
- Serial baseline. Revm's default: execute tx-by-tx in order. Easy to reason about; cannot use multiple cores.
- block-stm pattern. Execute multiple txs optimistically; track read/write sets; if a conflict arises, re-execute the conflicting tx. Used by Aptos / Move.
- Conflict detection. Each tx has a
read_set+write_set. If tx_a writes a slot tx_b read, tx_b must re-execute with the new value. - Re-execution. Track per-tx state version; on conflict, roll back tx_b's state changes and retry.
- block-stm in revm.
revm_parallelcrate wraps the default revm; adds the conflict-detection layer. Optional; opt-in. - Solana's deterministic parallelism. Solana requires tx to pre-declare its account locks. No conflict possible at runtime; pure parallelism. Trade-off: harder DX.
- Block-stm vs deterministic. Block-stm = easier DX (no lock declarations) but extra runtime cost on conflict. Deterministic = harder DX but predictable cost.
- Production examples. Aptos uses block-stm. Solana uses deterministic. Some Ethereum L2s experiment with block-stm (e.g. Monad).
- Speedup. Up to 4× on conflict-light workloads. Lower on heavy contention. Depends on workload.
Worked example + steps
Parallel execution — beyond the serial interpreter loop
Every Inside Revm lesson so far has treated execution as serial: one transaction at a time, through the interpreter, then commit, then next. This is how Reth and revm ship today and how mainnet has worked for a decade. It is also the single biggest performance ceiling on the EVM, and a list of teams — Sei, Monad, MegaETH, Aptos (origin of the technique) — have shipped or are shipping parallel EVMs. Reth itself has experimental parallel-execution paths in the source tree. This lesson teaches you the model.
📌 Honest framing. Parallel EVM is a moving target. The specific implementations differ across Sei, Monad, MegaETH, and Reth's experimental work. What stays constant — and what this lesson teaches — is the block-stm pattern (optimistic concurrency control with read/write conflict detection), plus how it maps onto the
Databasetrait you've already walked.
1. Why serial execution is the ceiling
A block contains N transactions. The interpreter executes them one at a time because each tx might read state another tx wrote. If tx 5 reads slot X and tx 3 wrote slot X, you have to run 3 before 5.
Empirically: roughly 10–20% of mainnet transactions touch state another tx in the same block also touches. The rest are independent — DEX swaps on different pools, transfers to different accounts, oracle updates that nobody else reads in that block. 80% of the block is being executed serially for no semantic reason. That gap is the prize parallel EVM goes for.
2. The block-stm pattern (the dominant approach)
Block-stm (block-level Software Transactional Memory) was introduced by Aptos for Move and ported to EVM by Sei, then adapted by Monad, MegaETH, and others. The core idea:
1. Speculatively execute all N transactions in parallel (multiple workers).
2. While each tx runs, record what it reads (read set) and what it writes (write set).
3. After execution, validate in commit order:
- If tx i's read set overlaps any earlier tx j's write set (j < i, j committed after i started),
i used stale data → re-execute i.
4. Repeat validation + re-execution until all N transactions are valid in serial order.
This is optimistic concurrency control: assume conflicts are rare, run in parallel, fix the ones that actually conflict. The 80% non-conflicting case runs once in parallel. The 20% conflicting case re-runs serially. Net throughput: 3–8× depending on workload.
The 90 independent ones execute in parallel and commit cleanly on first pass. The 10 sandwich-arb ones all read and write the same pool state — they get speculatively executed in parallel, then 9 of them detect they used stale read sets and re-execute. After ~2 re-execution waves the 10 commit serially. Total wall-clock: ~1 parallel pass + 2 small re-execution waves, instead of 100 serial steps. The conflicts cost you, but only proportionally.
🛣️ The road not taken (Solana): Solana solves the same throughput problem with the opposite bet. Every Solana transaction declares its read / write account set upfront, encoded in the tx message itself. The runtime (Banking Stage scheduler) then groups non-overlapping tx and runs them in parallel with zero speculation and zero retry — you never execute a tx whose dependencies are wrong, because the dependencies are known statically before execution. EVM's block-stm takes the opposite bet: keep the developer model permissive (no declared lock sets, contracts can touch arbitrary storage), and pay for it at the runtime layer with conflict tracking + re-execution. Two valid SE answers to the same problem; the choice between them is a tradeoff between developer ergonomics and runtime cost, not "right vs wrong."
3. Where this lives in the Database trait
Look back at the Database trait you walked in the Database chain:
pub trait Database {
type Error: DBErrorMarker;
fn basic(&mut self, address: Address) -> Result<Option<AccountInfo>, Self::Error>;
fn code_by_hash(&mut self, code_hash: B256) -> Result<Bytecode, Self::Error>;
fn storage(&mut self, address: Address, key: StorageKey) -> Result<StorageValue, Self::Error>;
fn block_hash(&mut self, number: u64) -> Result<B256, Self::Error>;
}
In serial execution, the executor just calls these and gets values. In parallel execution, every basic / storage call is also a read-set entry, and every state mutation is a write-set entry. The block-stm scheduler wraps the standard Database with a tracking layer:
pub struct TrackedDatabase<D: Database> {
inner: D,
read_set: HashSet<(Address, StorageKey)>, // populated by storage()
read_accounts: HashSet<Address>, // populated by basic()
// write_set lives in the executor's journaling layer
}
impl<D: Database> Database for TrackedDatabase<D> {
type Error = D::Error;
fn storage(&mut self, addr: Address, key: StorageKey) -> Result<StorageValue, Self::Error> {
self.read_set.insert((addr, key)); // ← record the read
self.inner.storage(addr, key) // ← delegate to real source
}
// ... basic(), code_by_hash(), block_hash() similarly track
}
This is why the Database trait shape pays off so heavily here. The lesson that walked it didn't say "this is also the seam where parallel execution plugs in," but it is. Any code that depends on Database works in parallel as long as the wrapper tracks reads; no executor or interpreter rewrite required.
🔍 Find in repo. Search
bluealloy/revmforparallelorstm. There's experimental code (often in a feature flag or separate crate) that does exactly this tracking-wrapper pattern. Read 50 lines of it. Notice how it composes with everything else you've walked.
4. The hard parts
Block-stm sounds easy in the abstract; the production complexity lives in:
| Problem | What makes it hard |
|---|---|
| Write set propagation | tx i's writes need to be visible to tx j>i when re-executing j, but not to tx k that hasn't committed yet. Requires a versioned-storage layer (think MVCC). |
| Re-execution ordering | After invalidation, re-execute in dependency order. A naive "re-run failed ones" loop can thrash if you re-execute in the wrong order. |
| Estimating read/write sets cheaply | A tx's read set is only known after it executes. Pre-execution prediction (via static analysis or hints) is a research area. |
| Hot contracts | All sandwich txs on the same pool form an unavoidable serialization. block-stm doesn't help; it just doesn't make it worse. The fix is at the application layer (multiple pools, AMM redesign). |
| Gas accounting | Parallel execution costs more gas total (re-executions) than serial. Who pays? Schemes differ. |
| Determinism | Multiple workers executing in parallel must produce the exact same state root every time. Race conditions in the tracker = consensus bug = chain split. |
The last row is the consensus-critical one. Parallel EVM's bug surface is huge compared to serial; this is why production rollouts have been cautious.
5. What different chains actually do
- Sei v2 (the first production EVM with block-stm): runs parallel by default. Their fork of revm includes the tracking-wrapper pattern from §3.
- Monad: parallel execution from day one. Custom interpreter (not revm), but the read/write-set tracking model is similar.
- MegaETH: parallel execution targeted, plus other optimizations (in-memory state, custom consensus).
- Reth: experimental. The mainline interpreter is serial; parallel execution work exists in branches and crates (Compass is one such effort) but is not production default.
- Mainnet Ethereum: serial. Parallel execution at the protocol layer is a long-running discussion (EIP-7960 family) but not active.
Mainnet's conservatism is about consensus risk and backward compatibility. A bug in parallel execution affects every node simultaneously and could split the chain — and mainnet has $400B+ on it. New chains can ship aggressive parallel execution because their TVL starts low and they iterate fast. The same code, same risk profile, different cost of failure. This is why new L1s eat mainnet's lunch on raw throughput — they can take the bet mainnet can't.
6. What this means for your career
Parallel EVM is the active research and engineering frontier in 2026. Teams shipping it (Sei, Monad, MegaETH) are hiring aggressively for engineers who can read revm's tracking-wrapper, understand block-stm, and contribute to either the scheduler or the conflict-detection layer. None of this work is possible without the Inside Revm + Inside Reth foundation you have now.
The path forward, if you want to make this your specialization:
- Read Sei's revm fork for the production block-stm pattern (open source)
- Read Aptos's original block-stm paper for the theoretical foundation
- Build a toy parallel executor against revm using the
Databasetracking pattern from §3 (a weekend project for someone with this curriculum's foundations) - Contribute to Reth's experimental parallel work or apply to Sei / Monad / MegaETH
Drill
- Read a real block-stm implementation. Open Sei's
sei-protocol/revmfork (or their parallel-EVM crate). Find the tracking-wrapper struct. Map its methods to theDatabasetrait you walked. 30 minutes. - Identify a parallelization-friendly block. Open Etherscan, pick a recent mainnet block. Skim the tx list. Count how many touch unique contracts (independent) vs how many touch the top 3 contracts (likely conflicting). What's your estimate of the parallel speedup ceiling for that block? 30 minutes.
- Read the original block-stm paper. arxiv:2203.06871. Skim sections 1, 3, 4. The Aptos model is for Move, but section 3's optimistic execution + validation diagram is the universal pattern. 45 minutes.
- Sketch your own tracking wrapper. In your fork of revm, write a
TrackedDbthat implementsDatabaseand prints every read. Run a small bytecode through it (likePUSH1 0x42 PUSH1 0x00 SSTORE STOPfrom earlier drills). See exactly which reads happen. This is the data block-stm uses to decide what conflicts. 1.5 hours.
Why this matters before the next lesson
The next lesson covers JIT/AOT compilation via revmc. Parallel and JIT are the two "beyond interpretation" frontiers — they attack the same throughput ceiling from different angles (parallel: more cores, JIT: faster cores). Production high-performance EVMs (Sei, Monad, MegaETH) are starting to combine both. After both lessons you have the vocabulary to read either path's source and understand what trade-offs each team picked.
📺 Further reading
- Aptos block-stm paper (arxiv:2203.06871) — the original
- Sei's parallel EVM technical post — production case study
- Monad's blog — different architecture, similar principles
Summary (3 lines)
- Parallel execution = block-stm optimistic (track read/write sets; re-execute on conflict) or Solana-style deterministic (pre-declared locks).
- revm has
revm_parallelcrate for block-stm. Opt-in; up to 4× speedup on conflict-light workloads. - Solana = deterministic = harder DX, predictable cost. Aptos / Monad pioneer block-stm in EVM space. Next: JIT/AOT.