Part I · Chapter 1 of 43

You could have invented Ethereum

One page, one continuous build. Start with a single shared spreadsheet, animate the one thing wrong with it, fix exactly that, and repeat — until a programmable, staked world computer falls out as the only thing it could have become.

Updated Jun 21, 2026 · 40 min
Assumed
  • curiosity

Most explanations of Ethereum hand you the finished machine and name its parts. This one builds it. We start with the dumbest design that almost works, watch the single thing that breaks it, and fix exactly that — nothing more. Do it nine times and the famous pieces stop being trivia to memorize. Each one is just the answer to “what broke in the last step?”

Here is the analogy we’ll hold the whole way down. Forget coins and miners for a moment. Picture one spreadsheet that the entire world shares — a single sheet of who-owns-what that anyone can read and anyone can propose changes to. By the end, that spreadsheet will be able to run programs, no one will own it, and lying to it will cost you a fortune. That spreadsheet is Ethereum.

And it isn’t only animation. The moment a step invents a piece, you’ll drive the real one right there — the actual secp256k1 signer, a working EVM, a byte-exact Merkle-Patricia trie, the live EIP-1559 controller, a reorg simulator you can attack — and pick up the spec detail the real protocol had to pin down. Same incremental thread, intuition straight through to the running machine.

1 step The shared sheet

One spreadsheet for the whole world

Throw away the bank. In its place keep a single public spreadsheet: one row per account, one column for its balance. To pay someone, you submit an edit — “subtract 5 from my row, add 5 to Bob’s” — and everyone applies the same edit to their view. There is no statement to request and no teller to ask; the sheet is the truth, and the truth is just a pile of numbers that edits move around.

WORLD STATE 0xAlice14 Ξ 0xBob9 Ξ 0xCarol2 Ξ +5 Ξ → edits move numbers in one shared sheet
The state is a sheet of balances; a transaction is an edit that moves numbers between rows.

That’s the whole mental model of Ethereum’s state: not a history of payments, but the current value in every row, right now. It’s clean, it’s shared — and it’s wide open.

→ Step 2: make every edit something only its owner could have written.

2 step Signing edits

A seal only you can stamp

Give every account a secret. Not a password you show to log in — a private key A secret 256-bit number. Its address is derived from it one-way; only the key can produce a valid signature, but anyone can check one. that never leaves your hands. With it you press a seal onto each edit: a signature. The magic of the seal is asymmetry — producing it requires the secret, but checking it requires only your public address. Everyone can confirm the seal is real; no one can counterfeit it.

send 5 Ξ to Bob message (public) sign with key signature verify ✓ authentic signer 0xYou a signature proves the message came from the key holder
The signature stamps onto a public message. Anyone verifies it — but change one character and the seal no longer matches.

Tamper with so much as a single digit of a signed edit and the seal stops matching the contents. The signature isn’t glued near the message; it’s computed from it. Your identity on the sheet isn’t a name anyone can type — it’s simply the ability to produce this stamp.

Verifying: accept the true, reject the tampered

Verifying is the mirror of signing. Anyone takes the message, the signature, and your public address and checks they agree — and there is no middle ground. The untouched message passes; the same message with a single byte flipped fails, every time. That binary outcome is the whole point: the seal turns “trust me” into “check it yourself.”

✓ untouched message send 5 Ξ to Bob sig verify VALID signer 0xYou ✗ one byte flipped send 5 Ξ to Eve sig verify INVALID signer ≠ you
The same signature, two messages. The untouched one verifies (✓ valid, signer = you); flip one byte and verification fails (✗ invalid) — there is no in-between.

From key to address: a one-way hash

Where does that “public address” come from? You run your public key through a hash — a one-way fingerprint function, and the first foundational tool we’ll lean on again and again. Feed it any input and it returns a fixed 32 bytes; change a single character and almost every output byte flips; and there is no way to run it backward. Your address is just the last 20 bytes of the hash of your public key. Meet the machine here — you’ll see this exact avalanche again when we fingerprint the whole state and link blocks together.

Interactive

keccak256 — the folding machine

Any input, however long, becomes exactly 32 bytes. Change one character and almost every output byte flips. That sensitivity is the entire reason a fingerprint can detect change.

Input17 bytes in · any length
keccak256256-bit sponge · always 32 bytes out
Digest · 32 bytes
d0e28d17a11f073ece738166449587d268272a4400680e4ae2577f2f98ab07ac
0xd0e28d17a11f073ece738166449587d268272a4400680e4ae2577f2f98ab07ac
One tiny change in → a totally different fingerprint out. That's the avalanche, and it's why a single edited balance rewrites the whole path of hashes up to the state root.

Real keccak256 (the same hash Ethereum uses), computed in your browser.

The real seal: secp256k1

Now the whole pipeline at once, for real. The seal is an ECDSA signature over the secp256k1 curve, the exact scheme Ethereum uses. Roll a key and watch private key → public key → address (that last arrow is the hash above), then sign a message. You’ll notice the signer can be recovered straight from the signature — park that; it’s the whole trick of step 3.

Interactive

Keypair & signature lab

The private key derives the public key derives the address — one way only. Sign a message, then recover who signed it from the signature. Tamper with the message to break it.

private key
secret · 256-bit random number · never share
public key0x044e3b81af…dd277956dea point on secp256k1 · shareable
address0x2c7536e3…96a65c23last 20 bytes · this is "you"

→ One-way only. Public key and address are derived from the private key; you can't run the arrows backward.

This is a signature, not encryption. The message stays public — anyone can read it; the signature only proves who wrote itand that it wasn't altered. Encryption (hiding the contents) is the other use of the same keypair math, and Ethereum doesn't do it to transactions. Real secp256k1 + EIP-191, computed in your browser.

Real secp256k1 ECDSA + EIP-191; address, signature, and recovery validated byte-for-byte against viem.

Formula
private key 1 public key 2 address 3
  1. 1 a random 256-bit secret — the only thing you keep
  2. 2 secp256k1 scalar-multiplies it onto the curve (one-way)
  3. 3 the last 20 bytes of keccak256(public key)
Each arrow goes one way only — right, never left. That asymmetry is exactly what a signature proves.

One clarification, because it follows straight from “the message is public” and almost everyone gets it backwards: signing is not encryption. The same keypair could encrypt, but with the roles flipped — and Ethereum L1 doesn’t hide your edits at all.

Signing (what we just built)Encryption (what it isn’t)
Goalauthenticity + integrityconfidentiality
Private keysignsdecrypts
Public keyverifies / recoversencrypts
The message ispublichidden

Your edit is plaintext on the sheet forever; the seal proves who, and unchanged — never hidden.

And one subtle hole the real protocol had to plug. A raw signature is just over bytes, so a dapp could hand you an innocent-looking “message” that is secretly a valid transaction — and your seal would authorize it. The fix is to never stamp raw bytes, but bytes inside a labelled context:

→ Step 3: read the author straight out of the seal.

3 step Reading the author

The signature already says who you are

Here’s the trick that lets Ethereum drop the “from” field entirely. From a signature and the message it covers, you can run the math backwards and recover the exact address that must have produced it. The author isn’t a claim attached to the edit — it’s something you compute from the seal itself. Lie about whose row to touch and the recovered address is a stranger with no money to move.

signature only ecrecover who signed? 0xBob…a3f1 the recovered signer no “from” field needed; the signature reveals the sender
Recovery: the signer's address is derived from the signature alone. The edit never needs to claim who sent it.

So an Ethereum transaction genuinely has no “from” field. The network takes your signed edit, recovers the address, and that is the only row it’s allowed to debit. Authorship and authority collapse into one unforgeable stamp — but authorship alone is not yet safety.

A signature is reusable — so the nonce

Here’s the flaw the seal doesn’t fix: it never expires, and the message it covers is public. The moment you hand Bob a signed “subtract 5 from me, add 5 to Bob,” Bob holds a perfectly valid edit he can broadcast again — and again. Every copy verifies; every copy moves another 5. One payment becomes a slow leak you never authorized.

without a nonce send 5 Ξ → Bob (no nonce) ✗ replays again 9 → 4 → -1 Ξ the same signed edit applies again and again — the balance leaks with a nonce send 5 Ξ → Bob nonce 7 · spent 9 → 4 Ξ the copy is rejected — nonce 7 already spent, so each edit applies once
A signed message is public and valid forever, so any copy replays. A nonce makes each one single-use.

The fix is one counter folded into the signed message: a nonce, “this is my 7th transaction.” The network remembers how many each account has spent and applies only the next number in line. Your nonce-7 edit lands exactly once; every replayed copy now names a 7 the account has already passed, so it bounces. Ordering comes free — an account’s edits can only take effect in nonce order, so no one can reshuffle them either.

Replayable across worlds too — so the chainId

One subtler copy remains. The same software runs on testnets and other EVM chains that hold the very same addresses. A signed nonce-7 transfer on a test network would verify just as well replayed onto the real one. So the signed message also commits to a chainId — making the edit valid on exactly one chain and inert on every other.

A bare transfer is finally watertight, and look at why each field is there: a recipient and amount to say what to do, a nonce so it can’t be replayed here, a chainId so it can’t be replayed elsewhere, and a signature that is the sender. Pull any one out and a specific door swings open.

Build the real transaction

That list — sender-by-recovery, nonce, chainId, recipient, amount — is an Ethereum transaction. Build and sign one for real: edit the fields, sign, and watch the sender get recovered from the signature, appearing nowhere in the bytes. Tamper with a field after signing and recovery collapses to a stranger.

Interactive

Transaction signer

Edit, sign, recover. The signing hash updates live; signing produces (r, s, yParity); the sender is recovered from that signature. Tamper with a field after signing to break it.

Your wallet0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266private key 0xac09…ff80 · a public throwaway demo key
The transaction
  • chainId1
  • maxFeePerGas30 gwei
  • gasLimit21000
  • no from field →
Signing hash · keccak256(0x02 ‖ rlp(tx))0x35935b2459…1917d94a

Real secp256k1 ECDSA + EIP-1559 RLP, computed in your browser. Edit any field after signing: the signature only authorizes the exact bytes it signed, so the recovered sender turns into a stranger — that's the whole security model.

Real secp256k1 ECDSA + EIP-1559 RLP; signing hash, tx hash, and recovery validated byte-for-byte against viem.

Recovery — the move that lets us drop the “from” field — is just the signing math run backward:

And the bytes are laid out as a typed envelope — a type byte, then an RLP payload — so the protocol can add new transaction shapes without ambiguity:

That one byte turned “the transaction format” into a versioned family — same core (signed, nonce-ordered, gas-metered), new fields bolted on as needs appeared:

  • 0x00 Legacy — the original, pre-typed format.
  • 0x01 EIP-2930 — adds an access list (pre-declared storage, for gas).
  • 0x02 EIP-1559 — today’s default: base fee + priority tip (the gas fields we reach in step 9).
  • 0x03 EIP-4844 — blob-carrying transactions, the heart of L2 data availability.
  • 0x04 EIP-7702 — lets an EOA temporarily act as a smart-contract account.

→ Step 4: let no one hold the master copy.

4 step Everyone's copy

No master copy — everyone runs the sheet

So nobody hosts the spreadsheet. Everybody does. Every participant keeps the full sheet and the rules for updating it, and independently re-checks every signed edit before applying it. There’s no privileged server to subpoena, bribe, or switch off. You don’t trust the network — you keep your own copy and verify it from the very first row.

your edit ✗ dropped one server single copy a single host can silently drop or rewrite your edit your edit every node re-checks it; no single host can silence it
The sheet is replicated everywhere. Each node re-derives the same state from the same edits — trust nobody, verify everything.

This is the real jump from “a database” to “a blockchain”: the data stops living somewhere and starts living everywhere, with the rules baked into every copy.

→ Step 5: fold the whole world into one number.

5 step One fingerprint

One fingerprint for the entire world

The blunt fix uses the hash from step 2: run the entire sheet through the folding machine and compare the one number that falls out. It half-works, and breaks two ways: changing a single balance forces you to re-hash the whole sheet, and to prove your balance to someone you’d have to hand them the entire sheet so they could re-hash it.

Fix both with a tree of hashes. Hash accounts in groups, then hash those hashes, level by level, up to a single root. Now editing one account only re-hashes the short path from its leaf to the root — not the whole sheet — and you can prove one account to someone holding only the root by sending just the sibling hashes along that path. That’s a Merkle tree.

But state isn’t a tidy fixed array; it’s a sparse, ever-changing map keyed by 20-byte addresses. A plain tree doesn’t encode where an account lives. So make the key be the path: read the address as a string of hex digits — nibbles — and walk them down the tree to find the account.

address a 7 3 f c branch root node — take slot a branch next node — take slot 7 extension shared nibbles 3 f c leaf value = 9 Ξ re-hash edit the leaf, re-hash up the path, new root
An address is a path of nibbles. Walk it down the tree to the account; three node types make that walk efficient.

Walking that path efficiently forces exactly three kinds of node, and each earns its place. A branch node is a 16-way fork — one slot per possible next nibble — used wherever paths diverge. A leaf holds the final stretch of path and the account’s value. And an extension node compresses a long run of nibbles that everything below happens to share, so the tree doesn’t waste a whole 16-way branch on a stretch where nothing forks. Hash every node from its children — keccak of its encoding — up to one root, and you have Ethereum’s Merkle-Patricia trie: the Patricia part makes keyed updates cheap, the Merkle part makes the whole thing one verifiable fingerprint.

0xA1… 14 Ξ 0xB2… 9 Ξ 0xC3… 2 Ξ trie BLOCK HEADER # 9f2e a2e0 edit one account → a brand-new state root falls out
Edit an account, re-hash up its path, and a brand-new 32-byte state root falls out — the single number that fingerprints the whole world.

Take the real trie apart

This is the genuine article — the same RLP, hex-prefix node tags, and keccak256 a live client uses; the stateRoot it prints is byte-for-byte what go-ethereum or reth would compute. Insert an account and watch the path restructure and pulse to a new root; click any node to read its encoding.

Interactive

Merkle-Patricia Trie visualizer

Insert or edit an account. Nodes glide to new positions and the changed path pulses up to a brand-new state root. Click a node to inspect its RLP and hash.

abcd19BRANCH2 children0xfe1d55…dfaaEXTENSIONshares: 70xd4c332…c9dfLEAF · inlineDave · 0.4 ETHpath …014BRANCH2 children0x74362b…7f04BRANCH2 children0x3bfd19…99b6LEAF · inlineCarol · 88.1 ETHpath …4LEAF · inlineAlice · 12.0 ETHpath …LEAF · inlineBob · 3.4 ETHpath …
Branch (16-way)Extension (shared prefix)Leaf (value)Rewritten by last edit
Block headerwhere the root lands
  • parentHash0x…
  • stateRoot0xfe1d55aa34af
  • transactionsRoot0x…
  • receiptsRoot0x…
  • number0x…
  • timestamp0x…
BRANCH
children
a b
rlp
0xf84480808080808080808080a0d4c33224293ce06be86e1378a345a9bb844acc66d204ef8b6b18edde43cdc9dfd38230148f4461766520c2b720302e34204554488080808080
rlp length
70 bytes
state root
0xfe1d55aa34af3c72deffdd4d1ff21f7ea4a835852e1a6a77f1ca753f86c2dfaa

A real MPT engine, validated byte-for-byte against @ethereumjs/mpt across 100 randomized cases.

Two encoding rules make every client agree to the byte. RLP serializes a node before it’s hashed; hex-prefix encoding packs a node’s nibble path plus a flag (leaf vs. extension, odd vs. even) into bytes. And one rule trips up every hand-rolled trie:

What sits in a leaf isn’t just a balance — it’s the account’s four fields, and two of them only come alive once accounts can hold code (step 8):

That storage_root is the tell: once contracts exist, each one holds another MPT for its storage, whose root lives here in the account leaf — so a single storage write ripples a new storage root into the leaf, which ripples a new state root, the same wave nested one level deeper. (One real-world detail: keys are actually keccak256(address), not the raw address — the “secure trie” — which scatters accounts so prefixes rarely collide.)

Why the shape is still changing

The hexary trie has a bill: to prove one account to someone holding only the root — what a stateless client A node that verifies blocks using witnesses (proofs) supplied with the block, instead of storing the full state itself. needs — you must reveal, at every level, the sibling hashes you didn’t descend into. A 16-way branch means up to 15 siblings per level; times depth, witnesses balloon into megabytes. That’s the roadblock to statelessness, and it’s why the commitment is being redesigned.

Interactive

Hexary MPT → Verkle → Binary

The same account, proven three ways. Watch how node arity and witness size trade off — and why the roadmap moved.

16 slots/level · many siblings to reveal
Node arity16-way
Witness sizelarge

A branch node has 16 slots. To prove one account you must reveal up to 15 sibling hashes at every level — and the secure-trie keys make the tree deep-ish and wide.

whyMerkle proof cost ≈ (arity − 1) × depth hashes. With arity 16, siblings dominate the witness.

Diagrams and bars are illustrative, order-of-magnitude — not a live cryptographic benchmark. The takeaway is structural: the commitment scheme dictates the tree shape, and the tree shape dictates how big a stateless witness has to be.

For years the fix was Verkle trees (vector commitments: one short proof for all 256 children, siblings stop mattering). Through 2024–2025 the roadmap pivoted toward a binary Merkle tree (EIP-7864): arity 2, ordinary hashing, no trusted setup, and friendly to the SNARK provers Systems that produce succinct proofs that a computation was done correctly. Simple hash-based trees are far cheaper to prove inside a SNARK than vector-commitment schemes. that will eventually verify Ethereum’s state transitions.

→ Step 6: freeze the history into a chain.

6 step The blockchain

Chain the batches so the past can’t move

Stop treating edits as loose slips of paper. Gather them into a numbered block and reduce it to a small header: its number, the parentHash (the hash of the block before it), a txsRoot fingerprinting the edits inside, and the stateRoot we just built — the fingerprint of the whole sheet after this block. The block’s own hash is the hash of that entire header. Because the folding machine from step 2 turns any one-bit change into a totally different output, the header binds the block to all of history before it and to the exact state it produced.

block #1 stateRoot a2e0 txsRoot c109 prev hash 3a07 block #2 stateRoot 71c4 txsRoot c119 prev hash 88b2 block #3 stateRoot 0db8 txsRoot c129 prev hash f0c5 new root → block.stateRoot branch root 2d7a a f branch a· 8a41 branch f· 77c2 7 9 2 5 a7 · Alice bal 9 Ξ h 8a47 a9 · Bob bal 6 Ξ h 55de f2 · Carol bal 20 Ξ h 9e10 f5 · Dave bal 3 Ξ h b204
A full state trie of four accounts under the block header. Edit Alice's balance (14 → 9) and watch the actual recompute: her leaf hash changes (3c1a → 8a47), its branch re-hashes (3c08 → 8a41), the root re-hashes (91f3 → 2d7a) into a new stateRoot (9f12 → a2e0), the block hash changes, and every later block breaks — while Bob, Carol, and Dave's untouched subtrees keep their hashes.

Now watch what one tampered balance does. Change Alice 14 → 9 deep in block #1’s state and the effect doesn’t stay local. Her account’s leaf changes, which re-hashes up the trie to a new stateRoot; the stateRoot sits in block #1’s header, so the header changes; the header’s hash changes; and now block #2’s parentHash points at a hash that no longer exists — so block #2 is invalid, which invalidates block #3, and so on down the line. One edited number, and the entire chain after it falls apart. To rewrite a single past balance you’d have to silently rebuild every block since. The past is frozen not by a guard but by the sheer weight of everything stacked on top. That linked, batched, state-committed history is the blockchain.

→ Step 7: agree on one chain, and make lying self-destructive.

7 step Agreeing on one chain

Agree on one history — and price the lie

We can’t appoint a referee, so the network has to converge on one chain by itself. The obvious idea — let everyone vote, one node one vote — dies instantly. Identities are free: I spin up a million fake nodes (a Sybil attack) and outvote the honest world before breakfast.

identities are free 1 actor +1 +1 +1 +1 +1 free → flood the vote identity costs a stake 1 actor stake 32 Ξ +1 an army is unaffordable
Votes counted per identity fail — identities are free to forge. A vote has to cost something real you can't fake.

So a vote must cost something unforgeable. There are two famous prices. Proof of work charges electricity: to extend the chain you burn real computation, and the chain with the most work behind it wins, because faking it means out-burning the entire honest network. Proof of stake — Ethereum’s choice — charges bonded money: validators lock up a deposit to propose and attest to blocks. It’s cheaper and greener, but the real prize is something work can’t offer: because every voter is a known, bonded deposit, a liar can be punished, not merely out-competed.

On that base, two mechanisms settle the chain. Fork choice keeps the network on one history moment to moment: follow the fork with the most attesting stake behind it, so a minority can never make their branch the heaviest. Finality makes old history permanent: periodically validators vote to finalize a checkpoint, and once two-thirds of all stake has, reversing it would require a third of all stake to sign two contradictory checkpoints — a provable crime the protocol catches and punishes by slashing, destroying their deposits.

time → 30 31 32 33 most votes 32' ✗ outvoted most-backed history wins; finalized blocks lock
The chain follows the most-staked fork; a finalized block can't be reversed without validators burning a third of all stake.

So the attacker’s own deposit is the deterrent. The double-spend isn’t stopped by a guard at the door — it’s stopped because pulling it off at scale sets fire to a fortune you posted in advance, while honest validators simply keep the heaviest, finalized chain.

Play the attacker

Don’t take that on faith — try to break it. You control a slice of the validators: pay a merchant, take the goods, then reveal a secret fork where the payment never happened, and watch fork choice and finality decide your fate (and the bill).

Interactive

Reorg / double-spend simulator

Set how much stake the attacker controls, then reveal the secret fork. Fork choice picks the heavier chain; finality locks blocks; the verdict shows whether the double-spend works — and what it would cost.

honest chain
#1💰 your payment
#2
#3
#4
280 votes · 70 validators × 4 slots
forks here ↓ (just before your payment)

Three layers stop a double-spend: a nonce blocks re-spending from one account; fork choice (LMD-GHOST) makes the heaviest-attested chain canonical so a minority fork loses; and finality + slashing (Casper-FFG) make reversing a finalized block cost a third of all staked ETH. Security is economic — and the numbers are enormous.

LMD-GHOST head selection + a Casper-FFG finality model, unit-tested. Stake/cost figures are real-order-of-magnitude.

The two mechanisms we just invented have real names; together they’re called Gasper:

And that same accounting prices out the whole family the double-spend belongs to: 51% (stake cost + slashing), long-range rewrites of ancient history (defended by weak subjectivity A new or long-offline node trusts a recent finalized checkpoint as its starting point, rather than the longest chain from genesis — neutralizing rewrites of distant history. ), Sybil itself (influence is proportional to staked ETH, not identity count), eclipse (diverse peer selection at the networking layer), and censorship (mitigated by proposer-builder separation and inclusion lists). The pattern never changes: find what the attacker must spend, and make it cost more than the attack can earn.

→ Step 8: let the cells hold programs.

8 step Cells that compute

Cells that run programs

Upgrade the sheet one more time: let a row hold not just a balance but code. Now an edit can call that code, which runs against the shared state — reading rows, writing rows, refusing if its rules aren’t met. Because every node runs the identical program over the identical state, they all reach the identical result. The spreadsheet has quietly become a world computer, and the little programs living in its cells are smart contracts. (This is the code_hash and storage_root we saw waiting in the account leaf back in step 5, finally put to use.)

PUSH 2PUSH 3ADDMUL code runs the same everywhere stack 2 3 5 ADD folds 2+3
A cell holds a tiny program. A virtual machine steps through its instructions over a stack — the same steps, same result, on every node on Earth.

This is the leap that makes Ethereum Ethereum rather than a faster bank. A contract is just an account whose behavior is fixed in code that anyone can read and no one can secretly override — an escrow that releases when conditions are met, a token whose supply rules are public, a vote no clerk can miscount.

And it forces one more field onto the transaction. To call a contract, an edit has to say which function and with what arguments — that payload is the data field. A plain transfer leaves it empty; calling code fills it with the encoded call. (If a transaction has no recipient at all, the data field is read as the code for a brand-new contract — which is exactly how contracts get deployed.)

Step the real machine

Here is that virtual machine for real — a stack A last-in-first-out list of 256-bit words, max depth 1024. Almost every opcode pops its inputs off the top and pushes its result back. for operands, scratch memory, persistent storage, and a meter. Load a program and step it one instruction at a time; watch values push and pop and the meter tick down (we price that meter properly in the next step).

Interactive

EVM stepper

Step or play through the bytecode. The highlighted instruction is the program counter; the stack shows top-first; gas ticks down per op. Storage persists; memory is scratch.

1 / 6
Program
  1. 00PUSH10x02
  2. 02PUSH10x03
  3. 04ADD
  4. 05PUSH10x04
  5. 07MUL
gas3 used · −3 this op
Stack (top first)
  • 00x22
Storage

— empty —

Memory

— empty —

A real (faithful-subset) EVM, executed in your browser. Every instruction pops and pushes the 256-bit stack, can touch memory (cheap, transient) or storage(expensive, persistent), and burns gas until it halts or runs out. Edit the bytecode or pick a program and step through it.

A faithful EVM subset (arithmetic, stack, memory, storage, jumps, gas), executed in your browser — 16 unit tests covering the opcodes and gas.

Every word on that stack is 256 bits — sized to hold a keccak256 hash or an address, with arithmetic wrapping modulo 2²⁵⁶ (try 0 - 1 for the max uint256). And the Solidity you might write compiles straight down to this: a mapping is a keccak256(key . slot) storage address; a function call dispatches on the first 4 bytes of calldata (the function selector The first 4 bytes of keccak256 of the function signature. The contract's dispatcher compares calldata's first 4 bytes against these to pick which function runs. ); revert undoes state and refunds the gas left.

→ Step 9: put a meter on every instruction.

9 step Metering work

A meter so nothing runs forever

Charge for computation, one instruction at a time. Every operation has a price in gas; each transaction prepays a gas budget; the meter ticks down as the program runs, and the instant it hits zero, execution halts and the changes revert. An infinite loop no longer freezes the world — it simply runs out of fuel and dies, with the attacker having paid for every step. Pricing is the halting solution.

every step costs gas; the budget only falls spent remaining PUSH -3 ADD -3 SSTORE -20000 out of gas → revert (all changes undone) charge per step and cap it, so every program terminates
Each instruction spends gas from a prepaid budget. Run out and execution halts — so no program can run forever.

That prepaid budget is itself a transaction field — the gasLimit — and it quietly does a second job: since execution can never burn more gas than the limit, it caps your worst-case loss when you call a contract whose behavior you don’t fully trust. The price you pay needs fields too. Block-to-block the base fee swings, so you don’t name an exact price; you name a ceiling, maxFeePerGas (the most you’ll tolerate), and a maxPriorityFeePerGas — a tip on top of the burned base fee to give the proposer a reason to include you. The base fee itself isn’t a blind auction; it’s a controller that drifts up when blocks are full and down when they’re empty, targeting half-full blocks, and it’s burned rather than pocketed.

Drive the controller

Before it lands in a block, a signed transaction waits in the mempool — a loosely-shared, per-node waiting room, gossiped peer to peer — priced by exactly that controller. Set how full each block is and watch the base fee chase the half-full target: up by at most ⅛ per block when demand runs hot, down when it cools.

Interactive

EIP-1559 base-fee simulator

Click a block to change how full it is (or pick a demand pattern). The base fee runs forward block by block — up over target, down under, ±12.5% max. The base fee is burned; the tip goes to the proposer.

demand— or click any block to change how full it is
5127gweitarget · 50%
base fee now44.87 gwei
burned / gas44.87 gwei
tip → proposer / gas2.00 gwei

The base fee isn't an auction — it's a controller. Each block, it moves ±12.5% at most, up when the previous block was over half full and down when under, steering the chain toward 50% full. And it's burned, not paid to anyone. Senders add a priority tip on top to compete for ordering.

The exact EIP-1559 base-fee formula, in integer arithmetic — unit-tested against the spec.

The whole controller is a few lines of integer math on the parent block:

A transaction then pays min(maxFeePerGas, baseFee + maxPriorityFeePerGas) per gas; if the base fee ever climbs above your maxFeePerGas, it simply waits rather than overpaying. And one consequence worth naming: the base fee buys inclusion, but the tip buys position — and position has value (a liquidation to capture, an arbitrage to front-run). That value is MEV Maximal Extractable Value — the profit a builder can extract by choosing which transactions to include and in what order. It turned block-building into a competitive market. , and under proposer-builder separation specialized builders now assemble the most valuable block they can and bid for the proposer to publish it.

With that, the transaction is complete — and so is the machine. Lay the transaction out and not one field is decoration; each is the answer to a specific “what breaks without it?”

TRANSACTION what breaks without it chainId replay onto another chain nonce replay on this chain to no recipient (or new contract) value how much ether moves data which code + args to run gasLimit a loop drains your balance maxFeePerGas overpaying a volatile fee maxPriorityFee no tip, proposer skips you sig r,s,yParity anyone could forge the sender
The whole transaction, field by field. Each row is forced by the attack or failure it prevents — nothing is arbitrary.
04 Go Deeper Where to take it from here