Part II · Chapter 5 of 43

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.

Updated Sep 13, 2026 · 14 min
Assumed
  • 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.)

1 step What sync must do

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.

2 step Full sync

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.

genesis head #1 #2 #3 #4 #5 #6 #7 #8 replaying 46% rebuilt re-execute every transaction, block by block correct but slow: days of CPU work
Full sync re-executes every transaction since genesis, rebuilding the state from nothing. Maximally trustless — and brutally slow.

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.

3 step Fast sync

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.

root node node node leafleafleafleafleaf download the trie node by node, verify each by hash millions of tiny requests; the pivot keeps moving
Fast sync downloads the state trie node by node, verifying each against its parent's hash — no transactions replayed. But the trie has hundreds of millions of scattered nodes.

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.

4 step Snap sync

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.

flat state, in big ranges 0x00…0f 0x10…1f 0x20…2f 0x30…3f rebuild rebuilt locally root a…f g…z healed ✓ a few big sequential ranges, not a million nodes: hours, not days
Snap sync pulls the state as big flat ranges (each with a proof), rebuilds the trie locally, and heals the parts that moved. A few large requests instead of millions of tiny ones.

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.

5 step Light clients

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.

root #0root #1root #2root #3root #4root #5 get balance 0xA7? 3a… b1… acct ✓ 12 Ξ keeps only headers; asks a full node and checks a Merkle proof verifies state itself, without storing it
A light client stores only headers. It fetches each piece of state on demand with a Merkle proof and verifies it against the header's stateRoot — trustless answers, almost no storage.

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.

6 step Checkpoint sync

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.

gen head replay all history from genesis: weeks instead: checkpointfinalized head trusted recent anchor → snap forward → minutes
Instead of replaying history from genesis, checkpoint sync trusts one recent finalized root and snaps forward from there — weeks of work become minutes.

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.

03 Spec & Code The whole ladder at a glance

What you traded, made explicit

MethodTrust modelTime to syncDisk
Full / archivere-executes all history — trusts nothingdays–weeks100s GB – TBs
Fast (legacy)verifies head, trusts Merkle for statehours+~state size
Snap (default)verifies head, trusts range proofsa few hours~state size
Light clientheader chain + a proof per querysecondsKBs–MBs
Checkpointtrusts one recent finalized rootminutespairs 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.

04 Go Deeper Where to take it from here