The blockchain is a log file, everything else is a cache
The same university course notes that had the block reward at 6.25 BTC in part one also say a full node needs 300 GB of disk. The chain blew past 700 GB a while ago and kept going. Numbers rot; the layout underneath them hasn't moved in a decade, and the layout is the interesting part. We've spent three posts on what the data means: part one read the chain of 80-byte headers, part two opened the transactions and the scripts that lock them, part three paid the electricity bill that makes appending expensive. One question is left over, and it's the one my notes answered best: where does all of this actually live?
The answer is comfortingly boring. A full node is a directory of flat files — blocks glued back to back, exactly as they arrived off the wire — plus two small key-value databases that exist only to avoid re-reading the flat files. If you followed the git series, you already know this shape: git also keeps one file format that is the truth and sidecar indexes that are merely fast. And the best part of the layout is the trick it enables: because of how blocks commit to their transactions, you can verify that a payment is buried in those 700-plus gigabytes while storing almost none of them. Part one promised you "twelve hashes"; today I pay.
Nothing here is a mockup. Block 57055 from part one comes back, and a new fixture joins it: block 100001, mined December 29th, 2010, with exactly twelve transactions, fetched raw from blockstream.info and bundled verbatim. The same TypeScript chain engine from the earlier posts runs behind every figure, and every hash on this page is computed in your browser from those bytes.
01 · One long file of blocks
A Bitcoin Core node keeps the chain in a directory called blocks/, as files named blk00000.dat, blk00001.dat, and so on. There is no clever container format. Each file is raw blocks, one after another, each prefixed by eight bytes: the four network magic bytes f9 be b4 d9 (mainnet's constant, the same four bytes that start every message on the wire) and a 4-byte length. When a file nears 128 MiB, the node starts the next one; space is preallocated in 16 MiB chunks so the file grows in cheap strides. That's the entire spec. My notes from 2021 got this part exactly right, down to the numbers, because there was nothing there to rot.
Here's what one of those blocks looks like raw, before I color it — the first lines of block 57055, our three-transaction friend from part one:
$ xxd block57055.bin | head -7
00000000: 0100 0000 e748 2d4c 7d2a 7924 e5f8 8a51 .....H-L}*y$...Q
00000010: 9019 1269 dea6 0ff9 0978 fdf8 fe7a f200 ...i.....x...z..
00000020: 0000 0000 560f 8a98 0194 1eeb 7a26 2d10 ....V.......z&-.
00000030: 9e6b 89cf 74a2 f837 2ae3 0eaf 95b5 74a2 .k..t..7*.....t.
00000040: 3ed3 8dfb 6330 f84b 249c 151c 86ea a00b >...c0.K$.......
00000050: 0301 0000 0001 0000 0000 0000 0000 0000 ................
00000060: 0000 0000 0000 0000 0000 0000 0000 0000 ................
In part one we stopped at byte 80. This time we go all the way down: every one of the block's 598 bytes belongs to a named field, and I refuse to leave any of them gray. Hover anything — bytes, the segment bar, the legend:
Things worth watching for:
- No byte is filler. Header, a one-byte transaction count, then three transactions, each one just version → inputs → outputs → locktime. The pink
prevoutfields are 36-byte pointers at older transactions, the same move asprev_blockone level down: it's pointers all the way down, and spending is naming what you spend. - The footer clinches it. A
blk*.datfile is nothing but these dumps glued together with magic and length between them. The framing bytes shown there are computed from the spec, not captured — nobitcoindruns on this laptop — which is exactly why I can show you the length field being justbytes.length, little-endian. - Modern block files lie to
xxd. Since Core v28, block and undo files are XOR'd with a random 8-byte key stored inblocks/xor.dat. It's not encryption and protects nothing cryptographically; it stops other software on your machine — antivirus scanners, deduplicating filesystems — from pattern-matching known bytes inside the chain. Naively hexdump a fresh node'sblkfile and you'll see noise until you XOR the key back out.
Next to every blk file sits a rev file: blk00042.dat has rev00042.dat. Those are undo files. When part three's fork race ends and a block your node applied turns out to be on the losing branch, the node must un-apply it — put back the coins it spent, delete the coins it created. The undo file holds exactly the data needed to run a block backwards: the down migration to the block's up. Databases made this pattern boring decades ago; Bitcoin just ships it as flat files too.
02 · The sidecar index, again
One puzzle should feel familiar. Blocks sit at arbitrary offsets inside a half-terabyte of .dat files, and the node can still fetch any block by hash instantly. The packfiles post hit the same wall — objects at arbitrary offsets in a 2 GB pack — and git's answer was the .idx sidecar: a lookup structure that contains nothing you couldn't recompute, existing only to make the archive seekable.
Bitcoin Core made the same call with different tools. blocks/index/ is a LevelDB database — Google's embedded key-value store, the same library sitting under Chrome's IndexedDB — that maps block hashes to locations. Two record families do most of the work, and they're homely enough that my course notes describe them in full: records keyed b + block hash hold that block's metadata — height, status flags, transaction count, which blk file it lives in and the byte offset inside it, plus where its undo data sits in the rev file — and records keyed f + file number describe each block file: how many blocks, the lowest and highest heights, timestamps. A handful of one-letter prefixes as a namespace scheme, in one of the most valuable databases on Earth.
And the division of trust is the one you already know from git: the .dat files are the truth, the index is an acceleration structure. Delete blocks/index/ and start with -reindex, and the node rebuilds every record by scanning the flat files. Delete a blk file and no index will save you. The packfiles post said it about .pack versus .idx, and it transfers verbatim: one is the database, the other is just fast.
03 · The coins database
Here's the observation that makes a second database necessary. Validating a new block never requires reading old blocks. The question a validator asks isn't "what happened in block 300,000" — it's "does the output this transaction is trying to spend exist, and is it still unspent?" Ask that against 700 GB of log and you'd be scanning for hours. So Core maintains the answer as a standing structure: chainstate/, another LevelDB, holding one record for every unspent transaction output in existence — the UTXO set from part two, materialized.
Internally Core calls it the coins database, and my notes preserve the etymology: a transaction hash plus an output index identifies one output, and since that pair is unique and spendable-exactly-once, each output is a coin — with its own value, its own locking script, minted when a transaction creates it and destroyed forever when one spends it. As of April 2025 there were about 173 million coins in the set, roughly 11 GB on disk — bloated by dust outputs left behind by the inscriptions wave, which mempool.space's UTXO set report puts at roughly a third of the set: coins nobody will ever spend that every full node must carry anyway, because unspent is unspent.
This database is the one validation actually touches, block by block, spend by spend. Which makes it the node's most precious cache and its biggest bootstrap cost: to build the UTXO set honestly you must replay all 900-odd-thousand blocks from genesis — that's what "initial block download" spends its days doing. Core ships an escape hatch for that called assumeutxo — the loadtxoutset RPC landed in v26 (2023), and v28 (2024) added the first hardcoded mainnet snapshot, at height 840,000: load a ~7 GB snapshot of the UTXO set at some recent height (snapshot heights like 910,000 have their hashes baked into the source), start validating new blocks within minutes, and let a background thread replay history to verify the snapshot was honest, retroactively. It's git clone --depth=1 for a blockchain, with the one difference that the full history still arrives eventually — trust the snapshot now, check it later, keep the receipts.
04 · Twelve hashes, as promised
Now for the trick the layout was hiding. Part one ended section 04 with a debt: a Merkle tree lets you prove one transaction is in a block by showing only the hashes along its path, "a dozen hashes instead of the whole block." Satoshi spent section 8 of the whitepaper on this idea and called it Simplified Payment Verification: a wallet that keeps only the 80-byte headers — all ~959,000 of them fit in about 77 MB, a phone-sized commitment to the whole chain — and asks a full node to prove inclusion when it cares about a specific payment.
The block I picked to pay this debt is real and slightly ceremonial: block 100001, the block right after Bitcoin's odometer rolled to six digits. Here's the explorer's word on it, captured from mainnet:
$ curl -s https://blockstream.info/api/block/00000000000080b66c911bd5ba14a74260057311eaeb1982802f7010f1a9f090
{"id":"00000000000080b66c911bd5ba14a74260057311eaeb1982802f7010f1a9f090",
"height":100001,"version":1,"timestamp":1293624404,"tx_count":12,"size":3308,
"merkle_root":"7fe79307aeb300d910d9c4bec5bacb4c7e114c7dfd6789e19f3a733debb3bb6a", …}
Twelve transactions. Suppose one of them pays you, and all you hold is headers. Pick your transaction below and watch what the full node has to send you — and, more importantly, how little:
Things worth watching for:
- Four hashes settle it. Your transaction's ID plus one sibling per level, folded upward with the same
sha256das always, must land exactly on themerkle_rootsitting in the header you already have. Twelve transactions, ⌈log₂ 12⌉ = 4 siblings, 128 bytes of proof. The amber nodes travel; the dim ones never leave the node's disk. - The wart from part one returns, load-bearing. Pick any of transactions 8 through 11 and one step of the climb shows
×2: an odd level paired your running hash with itself, so that step ships zero bytes. I didn't get to choose whether to implement the duplication — without it, real proofs from real blocks don't verify. - Corrupt a sibling and watch the floor give out. Flip one byte of any amber hash and the fold diverges immediately, every level above goes red, and the final comparison against the header fails. A proof is not a claim, it's a computation you re-run; there is nothing to take on faith and therefore nothing to lie about.
- The promise was about a bigger block. Toggle the scale row: at 4,096 transactions — a full modern block — the proof is exactly twelve hashes, 384 bytes. A million transactions would cost twenty. The proof grows with the logarithm of the block, which is the closest thing to free that cryptography sells.
Honesty about where the toy ends, part one of two: my engine still only parses pre-segwit blocks, so my fixtures are from 2010. The mechanism hasn't changed — txids still pair up into the same tree, and a proof for a transaction confirmed this morning folds exactly like the one above — but modern wallets juggle a second, parallel tree for witness data that my 200 lines don't speak.
05 · What SPV became
Satoshi's sketch had a hole he never had to patch: which transactions should the full node prove to you? Your wallet has to find its payments before it can verify them, and how it asks is where the privacy goes.
The first deployed answer was BIP37 (2012): the wallet hands nodes a Bloom filter — a fuzzy, probabilistic sketch of its addresses — and nodes reply with matching transactions plus their Merkle proofs. It failed in an instructive way. The filters leaked almost everything (researchers showed you could recover a wallet's addresses from its filter with embarrassing ease), and serving arbitrary filter queries let anyone make a node grind through gigabytes for free. Bitcoin Core stopped serving Bloom filters by default in 2019. The lesson wasn't that SPV was wrong; it was that asking a server about yourself is the leak, no matter how you fuzz the question.
The modern answer, BIP157/158, inverts the direction. The node computes one compact filter per block — a Golomb-coded digest of every script in it, a few tens of KB — and the client downloads filters wholesale, checks locally whether a block concerns it, and fetches whole blocks only on a match. The server learns nothing about what you're looking for, because you never ask about anything specific. That's the "Neutrino" model, and the Merkle proof from section 04 is still doing the settling underneath.
And the deployed reality, honestly stated: most wallets that call themselves SPV today are Electrum-protocol clients. They keep headers and verify Merkle proofs — section 04 again, it's always section 04 — but they find their transactions by asking an index server "what touched this address?", which is BIP37's leak without the Bloom filter fig leaf. It's a trade: convenience and instant sync against a server that learns your addresses. Three generations of light client, one cryptographic core, moving only the question of who learns what.
06 · A field guide to nodes
So "running a node" is not one thing; it's a menu of trade-offs over which of the artifacts from sections 01–03 you keep. Flip through the five common configurations:
Things worth watching for:
- A pruned node is a full node. The most persistent misconception in the taxonomy. It validated every block from genesis — it just deleted the raw bytes afterwards, keeping the chainstate and every header. It trusts nothing and no one; the only thing it gave up is the ability to serve history (and rescan old wallets). Validation, remember, only ever needed the coins database.
- The chainstate is the hard-won part. Notice what survives every configuration that validates: ~11 GB of UTXO set. The 700 GB of blocks are the evidence; the 11 GB are the verdict. assumeutxo is precisely the observation that you can ship the verdict first and re-check the evidence in the background.
- The light clients can't see double-spends. A BIP157 wallet verifies that its payment is in a block atop the most-work chain — but whether that block also contains a rule-breaking spend, it cannot know. It outsources rule enforcement to the miners' incentives and everyone else's full nodes. That's the actual price of 100 MB versus 700-plus GB, stated without marketing.
One resident of the taxonomy stores almost nothing and isn't really a node class at all: Lightning. It's a second peer-to-peer network bolted on top, where two parties open a channel on-chain and then transact off-chain as fast as they can sign, settling the net result to the blockchain later. My course notes reached for the perfect analogy and I'm keeping it: it's Splitwise. Log a hundred debts among friends, compute who actually owes whom, make one bank transfer at the end.
07 · The Bitcoin half, compressed
This closes the Bitcoin half of the series, so the compression covers four posts: the chain is a linked list of 80-byte headers, each priced by proof-of-work (parts one and three). Money is unspent transaction outputs — coins — each locked by a small script, spent by satisfying it (part two). And all of it lives as flat files: an append-only log of blocks in 128 MiB chunks, undo files to run history backwards, one LevelDB to find things and another holding the current set of coins, both rebuildable from the log. Headers commit to transactions through a Merkle tree, so 80 bytes vouch for everything and a payment proves itself in ⌈log₂ n⌉ hashes. There is no other machinery. Second honest disclaimer and a familiar one: every size in this post — 700 GB, 173 million coins, 11 GB — is already rotting the way my notes' 300 GB did; the structure is the part you should keep.
Next up is the other half of the series, and the contrast is the point: Ethereum looked at this design — the log, the coins, the proofs — kept the chain of headers, and threw away almost everything else. No UTXOs, no coins database; in their place, accounts, balances, and a tree so central that the whole system is best described as a database that happens to have a blockchain attached.
Same promise as always: it's bytes all the way down, and you can read every one of them.