Why the EVM Runs Serially
The part overview promised that Sealevel does not add parallelism so much as reveal it. Before we can believe that, we have to see the ceiling it breaks. This page is that ceiling: the machine Sealevel reacts against, examined closely enough that its serialization looks not like a missing optimization but like the only correct thing it could possibly do.
The claim is strong and worth stating plainly. Ethereum’s virtual machine executes transactions one at a time, in the order they were included, by necessity — not by accident, not for lack of engineering effort. No amount of cleverness inside the EVM can safely parallelize a block, because the EVM cannot know what a transaction will touch until it has already run it. That single fact is the whole ceiling. Sealevel does not out-engineer the EVM; it changes the one precondition that makes the EVM’s hands tied.
The world as one state-transition function
Section titled “The world as one state-transition function”Start from what a blockchain fundamentally is — the book’s throughline: a single global state machine. Ethereum makes this literal. There is one object, the world state: a mapping from every account address to that account’s state (its balance, nonce, code, and storage). A block is an ordered list of transactions, and processing the block means folding each transaction into the world state, in order:
world state Sₙ = a mapping { address → account } account = { balance, nonce, codeHash, storageRoot }
applying a block: S₀ ──tx₀──► S₁ ──tx₁──► S₂ ──tx₂──► S₃ ── … ──► Sₙ (fold each transaction into the state, strictly left to right)This is not a metaphor; it is close to the actual definition in Ethereum’s specification. The state
transition function Υ (upsilon) takes a state and a transaction and produces the next state:
S' = Υ(S, tx). A block is just Υ applied repeatedly, and the order matters — Υ(Υ(S, txA), txB)
is generally not Υ(Υ(S, txB), txA) when the two transactions touch overlapping state. If Alice has 10
ETH and both txA and txB try to send 8 ETH from her, whichever runs first succeeds and the other
fails for insufficient funds. Reorder them and a different transaction fails. Order is part of the
answer, so order must be fixed and honored.
So far this describes a rule, not a limitation. Lots of ordered computations still parallelize — you can add up a list in any grouping if addition is associative. The question is not “does order matter?” but “can the machine tell, ahead of time, which pairs of transactions actually interact?” If it could, it could run the non-interacting ones concurrently and only serialize the ones that truly depend on each other. Everything hinges on that word: ahead of time.
Hidden storage: state lives with the contract
Section titled “Hidden storage: state lives with the contract”Here is where Ethereum’s answer becomes “no.” Recall the account model this book already built (see Contract-Owned Storage vs External Accounts): in Ethereum, a contract account holds both its code and its own storage. State is welded to the contract. A contract’s storage is a private key-value map — 2²⁵⁶ slots — reachable only through that contract’s own code.
┌───────────────────────────────────┐ │ Contract account 0xDEX… │ │ ┌──────────────┐ │ │ │ code (EVM) │ ← runs to decide what it touches │ ├──────────────┤ │ │ │ storageRoot ─┼─► trie { │ │ │ │ slot 0: … │ ← which slots? │ │ │ slot k: … │ only the CODE knows, │ │ │ … │ and only once it RUNS │ └──────────────┘ } │ └───────────────────────────────────┘The runtime — the EVM itself — is deliberately blind to what is inside that storage trie and to which slots any given call will read or write. That blindness is not a bug; it is the natural consequence of the design decision to put state with the contract. The contract’s storage is the contract’s private business, and “which slots do I touch this time?” is answered by running the contract’s code with this transaction’s inputs against the current state.
Consider a token transfer on a typical ERC-20 contract:
// The slots touched depend on `to`, on `msg.sender`, and on runtime state.function transfer(address to, uint256 amount) public returns (bool) { balances[msg.sender] -= amount; // slot = hash(msg.sender, balancesBase) balances[to] += amount; // slot = hash(to, balancesBase) emit Transfer(msg.sender, to, amount); return true;}Which storage slots does this touch? Two slots in the balances mapping — but which two is computed
from msg.sender and the to argument by hashing them into slot addresses. You do not know to as a
storage slot until you have the transaction, and you do not know whether the branch even runs (a
require, a re-entrant call, a proxy delegatecall to swappable logic) until you execute it. The
footprint is a function of execution, and execution is the only oracle for it.
Now scale that up. transfer is the tame case. A real transaction calls a router, which calls a pool,
which calls a token, which calls a fee handler — each hop a separate contract with its own hidden
storage, each hop’s choice of next contract possibly data-dependent. The set of accounts and slots the
whole transaction will touch is spread across contracts the EVM cannot enumerate without walking the call
graph, and it cannot walk the call graph without running the code.
tx → contract A (touches A's slots — which ones? run it) └─► calls B (B's slots — run it) └─► calls C, but WHICH C depends on B's state (run it) └─► reads slot X
The transaction's true footprint = ⋃ of all of these, and every set in that union is only known AFTER the call that produces it.Unknown footprint forces serialization
Section titled “Unknown footprint forces serialization”Now assemble the argument. It is short, and once you see it you cannot unsee it.
A runtime may run two transactions concurrently only if it can prove they are disjoint — that no account or storage slot written by one is read or written by the other. That is the fundamental safety condition; violate it and you get a data race on shared state, and the final world state depends on thread timing rather than on the block’s defined order. A blockchain that produced timing-dependent state would not be a state machine at all.
To prove two transactions are disjoint, you need their footprints. On Ethereum, the only way to learn a transaction’s footprint is to execute it. But executing it is the very thing you were trying to decide whether it’s safe to do concurrently. You are stuck in a loop:
Want: run tx_i and tx_j in parallel (only safe if their footprints are disjoint) Need: footprint(tx_i) and footprint(tx_j) But: footprint(tx) is only known AFTER executing tx ⇒ you must execute to learn what you needed to know BEFORE executing → you cannot prove disjointness in advance → you cannot safely parallelize → run everything one at a time, in order. ∎When you cannot prove two transactions are disjoint, the only safe assumption is that they might collide, and the only safe schedule under that assumption is total serialization: run them strictly in order, each seeing the full effect of every transaction before it. That is exactly what the EVM does. Its serialism is forced by the unknowability of the footprint, which is in turn forced by state living inside contracts. The runtime is blind to the state (it lives with the contract), so it must be conservative (assume everything conflicts), so it must serialize. Three sentences, each implying the next.
Notice what the EVM is not doing wrong. It is not being lazy or under-optimized. Given a world where footprints are unknowable in advance, one-transaction-at-a-time is the correct algorithm — the unique schedule that is safe without any information about what conflicts with what. You cannot optimize your way out of a missing input. The EVM is optimal for the model it was handed; the model is the ceiling.
Under the hood — why “just try it in parallel and re-run on conflict” is a different bet
Section titled “Under the hood — why “just try it in parallel and re-run on conflict” is a different bet”A fair objection: databases run transactions concurrently all the time without knowing footprints up front. They use optimistic concurrency control — run everything in parallel against snapshots, detect after the fact whether any two transactions actually conflicted, and abort and retry the losers. Why can’t the EVM just do that?
It can, and this is a real and active line of research (often called parallel EVM or optimistic / speculative execution, e.g. Block-STM–style schedulers used by some chains). But look carefully at what it changes and what it costs:
- It does not make footprints knowable — it gambles that conflicts are rare. You execute speculatively, track the slots each transaction touched as it runs, then validate: if a transaction read a slot that an earlier-in-order transaction wrote, you throw its work away and re-execute it. The footprint is still only learned by running; you have just moved the discovery after a speculative run instead of before.
- Worst case is worse than serial. On a contended workload — everyone hitting the same hot contract — optimistic execution can abort and re-run repeatedly, doing the serial work plus the wasted speculative work. You pay for the guess when it’s wrong.
- It bolts complexity onto an unchanged model. State still lives inside contracts; the runtime still can’t see footprints in advance. The scheduler compensates for that blindness with snapshots, version tracking, and rollback machinery.
Sealevel makes the opposite bet. Instead of guessing footprints and paying to fix wrong guesses, it requires them — the transaction declares its footprint before it runs, so the scheduler never guesses and never re-runs. That is the pivot the rest of this part builds: not a better scheduler for a blind runtime, but a runtime that isn’t blind. Both are legitimate engineering; they differ in where the footprint information comes from — recovered after the fact, or supplied up front.
The pivot: parallelism is revealed, not added
Section titled “The pivot: parallelism is revealed, not added”Stand back and name the exact thing that traps the EVM. It is not that Ethereum transactions genuinely all conflict — most of them don’t. Alice paying Bob and Carol paying Dave are as independent as two transactions can be. The parallelism is there in the workload, sitting unused. What’s missing is not independence; it’s visibility — the runtime cannot see the independence, because the footprints that would reveal it are hidden inside contract storage until execution.
That reframes the whole problem, and it is the single idea this part is built on:
The EVM’s serialism is not a property of blockchain transactions. It is a property of hidden footprints. Make the footprint visible before execution, and the independence that was always there becomes something the scheduler can act on.
Parallelism, then, is not a feature you add to a runtime by making it cleverer. It is a fact about the workload that you reveal by changing where state lives and requiring transactions to declare what they touch. Everything from here does exactly that. The next page shows Solana’s mechanism — accounts declared up front with read/write flags — which is nothing more than “make the footprint visible before execution,” turned into a concrete transaction format. Then the conflict rule states precisely when two visible footprints may run together, scheduling sorts a whole block by that rule without running anything, and parallel execution finally spends the idle cores. The ceiling proved here is the exact height the rest of the part climbs.
Check your understanding
Section titled “Check your understanding”- Ethereum’s world state applies transactions “one at a time, in order.” Explain why order is load-bearing — give a concrete pair of transactions whose result changes if you swap them.
- What does it mean to say a transaction’s footprint is “unknowable in advance” on Ethereum, and which design decision about where state lives is the root cause?
- Reconstruct the argument from “unknown footprint” to “must serialize” in your own words. Which single safety condition is the runtime unable to check ahead of time?
- The EVM’s serial execution is often described as a limitation. In what precise sense is it actually the correct and even optimal algorithm — and what would have to change for a better one to exist?
- Restate the part’s central pivot: is Sealevel adding parallelism or revealing it? What has to become visible, and when, for the independence in a workload to be usable?
Show answers
- Order determines the result whenever two transactions touch overlapping state, because the state
transition function is not commutative there:
Υ(Υ(S, txA), txB) ≠ Υ(Υ(S, txB), txA). Concretely, if Alice holds 10 ETH and bothtxAandtxBsend 8 ETH from her, whichever runs first succeeds and the second fails for insufficient funds — swap them and a different transaction fails. So the block must define, and the runtime must honor, a fixed order. - It means the runtime cannot enumerate which accounts and storage slots the transaction will read or write until it actually executes it. The root cause is that state lives inside the contract (code and storage welded together): a contract’s storage is private, and which slots a call touches is computed by running the contract’s code against this transaction’s inputs and the current state — including data-dependent calls to other contracts, each with its own hidden storage.
- Two transactions can run concurrently only if you can prove they’re disjoint (no slot written by one is read or written by the other). Proving disjointness requires both footprints; on Ethereum a footprint is only known after executing the transaction — but executing is the very act you were deciding whether to do concurrently. So you can’t prove disjointness in advance; you must assume any two might collide; the only safe schedule under that assumption is total serialization. The uncheckable condition is disjointness of footprints.
- Given a model where footprints are unknowable before execution, one-transaction-at-a-time is the unique schedule that is safe with zero information about conflicts — so it is optimal for that model. You can’t optimize past a missing input; no scheduler cleverness recovers information the model never exposes. For a better algorithm to exist, the model must change so footprints become available before (or, in optimistic designs, are recovered and reconciled after) execution — which is precisely what Solana’s declared accounts do.
- It is revealing, not adding. The independence between transactions (Alice→Bob vs Carol→Dave) is already present in the workload; the EVM just can’t see it. What must become visible is each transaction’s footprint — the accounts it will touch, flagged read/write — and it must be visible before execution, so the scheduler can compare footprints and run the provably disjoint transactions in parallel without ever running them to find out.