Metrics & math
Every published number lives in one of three families: structural, operational, or resilience. Each family is governed by a small set of equations — written down so a reader can disagree precisely.
Structural metrics
These describe the trie at rest.
Account count and storage slot count
For accounts and total storage slots :
Both are exact counts, sampled directly from the state-composition sensor.
Total size scales linearly with the multiplier
Because every milestone is rebloated to the same byte composition, total trie size is just the mainnet total scaled by the multiplier:
The measured points sit right on this line at 1×, 3.5×, and 5× (see Results).
Expected trie depth
The MPT branching factor is 16 (hex). For a balanced trie of leaves the expected depth is
The trie is not balanced — but Ethereum addresses are keccak-hashed before insertion, which gives a near-uniform distribution and makes the log-16 estimate accurate to within a fraction of a nibble.
The mainnet baseline measures avg account path depth = 7.61 nibbles and avg storage path depth = 4.84 nibbles (block 24,927,663, 2026-05-05). For comparison, — the trie carries about half a nibble of structural overhead from extension nodes and unbalanced subtrees.
The measured account-trie leaf distribution confirms the log₁₆ prediction — going from mainnet to 5× shifts the whole distribution one level deeper (avg 7.61 → 8.20), not many. At 5× the account trie (avg 8.20) also runs deeper than the storage tries (avg 7.24). Both distributions are plotted on the Results page.
Branch occupancy
For each branch node with children , occupancy is
A near-saturated trie (high ) is cheaper per-leaf but more expensive to mutate; the structural metric tells us where on this curve we are. Mainnet sits at — most branches carry just 3–4 of their 16 possible children, which is the consequence of keccak-distributed insertion against a trie that is far from full.
Operational metrics
These describe the cost of touching state.
Account-proof size
with bytes (RLP framing for the leaf) and bytes per internal level (15 sibling hashes + RLP overhead). We publish as empirical fits per milestone, not as theoretical constants.
The measured mainnet anchor (head 24,935,360, 2,755 unique addresses sampled):
| Quantity | Account proof | Storage proof |
|---|---|---|
bytes_p50 | 3,821 B | 1,211 B |
bytes_p95 | 3,929 B | 2,371 B |
nodes_p50 | 9 | 3 |
nodes_p95 | 10 | 6 |
These are the baseline points everything else is measured against. Storage proofs are ~3× smaller and shallower than account proofs because the average storage trie has only 4.84 nibbles of depth and most random slot queries terminate at an absent-leaf (proof of non-existence is shorter than a full leaf traversal).
Proof size tracks log₁₆ of account count, not the raw multiplier — which is why the 3.5× state (392M accounts, barely above mainnet) measures the same as mainnet, while the rebloated 5× (1.99B accounts) sits one log-step higher. The theory-vs-measured plot is on the Results page, under eth_getProof witness size.
Storage-proof size
For a storage slot read on contract the proof is account-path + storage-path:
That second term is what makes long-tail contracts disproportionately expensive — see problems.
Block execution latency
For each measured block we record wall-clock execution time
reported as median, p95, and p99 over a window of 1024 consecutive blocks.
Witness density
The bytes of proof emitted per unit of gas consumed:
This is the metric statelessness has to drive down. A pre-Verkle baseline is the whole point.
Resilience metrics
These describe how a fresh node gets to a measured state.
Snap-sync time-to-tip
For a sync starting at wall time that reaches the chain head at :
We split into bulk-phase time and heal-phase time, because in practice they scale differently.
Steady-state mutation rate
The fraction of leaves the source node changes during a sync of duration :
If approaches the rate at which the syncing node ingests new ranges, the snap-sync algorithm enters a refetch loop. The 10× ladder is partly designed to find that crossover.
The benchmark round
Every milestone (1× / ~3.5× / 5× / 7× / 10×) runs the same battery across every EL client that supports it — Nethermind, Geth, Besu, Reth, Erigon (issue #21):
Sync — snap-sync wall time from a peer at the milestone state; peak/sustained download bandwidth; disk IOPS and throughput; peak and steady-state RSS; final on-disk DB size.
Execution under live head — block processing time p50/p95/p99 over a fixed 10k-block window; gas processed per second; CPU %; RSS growth rate.
JSON-RPC latency under read load — concurrent clients hitting random recent blocks:
eth_getBalance, eth_getStorageAt, eth_call, eth_getProof (account + storage, with proof
byte sizes), eth_getCode.
Output is per-milestone {client, state_size, metric, p50, p95, p99, mean} JSON/CSV committed to
benchmarks/<milestone>/, plus cross-client Grafana deltas. A metric that degrades >25 % vs the
previous milestone is flagged as a regression. Acceptance requires reproducibility within ±5 %.
What we deliberately don’t measure (in the headline)
- Gas/sec throughput against benchmark workloads — too easy to game with a hand-picked workload.
- Disk-byte counts — too dependent on the underlying filesystem and compression.
- CPU utilisation — too dependent on host topology.
These appear in per-run appendices for reproducibility, never in the comparison.