Ethereum stores all account state in a Merkle Patricia Trie. The key = keccak256(address). The trie is 16-way (hexadecimal nibbles). This enables cryptographic proofs of any account state.
Interactive: How an Address Becomes a Trie Key
Type any Ethereum address and see exactly how it gets hashed and traversed through the MPT.
0x742d35Cc6634C0532925a3b844Bc454e4438f44e0x742d35Cc6634C0532925a3b844Bc454e4438f44eAn Ethereum address is 20 bytes (40 hex chars). This is the input to the state trie key derivation.
0x742d35Cc6634C0532925a3b844Bc454e4438f44e0x48323d7fabcdef98765432100x48323d7fabcdef98765432104 → 8 → 3 → 2 → 3 → d → 7 → f → a → b → c → d → e → f → 9 → 8nibbles[0] = '4'root → child['4'] → child['8'] → ...Leaf node reachedRLP(nonce, balance, storageRoot, codeHash)MPT Node Types — 3 Types, 1 Trie
16-element array [child0...child15, value]. At each branch, we follow the next nibble. The array is always length 17.
[child_a, child_b, ..., child_f, optional_value]Compressed shared prefix: [encoded_path, next_node]. Instead of one branch per nibble, skip identical nibbles.
[encoded("ab3"), → next_node_hash]End of path: [remaining_path, value]. Contains the compressed remaining nibbles and the actual account data.
[encoded("...remaining"), RLP(account)]1// Ethereum MPT — 3 node types in LevelDB2// DB key = keccak256(RLP(node))3// DB val = RLP(node)45// 1. BRANCH NODE — 16 children + optional value6branch_node = [7 child[0], // pointer to child for nibble '0'8 child[1], // pointer to child for nibble '1'9 // ...10 child[15], // pointer to child for nibble 'f'11 value // optional value at this path12]1314// 2. EXTENSION NODE — compressed shared prefix15extension_node = [16 "shared_prefix", // hex-encoded nibbles (compressed)17 next_node_hash // pointer to next node18]1920// 3. LEAF NODE — compressed remaining path + value21leaf_node = [22 "remaining_path", // nibbles not yet traversed23 value // the account data (RLP encoded)24]
Full Traversal — Step by Step
1// How keccak256(address) becomes a trie path23address = "0x742d35Cc6634C0532925a3b844Bc454e4438f44e"45Step 1: Hash the address6key = keccak256(address)7 = "0xabc123def456..." (32 bytes = 64 hex chars)89Step 2: Split into nibbles (0-f)10nibbles = ['a','b','c','1','2','3','d','e','f','4','5','6'...]1112Step 3: Traverse the 16-way trie13root → [branch at 'a'] → [branch at 'b'] → [branch at 'c'] → ...14 ↓ ↓ ↓15child[a] child[b] child[c]16 ↓ ... → LEAF (account data)1718// At each branch: follow the next nibble19// At extension: skip the shared prefix20// At leaf: read the value
What's Stored at the Leaf?
1// What's stored at a leaf node (Ethereum account)2account_value = RLP([3 nonce: 42, // how many txs sent4 balance: 1.5 ETH, // in wei (1.5e18)5 storageRoot: 0xdef456..., // root of THIS account's storage trie6 codeHash: 0x789abc... // keccak256(bytecode), 0x empty for EOA7])89// For smart contracts, storageRoot points to ANOTHER MPT:10// contract_storage_trie[keccak256(slot)] = value11// e.g., ERC20 balances[address] stored as:12// key = keccak256(abi.encode(address, slot_index))
EOA vs Contract Account
storageRoot = empty_trie_root
storageRoot = root of its own storage trie
Storage Trie (per contract)
Each contract has its own second-level MPT. ERC20 balances are stored here:
key = keccak256(address ++ slot)
val = RLP(balance)This is why Ethereum has 4 tries: state, storage, transactions, receipts.
RLP Encoding — Ethereum's Wire Format
Every node in the trie is encoded with RLP before being hashed and stored in LevelDB.
1// RLP Encoding — Ethereum's serialization2// (Recursive Length Prefix)34RLP(string "dog") = [ 0x83, 'd', 'o', 'g' ]5 ↑ 0x83 means "3-byte string"67RLP(list [1, 2]) = [ 0xC2, 0x01, 0x02 ]8 ↑ 0xC2 means "2-byte list"910// Account encoded:11RLP([nonce, balance, storageRoot, codeHash])12→ 0xf8 44 01 88 0de0b6b3a7640000 a0 def456... a0 789abc...13 ↑list ↑nonce ↑balance(8 bytes) ↑storageRoot(32) ↑codeHash(32)
4 Tries Per Block
1// Ethereum block — 4 separate tries2block = {3 // Each block has these Merkle roots:4 stateRoot: 0xabc..., // root of global state trie5 transactionsRoot: 0xdef..., // root of all block txs6 receiptsRoot: 0x789..., // root of all tx receipts78 // State trie:9 // keccak256(address) → RLP(nonce, balance, storageRoot, codeHash)1011 // Storage trie (per contract):12 // keccak256(32-byte-key) → RLP(value)13}1415// Proof that Alice has 1 ETH (Merkle proof):16// stateRoot → [node hash 1, node hash 2, ..., leaf]
How MPT Storage → Capabilities & Limitations
Merkle tree → cryptographic state rootAny account state can be proven with a Merkle proof. Light clients can verify state without full node.
16-way trie with keccak256 keysEvery write to account state touches O(log₁₆ n) nodes — each must be re-hashed and re-stored in LevelDB. High write amplification.
4 separate tries per blockRich data model — query state, txs, receipts all independently. But 4× the Merkle overhead.
Sequential execution requiredTransactions can read/write any account — must run in order to detect conflicts. No native parallelism.
Storage trie per contractSmart contracts have isolated, provable storage. Powers DeFi composability.