github NethermindEth/nethermind 2.0.0
v2.0.0

latest release: bootnode-2.0.0
5 hours ago

Release notes

669 merged pull requests since 1.39.3, from 39 contributors.

Nethermind 2.0.0 is a hardened, faster, archive-capable execution client, built for Ethereum, its L2s, and beyond. It consolidates everything shipped in 2.0.0-rc and 2.0.0-rc2.

Most nodes upgrade in place with no resync. There are breaking changes — read that section before upgrading, especially if you run an archive node, set custom config keys, or build against Nethermind as a library or plugin.


New archive layout

Running an archive node used to mean a heavyweight, separate database and a lot of disk. Nethermind 2.0.0 puts archive-grade history on the flat state database (#12943), and that changes the trade-offs:

  • No separate archive database, no new protocol. History is built up by ordinary syncing — the same node, the same peers. You don't run a different node type or transfer data between nodes.
  • Header-verifiable. The state root at any retained height can be reconstructed from the flat rows and checked against that block's header. Historical answers are provable, not best-effort.
  • You choose how much history you pay for. Three shapes, one feature:
    • Full archive — retain every historical state.
    • Rolling window (FlatDb.HistoryRetention=Rolling + FlatDb.HistoryRetentionBlocks) — answer historical queries for the last N blocks and reclaim older state.
    • Since a fixed height (FlatDb.HistoryRetention=SinceBlock + FlatDb.HistoryRetentionSinceBlock) — keep everything from a chosen block onward and never prune it.
    • Per-address (FlatDb.HistorySliceAddresses=…) — retain deep history only for the contracts you query while keeping the general window small.
  • Correct by design at the edges. Below the retained floor, reads fail closed with an explicit "not retained" response instead of silently answering from live state.
  • A competitive footprint. A full Ethereum mainnet archive is approximately 2.1 TB today, substantially smaller than legacy trie/hash-based archives.
  • Historical eth_getProof (#13098). An unwindowed flat archive can serve eth_getProof and proof_* at covered historical heights. Off by default.

The upshot: teams that previously ran a full archive "just in case" can instead use a bounded window — or retain deep history only for selected contracts — to reduce disk and operational overhead without giving up the historical answers they need.

How much disk to expect now

Measured on live nodes, mainnet at ~block 25.88M (2026-09-01). Numbers grow slowly as the chain grows — treat them as "today's floor," not a fixed ceiling.

Configuration Retained history Execution DB Total node disk Provision
Full mainnet archive Historical state from genesis; historical proofs are served only close to the chain tip ~2.1 TB ~2.3 TB 3 TB
Windowed mainnet archive ~2 months of state and ~1 year of blocks; block pruning measurement pending ~0.9 TB (926 GB; provisional) ~1.0 TB (provisional) 1.5 TB
Per-address mainnet archive Deep history for 6 of the busiest contracts + a 256-block base ~1.8 TB ~1.9 TB 2.5 TB
Full Gnosis archive Historical state from genesis; historical proofs are served only close to the chain tip ~0.94 TB (941 GB) ~0.94 TB 1.5 TB

Current measured saving: retaining ~2 months of state while keeping ~1 year of blocks takes the execution DB from ~2.1 TB → ~0.9 TB — roughly 60% less disk. The figure for a fully pruned ~2-month block window is still being measured.

Per-address disk scales with how active your chosen contracts are — pick lower-traffic contracts and it drops sharply. It's a "keep deep history cheaply" tool.

Rule of thumb for planning:

  • Need full mainnet history → provision 3 TB (2.2 TB DB + CL + room to grow).
  • Only need recent history → the current measurement is ~0.9 TB for ~2 months of state with ~1 year of blocks retained. The fully pruned ~2-month figure is pending; size your window with HistoryRetentionBlocks.
  • Need forever-history for a few specific contracts → budget by those contracts' activity, plus a small base window.

Performance

Flat state at chain tip

A 12-hour Ethereum mainnet measurement across six fully synced, warm nodes found the flat state layout to be the fastest configuration at chain tip on both hardware tiers. Against the same-tier 1.39.2 half-path baseline, flat processed blocks at 1.80× the throughput on 8 vCPU standard machines and 2.55× on 16 vCPU premium machines.

The comparison used three configurations — 2.0.0 flat, 2.0.0 half-path, and 1.39.2 half-path — on each hardware tier, all over the same 12-hour window. Each node contributed 2,880 samples and processed 3,587–3,588 blocks, with zero bad blocks, zero sample gaps, and zero restarts. RAM, storage, region, and OS image were identical; core count was the only hardware difference between tiers.

Mean block processing time at chain tip

Mean block processing time at chain tip

The largest improvement is in tail latency, not the typical block. Flat's p99 was 351 ms on standard and 208 ms on premium — only 4.3× and 4.2× its own median. The 1.39.2 baseline reached 1,849 ms and 1,646 ms at p99, or 21× and 24× its median. 2.0.0 half-path also substantially reduced the tail, showing that part of the gain comes from 2.0.0 generally and part from the flat layout.

Median, p90, and p99 block processing time

Median, p90, and p99 block processing time

Flat also benefited most from additional cores: mean processing time improved from 96.5 ms on 8 vCPU to 57.6 ms on 16 vCPU, a 1.67× improvement. The equivalent gain was 1.13× for 2.0.0 half-path and 1.18× for 1.39.2 half-path.

The trade-off is initial sync time. Flat took 192.0 minutes on standard and 128.6 minutes on premium — about 75% longer than the same-tier 1.39.2 half-path baseline. Once synced, it delivered the best and most consistent chain-tip performance measured here; 2.0.0 half-path was the fastest configuration to reach tip.

Time to sync to chain tip

Time to sync to chain tip

Faster eth_call under load

Across the completed benchmark cells, 2.0.0 answers the internal 497-record eth_call corpus 15–27% faster than 1.39.3, with byte-identical responses on all 497 records in every layout and architecture measured. It also sustains 22–25% more heavy high-gas multicalls per second.

Both versions ran against the same mainnet snapshot at block 25,490,000, using the same seeded request sequences and runner, interleaved over overlay mounts on pristine snapshots. Figures come from the run-rpc-benchmarks workflow, measured on the 2.0.0-rc build; nothing on the eth_call path changed between that build and 2.0.0.

Latency on the 497-record corpus

The corpus represents heavy simulation traffic: every record carries state overrides, and the median request is about 331 KiB. Each arm replayed 20,000 seeded requests at 100 requests per second after a discarded 60-second warm-up.

eth_call latency percentiles, 1.39.3 vs 2.0.0

eth_call latency percentiles, 1.39.3 vs 2.0.0

The gain widens into the tail on the flat layout: amd64 p99 drops 29.5%, from 104.79 ms to 73.92 ms. This reflects real work saved rather than scheduling luck — CPU time per request falls 25.5% on amd64 flat and 17.7% on arm64 flat. Paired per-record medians agree with the aggregate: −20.1% on amd64 (95% CI −20.6% to −19.6%; 451 of 497 records faster) and −25.2% on arm64 (95% CI −25.7% to −24.3%; 476 faster).

Throughput

Closed-loop throughput was measured over 40 passes across all 497 records at concurrency 16. Compared with 1.39.3, 2.0.0 delivered 29.6% more throughput on amd64 flat, 36.4% more on arm64 flat, and 20.6% more on amd64 halfpath.

Closed-loop eth_call throughput, 1.39.3 vs 2.0.0

Closed-loop eth_call throughput, 1.39.3 vs 2.0.0

Heavy high-gas multicalls

Three EthCallChaos high-gas Multicall3 aggregate3 scenarios — roughly 34 KB payloads carrying about 500 ms of EVM work each — ran in isolation at 100 and 300 requests-per-second targets, in ABBA order, with 512 virtual users. Both versions saturated at both target rates, so achieved throughput is the relevant measure; p99 values in the 19–37 second range represent queue depth, not call latency.

Heavy high-gas Multicall3 capacity, 1.39.3 vs 2.0.0

Heavy high-gas Multicall3 capacity, 1.39.3 vs 2.0.0

Repeats within a version varied by about 1 request per second, against a between-version gap of about 5 requests per second on flat and 3.5 on halfpath. Every multicall cell completed with zero failed requests, while the accompanying light eth_call contract mix remained sub-millisecond on both versions.

Results summary

Cell Architecture Layout Mean latency Paired median Throughput Response parity
497-record corpus amd64 flat −27.3% −20.1% +29.6% 497/497
497-record corpus arm64 flat −23.6% −25.2% +36.4% 497/497
497-record corpus amd64 halfpath −15.5% −16.3% +20.6% 497/497
Heavy multicall amd64 flat Saturated +22–25%
Heavy multicall amd64 halfpath Saturated +23%

Other optimizations

  • Keccak-256 ~30% faster via AVX-512VL and batched hashing. (#12844)
  • Single-call RocksDB point reads remove a value-sized native allocation and copy on every read. (#12986)
  • EVM stops redundantly zeroing memory on overwrites and fresh allocations — large wins on memory-heavy execution. (#12953)
  • eth_call state overrides applied without merkleization. (#12904)
  • Precompile result cache is partitioned per precompile and bounded — a cheap precompile can no longer evict an expensive one, and the cache no longer grows without limit. (#12966)
  • JavaScript tracers are bounded per transaction. Each transaction gets its own V8 engine, released as soon as its result is rendered, so only one engine is alive at a time during a block trace. A native noopTracer is available. (#13464)
  • ARM64-specific: EVM byte-swap fast path (eth_call latency and block-processing improvements) and a per-thread EVM call-pool free list. (#12798, #12905)
  • Plus hot-path loop/atomics cleanups. (#13000, #12998, #12870, #12905)

Highlights

  • Flat DB is the default state backend (#11931). FlatDb.Enabled defaults to true. A new node, or a resync, gets the flat backend. An existing patricia database is detected at start and stays patricia — upgrading does not migrate or resync you. To sync a fresh node on patricia: --FlatDb.Enabled=false.
  • Flat-history archive (#12943). Archive-grade, header-verifiable history in four shapes (full, rolling window, since-block, per-address), all built from ordinary syncing — no separate archive database, no new P2P protocol. Below the retained floor, reads fail closed rather than silently answering from live state.
  • Historical eth_getProof on a flat archive (#13098). Off by default.
  • Streaming snapshot download (#12862). Import an archive snapshot without first storing the full archive locally, lowering peak disk during bring-up. Provision free disk equal to at least 1.5× the snapshot size; the import fails if less space is available.
  • Standalone bootnode tool (#12601). A dedicated discv4/discv5 bootnode with REST APIs, Prometheus metrics, and Docker/Grafana assets.
  • In-house discv5 (#11728), replacing the external implementation.
  • EIP-7805 (FOCIL) inclusion lists (#8003) and a sparse blob pool with eth/72 cell-level exchange + engine_getBlobsV4 (#11094). Both are implemented and fork-gated (they activate with their respective forks).

Fresh nodes on the default config also serve snap (Sync.SnapServingEnabled defaults on for the flat path). Set Sync.SnapServingEnabled=false if you do not want the extra outbound bandwidth. Existing patricia nodes are unaffected.

A patricia node upgraded to 2.0.0 will still create empty flat column families on disk (the probe opens them before it notices the patricia state). This is harmless extra disk and file descriptors; they are not used.

⚠️ Breaking changes

If you run a node (config)

  • Db.FlushOnExit is now an enum, not a boolean. Values: None, WalOnly, Full. The default changes from full-flush to WalOnly. If your config, env var, or CLI still sets true/false, the node fails to start — change trueFull, falseNone.
  • Blocks.PreWarmStateOnBlockProcessing was replaced by Blocks.PreWarming. New enum values: None, Block, BlockAndMempool (default BlockAndMempool). The old key is now silently ignored (you'll get a warning, not a crash) — rename it or you lose your setting.
  • Numeric config keys are now unsigned. Negative values are rejected and the node fails to start. The one common case: Receipt.TxLookupLimit: -1 (the old "never index" sentinel) must become 18446744073709551615.
  • Receipt.MaxBlockDepth default lowered from 10000 → 1000. A single eth_getLogs spanning more than 1000 blocks is now rejected unless the log index is enabled. If you rely on wide ranges: raise Receipt.MaxBlockDepth, split the query, or enable the log index.
  • FlatDb.HistoryRetention is required whenever you set a window size or a since-block. FlatDb.HistoryRetentionBlocks on its own fails to start. A window size without a mode, Rolling without a size, SinceBlock without a block, or both numbers at once, are all refused.
  • A configured FinalTotalDifficulty is no longer treated as "TTD reached" (#12555). Custom-FTD configs that relied on that inference will wait for the real TTD.
  • sepolia and hoodi default configs are now discv5-only (hoodi's static bootnode list was removed). Stock-config nodes on those networks discover peers via discv5 + DNS. Custom DiscoveryConfig is unaffected.
  • Mainnet ancient-history barrier raised (block 15537394 → 24600000). Existing nodes keep all their data. Only new or resyncing nodes retain less pre-24.6M history by default.

If you run an archive node

  • Archive configs now enable the flat-database backend with history (see New archive layout above). Your existing patricia archive DB keeps working (the new flag is ignored on an existing DB — nothing corrupts). To actually run the new flat archive you must resync, or set FlatDb.ImportFromPruningTrieState=true for an in-place trie→flat import. Enabling flat history is forward-only — there is no backfill below the point you enable it.

If you integrate over JSON-RPC / Engine API

  • engine_newPayloadV5: a malformed block-access-list envelope now returns {"status":"INVALID", ...} (HTTP 200) instead of JSON-RPC error -32602. (#13012)
  • trace_simulateV1: the response field stateChanges was renamed to stateDiff. (#12892)
  • Receipt JSON on the t8n / direct-serialization path now emits root or status (mutually exclusive), not both. The standard eth_getTransactionReceipt path is unchanged. (#12867)
  • debug_traceCall now uses a base fee of 0 for unpriced calls, matching go-ethereum. Reported gas figures change accordingly. (#12804)
  • eth_maxPriorityFeePerGas returned a gas price; it now returns a priority fee. (#13129)
  • eth_getStorageAt position parsing is stricter — spec-compliant hex-quantity clients are fine, but a client sending a bare number or un-prefixed string may now get a parse error. (Verify against your integrations.)
  • Amsterdam-gated (not active on mainnet yet, relevant if you test against devnets): eth_call now returns errors (out-of-gas, address collision, EF-prefix) where it previously returned 0x; eth_estimateGas and eth_createAccessList return updated values. (#12806, #12971, #12970)

If you build against Nethermind (plugin / library authors)

  • Many IConfig numeric properties changed from long/int to ulong. This is source-breaking — recompile your plugins against 2.0.0.

Consensus / protocol

  • EIP-7610 support removed. (#12935) No change on any live network — the storage-collision check is superseded by EIP-684 and was never activated as a fork. Only affects state-test / collision fixtures.
  • Stricter RLPx/Snappy frame validation. (#12896) Malformed frames are now rejected/disconnected. Conformant peers (Geth, Besu, older Nethermind, CL relays) are unaffected — this is DoS hardening.
  • Optimism engine_signalSuperchainV1 V0 byte layout corrected (7→8 bytes) to match op-geth/op-node. (#12805) OP-Stack operators only.

Dropped

  • ethernity-sepolia network removed (ethernity-mainnet is retained).

Sync, database & state

  • Mainnet snap no longer livelocks on the last large-storage account; forced pivot updates are distance-guarded. (#13155, #13200 / #13310)
  • A stalled snap sync is now visible at the default log level. (#13248 / #13257)
  • In-flight sync dispatch is drained before the database is torn down, so a graceful restart of a syncing node no longer SIGSEGVs. (#13154 / #13309)
  • Shutdown no longer flushes the unfinalized tail to RocksDB. A clean restart used to leave the persisted state at an unfinalized block, making any reorg below the pre-shutdown head unservable. (#13523)
  • Fixed false header < body block-tree corruption after an ungraceful restart during beacon sync. (#12876)
  • Flat warmer correctness: isolate warmer misses and safely publish verified reads (resolves InvalidStateRoot waves); restore the trie-warmer negative cache without poisoning live reads; warmer misses no longer escape through shared trie children. (#12951, #12877, #13272)
  • A malformed snap AccountRange no longer strands a sync partition; a malformed header response no longer stalls fast-headers. (#12979, #13019)
  • Receipt bodies are looked up by number and hash. (#13139)
  • Compacted snapshots are released after base conversion; finalized forks are pruned from persisted snapshots. (#13223, #13230)
  • Full-pruning snapshot trigger is measured from the pruning boundary. (#13199 / #13308)
  • Fast-sync settings (pivot block, hash, and total difficulty) are refreshed for every supported network at release, so a fresh node starts from a pivot its peers still retain. (#12962)

JSON-RPC & Engine API

  • Streamed trace_* keeps the module rented until the result is written. Concurrent traces no longer collide on the overridable world scope. (#13095)
  • Unauthenticated input no longer produces empty HTTP 500s, leaked stack traces in error.data, or a WARN flood of -32602s. A chunk-size ≥ 0x80000000 is HTTP 400. Deep recursive eth_estimateGas no longer answers -32603 "Stack empty." (#13194, #13195, #13197, #13198, #13156 / #13251)
  • A missing-argument marker on a required parameter is rejected as -32602, not an NRE. (#13235)
  • Out-of-memory on the RPC path is logged without formatting the request. (#13225)
  • The chainspec-misconfiguration warning no longer fires as a false positive; JSON-RPC logs when it actually opens. (#13202, #13203 / #13255)
  • Each engine_getPayload response is built from a single block candidate; the improvement context of a replaced payload is disposed. (#13093, #13206)
  • Browser POSTs are routed past the trusted fast lane so CORS headers are emitted. (#12900)
  • Fixed a flaky eth_simulateV1 parent when a block-body write is pending. (#12984)
  • Documentation-generation fixes for RPC response schemas. (#12892, #12869, #12866)

Networking & protocol

  • ENR bootnodes accepted in discv4. (#12992)
  • Conservative block-body transaction count limit; inbound RLP byte-list caps. (#12990, #12914)
  • eth/72 announced cell-mask fix; local backpressure no longer charged to peers. (#13007)
  • Restored eth/68 blob-violation handling; IPv6 enode text endpoints. (#12605, #12678)
  • Kademlia discovery traffic is reduced on a healthy table. (#13124)
  • Undersized ECIES ciphertexts throw controlled exceptions. (#13135)
  • Fixed a peer-update timer shutdown race. (#13010)

Consensus correctness (state-affecting fixes)

  • Zero-value balance credits no longer create an empty/dead account under EIP-158/EIP-161. (#13520)
  • Zero-nibble extension nodes are rejected in proofs — a canonical trie never emits one, and other clients forbid it. (#13519)
  • EIP-8037 beacon-root updates use canonical system calls rather than ordinary capped-transaction gas initialization. (#13483)
  • Preserve the RIPEMD touch across nested reverts (go-ethereum parity). (#13016)
  • Reject deposit logs with non-canonical ABI offsets (stricter EIP-6110 validation; honest blocks unaffected). (#12895)
  • Detect Geth genesis format independent of property order. (#12784)

Txpool

  • Blob transactions persisted by a 1.39.x node are decoded correctly when a reorg re-adds them. The blob-pool storage format gained a cell-mask trailer in 2.0.0; reading a block's worth of older records back could mis-frame them and abandon the transaction-pool update for that block. Upgrading in place needs no action. (#TBD-BLOB)
  • Corrupt light-blob records are skipped on pool restore instead of aborting it; blob persistence after revalidation is atomic. (#13032, #13045)
  • Rlp/TxDecoder type-initializer cycle broken. (#13158)

L2 & other chains

  • Optimism: OP Superchain chain registry refreshed (~40 chainspecs); OP payload decoding hardened against a malicious offset table; protocol-version signaling fix. (#12963, #12920, #12805)
  • XDC: runs the patricia state backend so GetNodeData can still be served to Go XDPoSChain peers; epoch resolution below the fast-sync pivot, archive start at the XDPoS v2 switch, blacklisted-address pool rejection, rewards read as XDC amounts, and several reliability fixes. (#13478, #12974, #12917, #12879, #12852, #12872, #12878)

Ready to the Glamsterdam fork on Sepolia

Network Epoch Start slot Unix UTC
Sepolia 353024 11296768 1791294816 Tue, 06 Oct 2026, 13:53:36 UTC

You can try Glamsterdam on Plataberget temporary testnet already! Just use --config plataberget. Hoodi and mainnet timings are still TBD


Known issues

These are open against the flat backend, which is the default for a new database. A node that upgraded in place from 1.39.x keeps its patricia backend and is not exposed to any of them.

  • A snap sync on the flat backend cannot resume across a restart. Every run clears the flat columns, so restarting mid-sync discards the state downloaded so far and starts again from zero. Plan an uninterrupted initial sync, or sync on --FlatDb.Enabled=false (patricia/half-path), which does resume. A node that has reached the tip never re-enters snap, so this affects bring-up only. (#13601)
  • Copy-style full pruning is not available on the flat backend. A flat node accepts Pruning.Mode=Hybrid, Pruning.FullPruningTrigger, and Pruning.FullPruningThresholdMb without warning but never prunes, and admin_prune reports disabled. If you depend on periodic full pruning, stay on the patricia backend for now. In-place upgrades from 1.39.x keep patricia and are unaffected. (#13600)
  • Legacy (non-snap) state sync on the flat backend can produce a bad state. With Sync.SnapSync=false, a restart during state sync can leave permanent account/storage holes (the trie is complete, the flat leaf is missing), and pivot changes can leave stale storage subtrees. Both surface later as a deterministic HeaderGasUsedMismatch. Use snap sync (the default), or resync on the patricia backend. (#13575, #13576)
  • A RocksDB auto-repair of a corrupted flat DB can silently drop leaves. The node then resumes as "already have state" and diverges from consensus. If RocksDB reports that it repaired your database, resync rather than continue. (#13577)
  • --FlatDb.Enabled=false on an existing flat datadir silently discards the state and full-resyncs. The node does not refuse. The inverse (a patricia DB with the new flat default) is detected and protected. Do not pass that flag to an existing flat node. (#13428)

Upgrade checklist

From 1.39.x

  1. Stop the node. No resync is needed for a normal full node, and your existing patricia database stays patricia — it is detected at start.
  2. Audit your config for the breaking keys above. The ones that crash on start are Db.FlushOnExit (bool→enum), any negative numeric value, Receipt.TxLookupLimit: -1, and FlatDb.HistoryRetentionBlocks without FlatDb.HistoryRetention.
  3. Rename Blocks.PreWarmStateOnBlockProcessingBlocks.PreWarming (otherwise it is silently ignored).
  4. eth_getLogs users: check whether Receipt.MaxBlockDepth (now 1000) affects your queries.
  5. Archive operators: decide between keeping your existing patricia archive (works as-is) or resyncing/importing to the new flat archive with history.
  6. Plugin/library authors: recompile against 2.0.0 (IConfig numeric types changed).
  7. Fresh nodes will serve snap. Turn it off with Sync.SnapServingEnabled=false if that is not what you want.
  8. Start the node and confirm 2.0.0 in the startup logs.

From 2.0.0-rc / 2.0.0-rc2

No database-format change and no resync. Do step 2 above (the FlatDb.HistoryRetention requirement arrived in rc2), then start.

Do not set --FlatDb.Enabled=false on an existing flat datadir — see Known issues.

Two smaller JSON-RPC rough edges are also open and being tracked: guard-rail rejections (-32002, -32005, 4444) still log one WARN per request (#13602), and "no state for that block" is answered with different error codes depending on the method (#13603).

Build signatures

The packages are signed with the following OpenPGP key: AD12 7976 5093 C675 9CD8 A400 24A7 7461 6F1E 617E

Build signatures

The packages are signed with the following OpenPGP key: AD12 7976 5093 C675 9CD8 A400 24A7 7461 6F1E 617E

What's Changed

New Contributors

Full Changelog: 1.39.3...2.0.0

Don't miss a new nethermind release

NewReleases is sending notifications on new releases.