Transferring 100 USDT (an ERC-20 token) on-chain does not add an entry saying “Alice paid Bob 100 USDT.” What actually happens is that the value in the contract's balanceOf mapping is overwritten: the sender decreases by 100, the receiver increases by 100. Which storage slots that rewrite ultimately lands in is decided by how the contract is written and by the compiler; the protocol constrains only what balanceOf returns. At the same time, an event log is appended to the transaction receipt. Logs are a byproduct consumed by off-chain indexers — the truth about balances lives only in the current values in storage.
The ledger analogy fails here. A ledger records what has already happened, and a reader sums up history to reach a conclusion; Ethereum holds a “current world” that is continually overwritten, and transactions only push it to the next version. To answer “what the EVM actually is,” we first have to define this “current world” clearly, and only then talk about who is responsible for rewriting it.
Bitcoin's ledger model works because its state structure is extremely limited
“Ledger” is an accurate description of Bitcoin. A transaction's inputs reference outputs of earlier transactions, and an output stays an unspent transaction output (UTXO) until a later input spends it. A UTXO can be spent only once, and the balance a wallet shows is really a sum of several UTXOs.
Bitcoin is not without state — the UTXO set is state. But that state has a very restricted structure. It is a pile of “coins” that can only be created whole and spent whole; there is no general-purpose key-value store, and scripts hold no variables that persist across transactions. “A ledger plus a set of unspent outputs” is therefore enough to describe the whole system: validating a transaction only requires checking that the referenced output exists and that the spending conditions are satisfied, with no need to query any “contract-internal variable.”
For Ethereum to support a contract holding a mapping that can be read and written at will, that mapping can no longer be treated as an implementation detail. Whether an ERC-20 transfer is valid depends on the current value of the caller's balance slot in contract storage. State therefore graduates from “a set maintained incidentally during validation” to a first-class object the protocol has to commit to.
The world state: compressing “everything right now” into 32 bytes
The Ethereum Yellow Paper defines the world state as a mapping from addresses to accounts. Each account is a four-tuple:
- nonce: the number of transactions this address has sent, or the number of contracts this contract account has created;
- balance: the balance denominated in wei;
- storageRoot: the root hash of the Merkle Patricia Trie over that account's storage contents;
- codeHash: the Keccak-256 hash of that account's code, with the code itself stored in the state database under this hash.
Two things need distinguishing here. storageRoot commits to the account's own key-value space, which is where the state variables a contract author declares ultimately live; codeHash points at the program, which no transaction rewrites after deployment. Committing to “data” and “code” separately in two fields is a premise that comes up repeatedly when discussing read-write sets and conflict detection later (0.5 “Three Kinds of Storage, Don't Mix Them Up: Memory, Storage, and Transient Storage” in the same series covers the ownership and lifetime of each of the three storage kinds).
All accounts are then organized into one global Merkle Patricia Trie, whose root hash is the stateRoot written into the block header. In other words, “what all of Ethereum looks like right now” can be compressed into 32 bytes, and the whole network only needs to agree on those 32 bytes.
The chain stores the state root; the state itself stays on each node
There is a fact here that is often glossed over: the Yellow Paper states explicitly that the world state is not stored on the blockchain. Each node maintains its own copy, and the chain keeps only the stateRoot commitment. Transactions and receipts are published in full; the state is not.
This division of labor determines many practical limits. To find out an account's current balance, there are only two routes: replay the entire history of transactions yourself to compute the state, or ask a node for a Merkle proof you can verify under the stateRoot. Light clients take the second route. They do not need to store all state, but they need someone to supply proofs, and a proof can only say “what this value is under a given stateRoot,” not that this stateRoot is the latest one. The latter is the consensus layer's job.
The same reasoning explains the difference between archive nodes and pruned nodes. Historical state keeps accumulating, so the disk cost of a node that keeps all of it keeps growing; a node that retains only recent state takes up less space and starts faster, at the cost of being unable to answer state queries at arbitrary historical blocks. In engineering terms, “on-chain data” has to be split into two things: the availability of transactions and receipts, and the commitment that is the state root. Conflating them costs you the ability to tell them apart when you later assess data availability and state growth.
The state transition function: what Ethereum specifies is a function
The Yellow Paper defines transaction-level semantics in a single formula, written as:
σ_{t+1} ≡ Υ(σ_t, T)
Here σ is the world state, T is a transaction, and Υ is the state transition function. In an easier-to-read form, Y(S, T) → S'. The Yellow Paper immediately stresses that valid state changes can occur only through transactions, and that invalid state changes far outnumber valid ones — for example, reducing some account's balance out of thin air without an equal increase elsewhere.
Once that sinks in, it can be restated: what the Ethereum protocol really specifies is “given the current state and a transaction, what the next version of the state is”; as for “what is stored on-chain,” that is merely this function's input and output. The same holds at the block level, where the Yellow Paper folds the same function along a sequence of transactions using Π:
Π(σ, B) ≡ Υ(Υ(σ, T₀), T₁)…
Only here does the term state machine land: the state is σ, the transition rule is Υ, and a block is a batch of transitions.
Ledger and state machine models differ in validation cost
Put the two models side by side and the difference lands on how validation works, not just on how data is organized.
A UTXO transaction declares its inputs explicitly: each input states which output of which transaction it references. A validator only has to look up those specific entries in the UTXO set and need not understand any global structure. That makes Bitcoin transaction validation naturally local, so unrelated transactions can be processed independently. Declarative account models follow the same idea — for example, a Solana transaction message lists the account addresses in advance, and instructions reference them by index.
In the account model, a transaction's read-write set is not written into the transaction. A contract can read any storage slot at any address, and what it read is known only after execution finishes. A validator must hold a state view identical to the one used during execution, or the result cannot be reproduced. This difference is the starting point for an entire line of technical work that follows: read-write set inference, optimistic concurrency control, and conflict detection all deal with “not knowing what a transaction will touch.”
Ethereum has tried moving toward the declarative end too. The access list introduced by EIP-2930 is an optional field of the new transaction type 0x01: a transaction can declare in advance the addresses and storage slots it will access, adding them to the accessed_addresses and accessed_storage_keys sets to get a discount. The EIP text also states that accesses outside the list are still allowed, only more expensive. The read-write set is therefore not forced into the transaction, and the conclusion above is unchanged by it.
Where the EVM sits in the protocol: the specification that says “how state changes”
The Ethereum Virtual Machine (EVM) is the concrete execution specification for Υ: a set of instruction semantics, a set of gas metering rules, and a set of exception and termination semantics. Contract code is a string of bytes that enters execution only when triggered by a transaction or by another contract through a message call.
The EVM is not the Ethereum protocol. Consensus, data availability, and settlement are each a separate layer, and the scaling map article breaks this down specifically. The EVM answers only one thing: given a piece of bytecode and an input, how to deterministically compute the state changes and return values. It also decides what that computation costs, and gas pricing rules are themselves part of consensus — they can be changed by hard fork, as when EIP-2929 raised the price of first-time account and storage slot access significantly.
One more thing that is easy to confuse: the EVM's machine model is not only a “stack.” Every execution context has a stack, memory, a program counter (PC), and a gas counter, while a contract's persistent variables land in account storage through SLOAD/SSTORE. The stack, memory, and execution loop are the subject of 0.4; the boundaries among the three storage kinds are the subject of 0.5.
Network-wide replay: why results must match bit for bit
Ethereum has no central executor. Every full node executes the same batch of transactions independently, computes its own stateRoot, and then compares that value through consensus. Any disagreement over an execution detail immediately becomes a consensus failure, so the specification has to be precise down to the byte.
Several hard constraints follow. Floating-point arithmetic never made it into the instruction set; there is only arithmetic on 256-bit word integers. External inputs such as wall-clock time, process IDs, and true random numbers cannot appear on the consensus path; the timestamp and block number a contract can read come from the block header and are values fixed by consensus.
Computation must also be metered and guaranteed to terminate; otherwise arbitrary code cannot be run safely inside a finite block. Gas is the device that rewrites the “halting problem” as “stop when the budget runs out,” which is why its metering constants are consensus parameters rather than performance-tuning knobs. An ordinary transfer has an intrinsic cost of 21000 gas, each zero byte of calldata costs 4 gas and each non-zero byte costs 16 gas, and these numbers are fixed by the Yellow Paper's fee schedule. Changing them and changing the block size are the same class of protocol change: they require a hard fork or validator signaling and cannot be decided by a single client on its own.
Determinism has another consequence that is easy to overlook: the specification allows no undefined behavior. The Yellow Paper states that DIV returns 0 when the divisor is 0 rather than throwing. The reason for that choice is to make all clients reach the same result on the same illegal input; it has nothing to do with language design preferences. Client unit tests and the execution-spec-tests conformance suite exist to lock down boundary behaviors like this. The coexistence of multiple independent clients (geth, revm, evmone, and others) is itself a check on determinism: if one implementation understood an edge case differently, the chain would fork, so disagreements have to be found during testing.
Deterministic is not the same as predictable
Two levels of uncertainty need separating. The execution layer is deterministic: the same bytecode, the same input, and the same state necessarily produce the same result. What result a transaction gets, however, is not decided by the execution layer alone. Put the same transaction at different positions in a block and the preceding state differs, so the result can differ. The profit in front-running and sandwiching comes from ordering power; the execution layer itself is still deterministic.
The distinction matters when evaluating parallel execution designs. What a parallelism effort has to preserve is “the execution result is equivalent to some serial order”; as for “the result is independent of when the transaction was submitted,” that was never a goal that could hold.
The EVM is not a frozen specification
Treating the EVM as a fixed standard leads to compatibility misjudgments. The specification evolves with hard forks: PUSH0 (EIP-3855, Shanghai) added an instruction that pushes the constant 0 onto the stack machine; EIP-2929 repriced cold and warm access; the transaction layer introduced a 16,777,216 gas cap per transaction (EIP-7825, shipped with Fusaka); and EIP-7935 raised the default client block gas limit to 60M.
So the phrase “EVM-compatible” is meaningful only with a fork height and a specific scope attached. How far bytecode, precompiled contracts, JSON-RPC, and tooling each go in compatibility is the subject of the article in the further reading.
When this model becomes a burden
The state-centric design buys general-purpose computation, and the cost shows up in three places.
A full node has to keep a ready-to-read current state around indefinitely, and its size grows with the number of accounts and storage entries, steadily raising the hardware bar.
A new node that wants to compute the stateRoot for itself has to replay historical transactions or rely on snapshot sync, tying validation cost to the length of history.
Because every transaction can touch any part of the global state, classic implementations can only execute one transaction at a time, which is the original constraint that every later discussion of parallel execution has to deal with.
Conversely, the failure of the ledger analogy is not absolute. Auditing, indexing, and reconciliation still process data as an event stream, because the logs in receipts were designed for off-chain consumption in the first place. The only problem is not mistaking that perspective for the protocol model.
Sources
- Ethereum Yellow Paper (Upsilon, Pi, how the world state is stored, the account four-tuple, DIV semantics, the gas fee schedule): https://ethereum.github.io/yellowpaper/paper.pdf
- ethereum.org, Ethereum Virtual Machine (EVM): https://ethereum.org/developers/docs/evm/
- ethereum.org, Understanding the Yellow Paper's EVM Specifications: https://ethereum.org/developers/tutorials/yellow-paper-evm/
- Bitcoin Developer Guide, Transactions (a UTXO can be spent only once, inputs reference specific outputs): https://developer.bitcoin.org/devguide/transactions.html
- Solana Docs, Transactions (the transaction message lists account addresses in advance, and instructions reference them by index): https://solana.com/docs/core/transactions
- ethereum.org, Nodes and clients (full nodes, archive nodes, and state pruning; only archive nodes keep the entire historical state): https://ethereum.org/developers/docs/nodes-and-clients/
- EIP-2929, Gas cost increases for state access opcodes: https://eips.ethereum.org/EIPS/eip-2929
- EIP-2930, Optional access lists (an optional field of type 0x01 transactions; accesses outside the list are still allowed, only more expensive): https://eips.ethereum.org/EIPS/eip-2930
- EIP-3855, PUSH0 instruction: https://eips.ethereum.org/EIPS/eip-3855
- EIP-7825, Transaction Gas Limit Cap (16,777,216 gas): https://eips.ethereum.org/EIPS/eip-7825
- EIP-7935, Set default gas limit to 60M: https://eips.ethereum.org/EIPS/eip-7935
- Ethereum Foundation, Fusaka Mainnet Announcement: https://blog.ethereum.org/2025/11/06/fusaka-mainnet-announcement
- go-ethereum, core/vm/instructions.go (opDiv writes 0 when the divisor is 0): https://github.com/ethereum/go-ethereum/blob/master/core/vm/instructions.go
- ethereum/execution-spec-tests (the conformance test suite): https://github.com/ethereum/execution-spec-tests
