Logo
New RPC users get 35% off their first monthView the offer
OnFinality Learn
Network & Protocol Guides12 min read

Ethereum Archive Node and Historical RPC: Querying Past Balances, Logs, and State

Learn what an Ethereum archive node stores, how to query historical balances, logs, and state via RPC, and how to verify your endpoint actually serves archive data.

TL;DR

This guide explains what an Ethereum archive node is, how it differs from a pruned full node, and how to query historical state and logs via RPC. It covers the relevant JSON-RPC methods, provides a runnable Node.js script to test archive support, and includes a checklist to verify your endpoint.

Direct Answer: What You Need to Query Historical Ethereum Data

To query historical Ethereum balances, code, storage, or logs, you need an RPC endpoint backed by an archive node—or a node that exposes state history via Erigon's debug/ots_ methods. A standard full node only keeps the latest state and can only answer eth_getBalance at the latest block. Archive nodes retain all historical state, enabling queries like eth_getBalance at block 1,000,000. This guide explains the mechanics, shows you how to test any endpoint, and provides a reproducible script.

  • Archive node: stores full state history, enabling queries at any past block.
  • Full node: prunes historical state, only serves latest state.
  • Block tags: 'latest', 'earliest', 'pending', or a hex block number.
  • Erigon archive nodes also expose state-history RPC methods (debug_ and ots_).

Ethereum Archive Node vs Full Node: What Data Is Kept

Ethereum execution clients (geth, Erigon, Nethermind, Besu) all expose the same JSON-RPC surface, but the data they can serve depends on the node's sync mode. A full node (geth default) downloads and validates all blocks but prunes historical state trie nodes, keeping only the latest state. An archive node (geth --syncmode full --gcmode archive) retains every state trie node, allowing queries at any historical block.

Erigon's archive mode is more granular: it stores state history as a series of state diffs, enabling not only eth_getBalance at old blocks but also specialized methods like debug_traceTransaction and ots_getTransactionOutput. The Ethereum Foundation's documentation on archive nodes and the JSON-RPC specification are the primary protocol references; Erigon's docs and independent tutorials (e.g., M. Hansson's Erigon guide) provide reader-facing details.

  • geth full node: prunes state, serves only latest state.
  • geth archive node: keeps all state, serves any block.
  • Erigon archive: stores state history, supports additional debug/ots methods.
  • Archive mode is a startup configuration, not an RPC parameter.

RPC Methods for Reading Historical Data

The core JSON-RPC methods for historical queries are eth_getBlockByNumber, eth_getBalance, eth_getCode, eth_getStorageAt, eth_getTransactionCount, eth_getProof, and eth_getLogs. Each accepts a block parameter that can be a block number, hash, or tag. For example, eth_getBalance with block tag '0x0' returns the balance at genesis. The method set and their semantics are documented in the official Ethereum JSON-RPC specification, and the data depth behind them is explained in the official Ethereum archive-node guide.

eth_getProof (EIP-1186) returns an account's balance, code hash, storage root, and Merkle proof at a given block, useful for verifying historical state. eth_getLogs filters logs by address and topics across a block range, which is essential for indexing historical events.

Erigon archive nodes additionally expose debug_ and ots_ methods (e.g., ots_getTransactionOutput, debug_traceBlockByNumber) that reconstruct state or trace transactions historically. These are not part of the standard Ethereum JSON-RPC but are documented by Erigon.

  • eth_getBlockByNumber: fetch block header and transactions.
  • eth_getBalance / eth_getCode / eth_getStorageAt / eth_getTransactionCount: read state at a block.
  • eth_getProof: get Merkle proof at a block (EIP-1186).
  • eth_getLogs: query logs across a block range.
  • Erigon debug_/ots_: state history and tracing.

Key Subtlety: Archive Is a Node Mode, Not an RPC Parameter

A common misconception is that you can request historical data from any node by specifying a block number. In reality, the node must have the historical state stored. If you call eth_getBalance with an old block on a full node, it returns an error like 'missing trie node' or 'historical state not available'. Archive mode is set at node startup; you cannot switch it on the fly.

Therefore, when choosing an RPC provider, you must verify that the endpoint is backed by an archive node. Providers like Chainstack, Infura, QuickNode, and OnFinality offer archive endpoints, but retention and availability vary. Always check the provider's documentation for archive support and block range.

  • Full node: eth_getBalance at old block → error.
  • Archive node: eth_getBalance at old block → correct value.
  • Provider archive endpoints: documented, but retention varies.
  • Always verify with a test query.

Runnable Example: Test Your Endpoint for Archive Support

The following Node.js script uses ethers v6 to (a) resolve a historical block, (b) read eth_getBalance at that block and at latest, and (c) page eth_getLogs across a bounded window with backoff. Replace the RPC_URL with your endpoint. The script prints results and a verdict on whether the endpoint serves historical state.

Expected output: if the endpoint is archive, the balance at block 1,000,000 will be a number (possibly 0), and the latest balance will differ. If not archive, the historical call will throw an error.

const { ethers } = require('ethers');

const RPC_URL = 'https://your-rpc-endpoint.example';
const provider = new ethers.JsonRpcProvider(RPC_URL);

async function testArchive() {
  const address = '0x0000000000000000000000000000000000000000'; // zero address
  const historicalBlock = 1000000; // block 1,000,000

  try {
    const historicalBalance = await provider.getBalance(address, historicalBlock);
    console.log(`Balance at block ${historicalBlock}: ${historicalBalance.toString()}`);
  } catch (e) {
    console.log('Historical balance failed:', e.message);
    console.log('Endpoint likely NOT archive.');
    return;
  }

  const latestBalance = await provider.getBalance(address, 'latest');
  console.log(`Balance at latest: ${latestBalance.toString()}`);

  // eth_getLogs paging example
  const filter = {
    address: '0x0000000000000000000000000000000000000000',
    fromBlock: 1000000,
    toBlock: 1000100
  };
  try {
    const logs = await provider.getLogs(filter);
    console.log(`Logs in range: ${logs.length}`);
  } catch (e) {
    console.log('getLogs failed:', e.message);
  }
}

testArchive();

Results Table and Archive Checks Checklist

Run the script against your endpoint and fill in the table below. The 'Archive?' column should be 'Yes' if the historical balance call succeeds, 'No' if it throws an error, and 'Unknown' if the error is ambiguous (e.g., rate limit).

  • Endpoint URL: ______
  • Historical balance (block 1,000,000): ______
  • Latest balance: ______
  • Historical call succeeded? Yes/No
  • Logs returned? Yes/No
  • Archive? Yes/No/Unknown

Common Failures and Fixes

When querying historical data, you may encounter errors. Here are common ones and how to fix them.

Error 'missing trie node' or 'historical state not available' means the node is not archive. Switch to an archive endpoint. Error 'block not found' may mean the block number is invalid or the node is not fully synced. Error 'rate limit exceeded' means you hit the provider's rate limit; implement backoff or use a higher-tier plan.

  • Missing trie node → use archive endpoint.
  • Block not found → check block number and sync status.
  • Rate limit → add retry with exponential backoff.
  • eth_getLogs timeout → reduce block range and paginate.

Tradeoffs and Limitations

Archive nodes require significantly more disk space and memory than full nodes. Exact sizes vary by client and time; as of 2026, an Ethereum archive node can require several terabytes, but this is documented as 'varies by provider' and changes over time. Providers often charge more for archive access due to infrastructure costs.

Historical queries are slower than latest-state queries because they require traversing older state tries. Latency varies by provider and network conditions; we do not assert specific numbers. Additionally, eth_getLogs over large ranges can be resource-intensive; providers may limit range size or require pagination.

  • Disk space: archive nodes need more storage (varies by provider).
  • Cost: archive endpoints are typically more expensive.
  • Performance: historical queries are slower.
  • Provider limits: log ranges and rate limits vary.

Next Steps and Further Reading

Now that you understand Ethereum archive nodes and historical RPC, explore related guides to deepen your knowledge. For network-specific details, see the Ethereum network page. For general historical data techniques, read the EVM historical data cookbook and the archive node vs full node comparison.

If you're choosing an RPC provider, review Ethereum RPC node types and RPC pricing. For performance considerations, see the Ethereum RPC latency guide and rate limits and 429s. Finally, explore the API service for managed endpoints.

Never Worry about Infrastructure Again

OnFinality takes away the heavy lifting of DevOps so you can build smarter and faster.

Get Started