Skip to main content

Architecture

Erigon is an Ethereum execution-layer client designed around three goals: low disk footprint, predictable performance, and operational simplicity. This page explains how those goals shape its internal design so you know what is happening when you run the binary.

At a glance

Every box above is a Go package that runs inside the erigon process by default. The same binary can also be split into independent processes — see Modular processes.

Staged sync

Erigon processes the chain in a series of stages rather than the traditional "fetch block → execute block → repeat" loop. Each stage runs to completion across the whole range before the next stage starts.

A simplified pipeline:

1. Snapshots → fetch immutable history files via BitTorrent (OtterSync)
2. Headers → download and verify block headers
3. Bodies → download block bodies
4. Senders → recover transaction senders from signatures
5. Execution → re-execute transactions, write state, and build the
state commitment (Merkle trie) — all in a single pass
6. Finalization → update the canonical chain

This is the order of Erigon 3's default stage list. There is no separate Commitment stage — trie/state-root computation runs inside Execution (see below).

Two consequences:

  • Faster initial sync. Stages are CPU/IO-bound in different ways, so the pipeline avoids waiting on a single bottleneck per block. Snapshot download (stage 1) parallelises with execution warm-up.
  • Cheap restarts. Each stage records its progress. Killing Erigon mid-sync and restarting resumes from the last completed checkpoint — Erigon does not re-execute from genesis.

In Erigon 3, the Execution stage absorbs many previously-separate stages (stage_hash_state, stage_trie, log_index, history_index, trace_index) into a single pass, which is one reason Erigon 3 syncs faster than Erigon 2.

Modular processes

Each of Sentry, Downloader, TxPool, RPC Daemon, and Caplin can run as an independent process that talks to the rest over a private gRPC API.

Use the all-in-one binary by default. Split out a service when you have a concrete reason:

  • Resource isolation — pin the RPC Daemon to its own CPUs so a heavy eth_call cannot stall block production.
  • Horizontal scaling — run multiple RPC Daemons against one execution node to serve more concurrent clients.
  • Replacement — substitute Erigon's TxPool or Sentry with your own implementation.
  • Security — keep the p2p surface (Sentry) on a separate host from the JSON-RPC surface (RPC Daemon).

See the Modules section for per-component documentation, and the repository's docker-compose.yml for a worked split-process example.

Storage model

Erigon stores everything under a single datadir. Two kinds of files live there:

KindPathRoleMutable?
Active statechaindata/Recent state and recent blocks, served from MDBXYes
Historical datasnapshots/Older blocks, history, indices — distributed as immutable .seg filesNo

This split is what enables Erigon's small disk footprint relative to other archive nodes:

  • chaindata/ is small (~15 GB on Ethereum mainnet) because it only holds latest state and recently-updated values.
  • snapshots/ is large but content-addressed. Once a .seg file is finalised it is the same file on every Erigon node in the world — meaning it can be distributed via BitTorrent and verified by hash. There is no rewrite-amplification penalty for keeping history.

For the exact directory layout, file sizes on mainnet, and how to split storage across fast/slow disks, see Database and Optimizing Storage.

Embedded consensus (Caplin)

Erigon ships with Caplin, a built-in consensus-layer client. Caplin runs embedded by default, so a single erigon invocation runs both the execution layer and the consensus layer with no extra processes, no JWT secret to manage, and no separate binary to upgrade.

If you prefer to run an external CL (Lighthouse, Prysm, Teku, Nimbus), pass --externalcl and connect via the standard Engine API. The Engine-API path is identical to what Geth, Besu, or Reth exposes.

See Caplin for full details.

Flat key-value state

Where most clients store account state in a Merkle Patricia Trie inside the database, Erigon stores it as flat key-value pairs and computes the trie root incrementally as part of Execution. Trade-off:

  • Read path is directgetAccount(addr) is a single key lookup, not a multi-level trie traversal.
  • Write path defers trie hashing — updates accumulate in the KV store and the trie is rebuilt only when state-root computation requires it.

This is the single largest reason Erigon's RPC performance stays flat under load: there is no background trie compaction that can spike CPU during an eth_call burst.

Pruning is a retention decision, not a sync mode

Erigon 3 has one sync pipeline. The user-facing choice is what to retain after sync, controlled by --prune.mode:

ModeRetainedUse case
archiveEntire state history + all blocksIndexers, block explorers, deep historical queries
full (default)Latest state + a rolling window of recent blocks (the default prune distance, ~262k blocks)Most users, dApps, validators
blocksAll blocks, but no state historyFull block/receipt history without archive-size state
minimalLatest state only (shortest block window)Solo stakers, constrained hardware

See Pruning Modes for the full comparison.

Where to go next

  • Database — datadir layout, MDBX internals, mainnet sizing
  • Modules — running RPC Daemon, TxPool, Sentry, Downloader as separate processes
  • Pruning Modes — choosing what history to keep
  • Optimizing Storage — splitting datadir across fast and slow disks