Bitcoin has no account state. Instead, it tracks Unspent Transaction Outputs (UTXOs). The UTXO set lives in a flat LevelDB. Transactions reference UTXOs β no trie needed.
The UTXO Model
What is a UTXO?
A UTXO = an unspent output from a previous transaction. Like a physical coin you haven't spent yet.
Your Bitcoin "balance" = sum of all UTXOs that your key can spend.
How Spending Works
- 1. Find UTXOs you own
- 2. Reference them as inputs
- 3. Create new output UTXOs
- 4. Old UTXOs are deleted from DB
- 5. New UTXOs are added to DB
Storage Layout
Interactive: UTXO Set (chainstate DB)
This is what Bitcoin's LevelDB looks like. Each row is one entry. Click a UTXO to see its details.
How a UTXO Gets Its Storage Key
Tx: Alice sends 1 BTC to Bobraw_tx_bytesA Bitcoin transaction contains inputs (UTXOs being spent) and outputs (new UTXOs created).
SHA256(SHA256(raw_tx_bytes))txid = 0xa3b4c5d6...txid + vout_index (e.g., 0)UTXO key = txid:0'C' + txid + voutDB['C'+txid+':0'] = {amount, script}1// Bitcoin's UTXO Set β stored in LevelDB "chainstate" DB2// Key: 'C' + txid + vout_index3// Value: VARINT(height*2 + coinbase) + compressed_txout45DB["C" + txid + vout] = {6 height: 750000,7 is_coinbase: false,8 amount: 0.5 BTC, // 50,000,000 satoshis9 scriptPubKey: "76a914...88ac" // Pay-to-PubKeyHash10}1112// To check if a UTXO exists: just look it up in the DB13// If key exists β UTXO is unspent14// If key missing β already spent
Merkle Tree β Verifying Transactions
Each Bitcoin block header contains a Merkle root β a single hash that commits to all transactions in the block. This enables SPV (Simple Payment Verification): light clients can verify a tx is in a block without downloading all transactions.
1// Merkle Inclusion Proof2// Prove Tx3 is in the block WITHOUT downloading all transactions34Block Header:5 merkle_root = 0xabc123... // 32 bytes67Proof for Tx3:8 tx3_hash = hash(Tx3)9 sibling_hash = hash(Tx4) // sibling at level 010 uncle_hash = hash(Tx1+Tx2) // uncle at level 11112Verification:13 step1 = hash(tx3_hash + sibling_hash) // = hash(Tx3+Tx4)14 step2 = hash(uncle_hash + step1) // = merkle_root1516 assert step2 == block.merkle_root // β Tx3 is in block!
Why Merkle Trees?
- β Prove a tx is in a block with just O(log n) hashes
- β Light clients verify without full blocks
- β Any change to any tx invalidates the root
- β Block header stays small (80 bytes) regardless of tx count
Proof Size
Full Block Structure
1// Bitcoin Block β what's stored vs what's computed2{3 // ---- BLOCK HEADER (80 bytes) ----4 version: 4,5 prev_hash: "000000000000abc123...", // links to parent block6 merkle_root:"0xdef456...", // root of all tx hashes7 timestamp: 1704067200,8 bits: 0x1d00ffff, // difficulty target9 nonce: 2083236893, // proof of work1011 // ---- TRANSACTIONS (variable) ----12 txs: [13 { txid: "0xaaa...", inputs: [...], outputs: [...] },14 { txid: "0xbbb...", inputs: [...], outputs: [...] },15 // ...thousands more16 ]17}1819// What's in LevelDB:20// blocks/ β raw block data indexed by hash21// chainstate/ β UTXO set only (not full tx history!)
Why UTXO vs Account Model?
β Bitcoin UTXO Advantages
- π Parallel processing β UTXOs are independent
- π No double-spend by design β each UTXO used once
- π Simple to audit β total supply = sum of all UTXOs
- π‘οΈ Privacy-friendly β new addresses per transaction
β Bitcoin UTXO Limitations
- π No smart contracts β only Bitcoin Script
- πΈ UTXO management β fragmented balance
- π¦ No account concept β complex for applications
- π¦ Heavier transactions β must reference all inputs
How UTXO Storage β Capabilities
UTXO = independent coin unitsNatural UTXO parallelism β txs spending different UTXOs can be processed independently
No global account state trieNo complex trie updates β writes are fast and predictable
Only UTXOs stored (not full tx history)chainstate DB stays small β can't query old state without archive data
Merkle tree in block headerSPV light clients possible β verify txs without downloading everything