Skip to content

Revision: Accounts, State & the World Computer

Six pages ago this part opened with a promise from Foundations: Ethereum is not a ledger that moves coins, it is a world computer — a shared machine whose current state every stranger agrees on. This part made the word “state” concrete. Before an opcode runs, before a gas unit is priced, before a hash is taken over a trie, there must be a thing being computed on, and this part built that thing from the ground up: a record, a map of records, and a rule for advancing the map.

This page is not new material. It is the part re-walked at altitude, in prose, so the five ideas snap into one shape you can carry into the rest of the book. The book’s question — how do untrusting strangers agree on the state of a shared world computer, and what does it cost to run one? — has two clauses, and this part answered the first while quietly running up the bill on the second. Both threads are pulled together at the end.

The central shift: from a set of coins to a map of accounts

Section titled “The central shift: from a set of coins to a map of accounts”

Start where Two Ledgers started: with the thing Ethereum deliberately stopped doing.

Bitcoin keeps no balances. Its entire state is the UTXO set — a pile of discrete, unspent coins, each spendable exactly once. A transaction consumes some coins and creates new ones; the old coins vanish. There is no row anywhere that reads alice: 50. Her balance is derived: the sum of the coins her keys can currently unlock. This is beautiful for money and useless for a computer, because a computer needs somewhere to remember things between operations — and a UTXO is engineered to be forgotten the instant it is spent.

Ethereum flips the data structure. Its state is a single global map, one entry per account, with a stored balance you increment and decrement in place.

BITCOIN ETHEREUM
─────── ────────
a SET of coins a MAP of address → account
spend = destroy coins, transfer = edit two rows
create new coins in place
balance is DERIVED balance is STORED
(sum the coins you unlock) (a number in your account row)
no per-program memory every account carries
(coins are forgotten) persistent storage (it remembers)

That move — from set to map, from derived to stored, from forgotten to remembered — is the whole part in one line. A map with mutable, persistent values is exactly the substrate a program needs: a token whose balances change, a market whose order book evolves, a vote whose tally grows. The account model is not a stylistic preference over UTXOs; it is the minimum viable substrate for programmability. You cannot write a program on data that is designed to disappear.

And the moment state becomes both shared and mutable, you inherit hard problems that stateless coins never posed: ordering, replay, determinism. The rest of the part — and much of the rest of the book — is the bill for that expressiveness.

The two kinds of account, and the one rule that separates them

Section titled “The two kinds of account, and the one rule that separates them”

Given a map of accounts, Two Kinds of Account asked the next question: what is an account? Ethereum’s answer is that there are exactly two kinds, and they live in the same map with the same four-field shape.

  • An Externally Owned Account (EOA) is controlled by a private key. It is a wallet. Nothing runs when you send it value; it just holds a balance and a nonce. Every transaction on the chain originates from an EOA, because only a key can produce a signature.
  • A contract account is controlled by code. When a transaction calls it, its code runs, reads and writes its own storage, and can call other accounts in turn. It has no key and cannot start a transaction on its own; it only ever acts because something called it.

The distinguishing rule is a single bit: does this account hold code? In the companion crate that is one method, and it is the entire definition:

/// Does this account hold contract code? If so, a call to it *runs* that
/// code; if not, a call is just a value transfer.
pub fn is_contract(&self) -> bool {
!self.code.is_empty()
}

Empty code means a wallet; non-empty code means a program. That one bit is the door between “a ledger of balances” and “a world computer.” It is why the account model can host programs at all: a contract is just an account whose code field is not empty, sitting in the very same map as every wallet, reachable by the same address mechanism, advanced by the same state-transition rule.

If a contract is just an account with code, Inside an Account asked what an account record actually contains. The answer is four fields, and the entire world-computer idea is hiding in which four:

address ──▶ ┌───────────────────────────────┐
│ nonce (txs sent so far) │
│ balance (wei held) │
│ storageRoot (contract memory) │
│ codeHash (contract code) │
└───────────────────────────────┘

Each field earns its place, and each contributes something specific to a programmable, replay-protected account:

  • nonce — a per-account counter, bumped once per transaction sent from this account. It is the chain’s replay protection: a transaction is only valid if its claimed nonce equals the sender’s current nonce, so re-broadcasting a signed transaction a second time names a stale nonce and is rejected. It also seeds the address of the next contract this account creates, which is why a deploy is deterministic.
  • balance — the native currency the account holds, counted in the smallest unit (wei; 1 ether = 10¹⁸ wei). This is the stored number that replaces Bitcoin’s derived sum — the thing a transfer edits in place.
  • storageRoot — a commitment to the account’s persistent key/value memory. This is the field that lets a contract remember between calls; it is where a token’s balances and a market’s order book actually live. For an EOA it commits to empty storage.
  • codeHash — a commitment to the account’s bytecode. Empty for an EOA, non-empty for a contract. This is the field the “one bit” of the previous section reads.

The last two fields are stored as hashes rather than the raw storage and code, and that is not an accident. A hash is a fixed-size fingerprint of an arbitrary-size thing, so an account record stays a small, uniform four-field row no matter how much storage a contract accumulates or how large its code is. The heavy data lives elsewhere, keyed by its hash; the account just points at it. That design is what makes the next idea — one root over the whole world — tractable.

The World State assembled every account record into a single structure: the map address → account. That map is the world state — the complete, agreed-upon snapshot of the machine at a point in time. In the companion crate it is exactly that, one line of intent:

/// The entire ledger: every account, keyed by address.
pub struct WorldState {
pub accounts: HashMap<Address, Account>,
}

But a map on your machine and a map on mine are only the same state if we can prove it cheaply. That is the job of the state root: a single hash computed over the entire world state, serialised in a fixed, canonical order. It is one 32-byte fingerprint of every account, balance, nonce, storage slot, and byte of code. Change anything and the root changes; change nothing and it stays byte-for-byte identical.

Byte-for-byte agreement on that one number is the entire point. Two nodes that computed the same root from the same transactions have proven, in 32 bytes, that they agree on the value of every account without exchanging the accounts themselves. That is how untrusting strangers check each other: not by trusting a report of the state, but by independently recomputing a root and comparing hashes. If the roots match, the states match; if they differ, someone diverged.

The canonical order is not a detail — it is the whole trick. A hash map’s iteration order is unspecified, so two nodes hashing the “same” map in different orders would get different bytes and different roots, and agreement would be impossible. The commitment only works if the serialisation is a function of the state and nothing else:

/// The state root: a hash over the whole world state, in a *fixed, sorted*
/// order. Change anything and the root changes; change nothing and it stays
/// identical — which is exactly what lets strangers compare states in 32 bytes.
pub fn state_root(&self) -> [u8; 32] {
let mut sorted: Vec<(&Address, &Account)> = self.accounts.iter().collect();
sorted.sort_by_key(|(addr, _)| **addr); // canonical order first
// ...serialise the sorted pairs, then hash.
}

The companion crate sorts-and-hashes a flat map; real Ethereum uses a Merkle–Patricia trie, which buys the same canonical-commitment property plus efficient proofs and cheap incremental updates. That machinery is the subject of the State & Tries part. The idea to carry forward from here is simpler and older than any trie: the state has a single root, and agreeing on the root is agreeing on the state.

The state transition: a deterministic rule, ordered by consensus

Section titled “The state transition: a deterministic rule, ordered by consensus”

The map is a noun. State Transitions supplied the verb — the rule that turns one world state into the next when a transaction is applied. Formally, a blockchain is a state machine:

stateₙ ──apply(tx)──▶ stateₙ₊₁
│ │
state_root state_root' (a hash that changes iff the state did)

Three properties of that rule are the load-bearing ideas of the whole part.

apply is deterministic. Given the same starting state and the same transaction, it always produces the same next state — and therefore the same next root. There is no randomness, no wall-clock, no “it depends.” This is what lets thousands of independent machines replay the same transactions and land on byte-identical states. Determinism is the mechanism behind the agreement the state root checks.

Consensus only orders transactions. The state-transition function does not decide which transactions run or in what sequence — that is the job of consensus and the block-production machinery in later parts. All the transition function needs is an agreed ordered list of transactions. Feed the same list, in the same order, to the same starting state, and every node computes the same result. Ordering is a hard, expensive, adversarial problem; the transition itself is a plain, deterministic function. Keeping those two concerns separate is what makes the system tractable.

Atomicity and the nonce keep the shared mutable state consistent. A transaction either fully applies or does not move the world at all — there is no half-applied state where value left a sender but never reached a recipient. And the nonce is the guard that makes ordering within one account unambiguous and non-replayable. The companion crate makes both concrete: validation failures are rejections that leave the world untouched, while a valid-but-reverting contract call still consumes its nonce so it can never be replayed.

pub fn apply(&mut self, tx: &Transaction) -> Result<Receipt> {
// ---- validate: any failure here rejects and leaves state untouched ----
let sender = self.accounts.get(&tx.from).ok_or(EthError::UnknownSender(tx.from))?;
if sender.nonce != tx.nonce { // replay / ordering guard
return Err(EthError::NonceMismatch { /* ... */ });
}
if sender.balance < tx.kind.value() { // affordability guard
return Err(EthError::InsufficientBalance { /* ... */ });
}
let snapshot = self.accounts.clone(); // atomicity: undo point
self.account_mut(tx.from).nonce += 1; // consumed no matter what happens next
// ...on a revert, roll back to `snapshot` but keep the nonce bump.
}

Read that top to bottom and you have the discipline the shared mutable state demands. A wrong nonce is a rejection (the world does not move). An unaffordable value is a rejection. A valid transaction that reverts is included — its nonce is spent, its other effects undone. Three outcomes, one deterministic rule, and every node walks them identically.

Under the hood — why a reverting transaction still costs the sender

Section titled “Under the hood — why a reverting transaction still costs the sender”

It is tempting to think a failed transaction should be as if it never happened. It is not, and the reason is the nonce. If a reverting transaction released its nonce, an attacker could resubmit it forever and clog the chain for free, or a transaction that “failed” could be replayed later against a different state. So the transition takes a snapshot, bumps the nonce before executing, and — on a revert — rolls the world back to the snapshot but re-applies the nonce bump. The state changes vanish; the nonce advance survives. That asymmetry is not a quirk; it is the thing that makes the ordered, one-shot-per-nonce guarantee hold even when contracts throw. (In real Ethereum this is also where gas gets spent regardless of success — the topic of the gas part.)

Three pictures, top to bottom, are the entire part:

address ──▶ [ nonce | balance | storageRoot | codeHash ] ← the RECORD
{ addr₁ → account, addr₂ → account, … } ──hash──▶ state_root
← the MAP + ROOT
stateₙ ──apply(tx)──▶ stateₙ₊₁ ← the TRANSITION

A record, a map of records committed to by a single root, and a deterministic rule for advancing the map. Get those three right and every stranger — starting from the same state and replaying the same ordered transactions — computes the identical next state and the identical next root. That is agreement, and it is the foundation the rest of the book is built on.

The book’s question has a second clause: what does it cost to run one? This part answered the first clause and, in doing so, ran up three bills that the account model’s expressiveness demands. Name them now, because the later parts are where they come due.

  • Mandatory ordering. Because balances are shared mutable rows edited in place, transactions from one account must run in a fixed sequence, policed by the nonce. Bitcoin’s independent coins could, in principle, be validated in any order; Ethereum’s account edits cannot. Expressiveness is paid for in lost parallelism — the trade Two Ledgers drew out first.
  • Ever-growing state. A UTXO is spent and forgotten; an account row, once created, tends to persist forever, and contract storage only accumulates. Every node must hold the entire, growing map to compute the root. That relentless growth is a first-order cost of a state that remembers — and the pressure behind statelessness and state-expiry research on the roadmap.
  • A linkable identity. A stable address → account mapping is what makes programmability possible, but it also means every action an account takes is threaded onto one persistent, publicly linkable identity — the privacy cost of a map, where Bitcoin’s fresh-coins-per-spend model offered a weak default of unlinkability.

Those three costs — ordering, growth, and identity — are the price of the world computer, paid in exchange for programmability. The EVM part shows what runs inside a transition; the gas part shows how each of these costs is metered and charged, turning “state is not free” from a slogan into a number attached to every operation. This part gave both of them their subject: a state worth computing on, and a rule worth pricing.