DEV Community

OpenChainBench
OpenChainBench

Posted on

The Archive Multiplier: Why eth_call at a Historical Block

TL;DR: Passing a historical blockNumber to eth_call, eth_getBalance or eth_getLogs silently routes your request to the archive tier of hosted RPC providers. In our production metrics, archive calls cost on average 26.7x more compute units than the same call at latest. This post explains why, shows the exact client code pattern that triggers it, and gives you three Prometheus queries to measure your own archive exposure in under a minute. Full cross provider measurements are published in the OpenChainBench RPC benchmarks.

Last week our RPC cost dashboard flagged an overage projection above four thousand dollars for a single billing cycle on a single provider. On paper, our services were doing normal eth_call operations. In practice, one small pattern buried in three separate indexers had multiplied our compute unit consumption by more than an order of magnitude, and nothing in the code review process had surfaced it.

This post breaks down what an archive multiplier is, why it silently inflates blockchain RPC bills across every major hosted provider, and how to detect it in your own Prometheus stack before the next overage alert lands in Slack.

What does "archive" mean at the Ethereum node level?

Every request that reads the state of a smart contract, whether through eth_call, eth_getBalance, eth_getStorageAt, eth_getCode, or a batch of these, requires the RPC node to reconstruct the world state at a specific block height.

Ethereum clients handle this in two modes.

Full node mode. The state trie is kept in memory or on fast SSD for the tip of the chain plus a rolling window of recent blocks. On Geth default settings that window is 128 blocks deep. Any query targeting latest, pending, or a block within that window resolves in a few milliseconds against the current state.

Archive node mode. The client preserves every intermediate state trie since genesis. Answering a query at a block from months or years ago requires reading historical trie data off disk and reconstructing the state at that point. This is orders of magnitude slower and dramatically more storage intensive. A mainnet Ethereum archive node currently requires around twenty terabytes of NVMe storage. A full node runs on about one and a half terabytes.

Hosted providers price this operational difference into their compute unit tables. The multiplier is not a bug in your billing. It reflects the real infrastructure cost of preserving all historical state.

The exact client code that triggers archive pricing

A typical viem call to read a token balance looks like this:

import { createPublicClient, http } from 'viem'
import { mainnet } from 'viem/chains'

const client = createPublicClient({
  chain: mainnet,
  transport: http(process.env.RPC_URL)
})

// Path A, cheap: reads the current state.
const balance = await client.readContract({
  address: TOKEN,
  abi: erc20Abi,
  functionName: 'balanceOf',
  args: [WALLET]
})

// Path B, expensive: reads state at a specific historical block.
const historicalBalance = await client.readContract({
  address: TOKEN,
  abi: erc20Abi,
  functionName: 'balanceOf',
  args: [WALLET],
  blockNumber: 18000000n
})
Enter fullscreen mode Exit fullscreen mode

From a developer perspective the two calls are indistinguishable. Both return a bigint. The blockNumber parameter on the second call is what promotes the request to the archive tier on the provider side.

In our codebase we found this pattern in five categories of code paths:

  1. Token balance backfills rebuilding historical wallet snapshots for analytics. Each snapshot iterates through blocks, calling balanceOf at each snapshot point.
  2. Pool state reconstruction for liquidity analytics, calling getReserves() on Uniswap v2 pools or slot0() on Uniswap v3 pools at every historical trade block.
  3. Bridge quote engines that need the exact token balance and allowance at the source block when validating a cross chain transfer.
  4. Governance snapshots replaying votes at a historical block to verify quorum.
  5. Audit tools and debuggers recomputing contract state at the block of a suspicious transaction to isolate the root cause of a bug.

None of these paths look expensive when you read them. They pass a single extra parameter to a familiar function call. But every one of those calls goes through the archive tier on the RPC provider.

How Alchemy, Chainstack and QuickNode price archive requests

Each provider publishes a compute unit or credit table in its public pricing documentation.

Alchemy documents its compute unit weights in the Alchemy compute units reference. The archive tier does not appear as a separate multiplier in the table itself. Instead, the archive rate applies once a request targets a block older than 128 blocks, and the effective cost per call in this mode is measured against the archive plan tier.

Chainstack uses Request Units and prices archive traffic at approximately double the standard rate on shared nodes. Enterprise archive dedicated nodes have a flat pricing model instead of per request billing.

QuickNode uses Credits per method. Archive calls carry a multiplier documented per method in the QuickNode API credits reference, typically between two and five times the equivalent full node call.

The observable behavior is consistent across all three. If you send the same request payload with and without a historical blockNumber parameter, the request without it hits the fast path and the request with it hits a slower and more expensive path. On heavily loaded indexers this compounds fast, because each iteration of a backfill loop produces one archive request.

What we measured on a production grade cluster

We instrument every outbound RPC call in our services with a Prometheus counter carrying labels for provider, method, chain, and node type. Over a rolling 24 hour window on one chain that receives moderate traffic, the raw breakdown was as follows:

Method Raw requests Compute units Effective ratio
eth_call archive 2,470,000 65,970,000 26.7
eth_getLogs archive 355,000 9,500,000 26.8
eth_getBalance archive 49,200 1,315,000 26.7
eth_call full 4,000 4,000 1.0

The pattern is clean. Full mode calls average one compute unit per raw request. Archive mode calls on the same methods average close to twenty seven compute units per raw request. The ratio is stable across eth_call, eth_getLogs, and eth_getBalance, which are the three read paths that carry the vast majority of our traffic.

Translated into monthly cost on a moderately busy indexer on one chain, the archive share of that traffic drove roughly one thousand dollars of overage per month. Multiplied across five chains and three indexer instances, the number grows quickly into the tens of thousands per year.

How to detect archive exposure with 3 Prometheus queries

If you already record outbound RPC calls into a Prometheus counter, the following queries surface archive exposure in about thirty seconds.

Query 1. Absolute compute units by node type over 24 hours:

sum by (node_type) (
  increase(rpc_compute_units_total{provider="alchemy"}[24h])
)
Enter fullscreen mode Exit fullscreen mode

Query 2. Archive share of total volume as a percentage:

sum(rate(rpc_compute_units_total{provider="alchemy", node_type="archive"}[1h]))
/
sum(rate(rpc_compute_units_total{provider="alchemy"}[1h]))
* 100
Enter fullscreen mode Exit fullscreen mode

Query 3. Top ten source services and methods contributing to archive volume:

topk(10,
  sum by (app, method) (
    increase(rpc_compute_units_total{provider="alchemy", node_type="archive"}[24h])
  )
)
Enter fullscreen mode Exit fullscreen mode

How to read the results:

  • Below 5 percent archive share: you are in good shape.
  • Between 5 and 20 percent: a specific backfill or indexer is doing more archive reads than it needs to, and the top ten query above will point at the culprit within seconds.
  • Above 50 percent: archive is your primary traffic pattern and the cost curve is dominated by the multiplier rather than by request volume, which usually means a stalled or looping job.

If you do not yet track this metric, the counter you want to add to your outbound HTTP transport takes labels for provider, method, chain, and node_type. Increment it once per response received, including 4xx and 5xx responses, because hosted providers bill errored calls just as they bill successful ones.

For a full working example of this instrumentation across twenty two chains and three geographic regions, the OpenChainBench RPC latency benchmarks publish the archive share of measured providers alongside p50, p95, and p99 latency. The instrumentation code that produces those metrics is open source on GitHub and the full approach is documented on the OpenChainBench methodology page.

5 strategies to reduce archive RPC costs in production

Once you have detected the pattern in your own metrics, five interventions consistently cut archive cost in production without changing product behavior.

1. Query at latest when the caller has no reason to pin a block. Audit every code path that passes a blockNumber parameter to readContract, getBalance, or getLogs. In a surprising fraction of cases the parameter was added defensively, without a functional requirement. Removing it drops the call from archive to full and eliminates the multiplier entirely.

2. Cache archive responses aggressively. An archive response for a finalized block is immutable. If your service asks for eth_call at block 18000000 today and again next week, the two responses are byte for byte identical. Wrapping archive calls in a Redis or Postgres cache with a TTL measured in months collapses repeat calls to a single provider hit.

3. Batch archive calls through Multicall3. If an indexer needs one hundred balanceOf reads at the same historical block, wrapping them in a single call to the Multicall3 contract at 0xcA11bde05977b3631167028862bE2a173976CA11 reduces the provider side accounting to one archive request instead of one hundred.

4. Materialize derived state in your own datastore. If your service repeatedly recomputes the same derived quantity from historical state, for example a token holder set at block N, write the result to your own Postgres or ClickHouse the first time and read from there for every subsequent access.

5. Split archive traffic onto a provider with a lower multiplier. The three providers price archive differently. If your workload is archive dominated, running a benchmark against Chainstack, QuickNode, and a self hosted archive node with the same request pattern will reveal cost differences worth thousands of dollars per month at production scale. The live RPC provider comparisons on OpenChainBench publish latency and error rate data across those providers so you can weigh cost against performance before switching.

The observability gap in current web3 SDKs

The reason this pattern persists in real codebases is that the archive tier is invisible from the SDK perspective. Viem, ethers, and web3.js expose blockNumber as a routine parameter with no warning that it changes the pricing tier on the RPC side. The provider knows, the developer does not, and the metric never surfaces until the monthly bill arrives.

Client side instrumentation with a node_type label is currently the only reliable way to close this gap. If you already use an eRPC middleware or a custom HTTP transport wrapper, adding the label at request emission time takes about thirty lines of code. Once the label is in place, alerting on archive share crossing a threshold becomes a one line Prometheus rule.

FAQ

What is an archive node in Ethereum?
An archive node stores every intermediate state trie since the genesis block, which allows it to answer state queries at any historical block height. It requires roughly 20 TB of NVMe storage on mainnet, compared to about 1.5 TB for a full node.

Why does eth_call at a historical block cost more?
Because the node must read historical trie data from disk and reconstruct the world state at that block, instead of serving the query from the hot state kept in memory for recent blocks. Hosted providers pass that infrastructure cost through as a compute unit multiplier.

How old does a block need to be to trigger archive pricing?
On most providers, any block older than the client's recent state window, which is 128 blocks on Geth default settings, is served from archive state and billed at the archive rate.

How can I tell how much of my RPC bill comes from archive calls?
Instrument your outbound RPC transport with a Prometheus counter labeled by provider, method, chain, and node type, then compute the archive share with the queries in this article. Independent cross provider archive measurements are also published in the OpenChainBench benchmarks.

Are archive responses safe to cache?
Yes, for finalized blocks. State at a finalized block is immutable, so responses can be cached indefinitely with no staleness risk.

Key takeaways

The archive multiplier is not a design flaw of hosted RPC providers. It reflects the real cost of preserving twenty terabytes of historical state on hot storage and answering queries against it. The problem is that the mechanism is invisible from client code, so the cost accumulates behind an opaque call that looks routine in a code review.

Three actions pay off immediately:

  1. Instrument outbound RPC calls with a Prometheus counter that includes a node_type label.
  2. Alert when the archive share of your total volume on any provider crosses five percent.
  3. Audit every code path that passes blockNumber and either remove the parameter, cache the response, or batch through Multicall3.

Once the three are in place, the next overage alert arrives with the root cause already visible in your dashboard rather than requiring an emergency investigation across five services and two Postgres replicas.

If you want to see the full instrumentation applied to twenty two chains from three regions, OpenChainBench publishes the live results under an open methodology, with data under a Creative Commons license and the harness source on GitHub.


Originally published on the OpenChainBench blog. Benchmarks, methodology and raw data are available at openchainbench.com.

Top comments (0)