How a node syncs
You download a fresh client and it has to catch up to a chain that's been running for years. The way it does that — and the five ways it could — is a clean little derivation, starting from the slowest, most paranoid method and earning every shortcut from there.
- how Ethereum stores state
- finality
You install a fresh node and press start. The network has been running for years; there are hundreds of millions of accounts and a chain of blocks stretching back to genesis. Your node knows none of it. Before it can validate a single new block or answer “what’s Alice’s balance?”, it has to catch up — reach the exact same current state as everyone else, and prove to itself that the state is right.
There isn’t one way to do that. There are five, and they’re not arbitrary: each is the fix for a specific flaw in the one before it. Start with the slowest, most paranoid method imaginable and we’ll earn every shortcut from there. (This builds directly on the trie that stores the state and finality — if those are fuzzy, skim them first.)
Two things, and a tension between them
To “be synced” means your node holds two things: the current state — every account and contract, the same one committed in the latest block’s stateRoot The 32-byte root of the Merkle-Patricia trie that fingerprints the entire world state. It lives in every block header; two nodes with the same stateRoot hold the identical state. — and enough of the chain to keep validating new blocks as they arrive.
The whole subject is one tension. You could recompute everything yourself from the beginning and trust nobody — slow but bulletproof. Or you could download the finished state from a peer and trust it’s correct — fast but credulous. Every sync mode is a different point on that line between trust and speed (and a third axis: how much disk you’re willing to burn). Watch where each one lands.
→ Step 2: trust nothing. Replay all of history.
Replay every block from genesis
The zero-trust design: download every block from genesis to the head, and re-execute every transaction in order, applying each to your own local copy of the state — exactly as the original validators did. After the last block, your computed stateRoot will match the head block’s, and you’ll know it does, because you did every step yourself. This is full sync, and it’s the ground truth every other method is measured against.
Nothing is more trustworthy: you’ve personally verified every state transition in Ethereum’s history. (Keep all the intermediate states and you have an archive node — the thing block explorers run: a few terabytes on a modern path-based layout, up to ~15–20 TB on the legacy hash-based one.)
→ Step 3: stop re-executing. Just download the state.
Download the trie instead of rebuilding it
Here’s the leap. The state is a Merkle trie, and a Merkle trie is self-verifying: every node references its children by their hash, so if a downloaded node hashes to the value its parent expected, it cannot be fake. So pick a recent pivot block, take its stateRoot on faith for a moment, and download the trie underneath it directly from peers — root first, then each child by its hash, checking every node as it lands. No re-execution at all. This was fast sync.
You only verify the head by following block headers and consensus, then trust the Merkle math to vouch for the downloaded state. Days collapse toward hours.
→ Step 4: forget the tree shape. Download the raw data in bulk.
Download flat ranges, then heal
The trie is just an index over plain key → value pairs. So stop asking for it node by node. Instead ask peers for contiguous ranges of the flat account data — “every account from 0x00… to 0x20…” — served straight from a flat snapshot in big sequential chunks, each chunk carrying a range proof (a small Merkle proof that the chunk is correct and complete against the stateRoot). Download the whole state as a handful of fat range requests, rebuild the trie locally from the flat data, then do one final heal pass to patch the few nodes that shifted while you were downloading. This is snap sync — today’s default in geth and reth.
Same trust model as fast sync — verify the head, trust the proofs for the state — but the bandwidth is used efficiently. A full mainnet node syncs in a few hours.
→ Step 5: don’t hold the state at all.
Keep only headers; ask for the rest, with proof
Drop the state entirely. A block header is tiny — a few hundred bytes — and it carries the stateRoot. So keep only the header chain. When you actually need something — Alice’s balance, a storage slot — ask a full node for it together with a Merkle proof: the short chain of sibling hashes from that leaf up to the stateRoot you already hold. You verify the proof yourself against the trusted root. If it checks out, the answer is genuine; the full node can’t lie without breaking the hash. This is a light client.
You store kilobytes instead of hundreds of gigabytes, and every answer is still cryptographically verified — you’ve traded holding the state for proving each piece on demand.
→ Step 6: start from a recent point you can trust.
Trust one recent finalized block, and skip the rest
Every method above quietly walks forward from genesis, trusting the heaviest chain. But proof-of-stake has a subtlety: a node syncing from genesis alone can be fooled by long-dead validators signing an alternate ancient history (a long-range attack). The fix doubles as the biggest speedup in the whole subject. Don’t start at genesis — start at a recent finalized checkpoint: a block root only weeks old, taken from a trusted source (your client ships a list; you can cross-check a block explorer or a friend). Because it’s finalized, reversing it would burn a third of all staked ETH — so it’s a safe anchor. From there, snap-sync the state and follow forward. This is checkpoint sync (a.k.a. weak-subjectivity sync), and it’s how real nodes actually bootstrap.
This is the one mode that changes the starting point rather than the download method, and it composes with the others: modern clients pair a checkpoint-synced consensus layer with a snap-synced execution layer, and a node that was offline for months can be live again in minutes.
What you traded, made explicit
| Method | Trust model | Time to sync | Disk |
|---|---|---|---|
| Full / archive | re-executes all history — trusts nothing | days–weeks | 100s GB – TBs |
| Fast (legacy) | verifies head, trusts Merkle for state | hours+ | ~state size |
| Snap (default) | verifies head, trusts range proofs | a few hours | ~state size |
| Light client | header chain + a proof per query | seconds | KBs–MBs |
| Checkpoint | trusts one recent finalized root | minutes | pairs with snap |
The column that matters is trust model: everything to the right is just the price you pay, and everything to the left is what you’re willing to take on faith. Snap-from-a-checkpoint won because it gives up almost nothing real — a finalized root is about as trustworthy as a thing can be — for almost all of the speed.