To query historical blockchain data via RPC, you need an archive node for state at past blocks (eth_call, eth_getBalance, eth_getCode, eth_getStorageAt) or a full node for historical blocks and logs (eth_getBlockByNumber, eth_getLogs). For large-scale scans, use an indexer or trace API. This guide explains the differences and provides runnable examples.
The Short Answer: Full vs Archive vs Trace Nodes
When you need historical blockchain data via RPC, the first decision is which node type to use. A full node stores every block and transaction but only the latest state (account balances, contract storage). An archive node stores all historical state snapshots, enabling queries like "what was the balance of this address at block 10,000,000?" A trace node additionally stores execution traces, enabling deep replay of transactions and state diffs. For most historical queries, you need an archive node; for block and log data, a full node suffices.
This distinction is critical because RPC methods like eth_getBalance and eth_call accept a block parameter. On a full node, passing a past block returns an error or incorrect data because the state is pruned. On an archive node, the same call returns the exact historical value. See our archive node vs full node guide for a deeper dive.
- Full node: stores all blocks, transactions, receipts, and the latest state only.
- Archive node: stores all historical state snapshots, enabling state queries at any block.
- Trace node: stores execution traces, enabling replay and deep analysis (e.g., Parity trace module).
Understanding Node Pruning and State Availability
Ethereum clients like Geth and Nethermind prune historical state by default. They keep only the latest state trie and a limited set of recent states (e.g., 128 blocks). This means eth_getBalance with a past block parameter will fail on a full node. The exact error varies: Geth returns "missing trie node" or "header not found", while Nethermind may return "Cannot read state at block ...".
Archive nodes disable pruning, storing every state snapshot. This requires significantly more disk space—hundreds of gigabytes to terabytes. For example, Ethereum archive nodes can exceed 2 TB as of 2026. Providers like OnFinality's RPC service offer archive endpoints for Ethereum and other networks, so you don't have to run your own.
When you query a public RPC endpoint, you don't know if it's archive or full. Always check the documentation or test with a known historical state. For instance, query the balance of a well-known address at an old block and compare with a block explorer.
curl -X POST https://eth-mainnet.public.blastapi.io -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_getBalance","params":["0x742d35Cc6634C0532925a3b844Bc454e4438f44e", "0x5F5E100"],"id":1}'Querying Historical Blocks and Transactions
Historical block and transaction data is available on any full node. eth_getBlockByNumber returns the block header, transactions, and optionally full transaction objects. eth_getTransactionByHash and eth_getTransactionReceipt work for any past transaction. These methods do not require archive state.
For example, to fetch block 15,000,000 (0xE4E1C0) with full transactions:
This is useful for auditing, analytics, and syncing applications. However, for large-scale scans (e.g., all transfers of a token), using eth_getLogs is more efficient than iterating over blocks.
curl -X POST https://eth-mainnet.public.blastapi.io -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["0xE4E1C0", true],"id":1}'Querying Historical State: eth_call, eth_getBalance, eth_getCode, eth_getStorageAt
To query state at a past block, you need an archive node. The key methods are:
eth_getBalance(address, block)– balance at a given block.
eth_getCode(address, block)– contract bytecode at a given block.
eth_getStorageAt(address, slot, block)– storage value at a given slot.
eth_call({to, data}, block)– simulate a call at a past block, useful for historical contract reads.
These methods accept a block parameter as a hex number, tag ("latest", "earliest", "pending"), or block hash. On an archive node, they return the exact historical value.
Example: Get the balance of the Ethereum Foundation wallet at block 10,000,000 (0x989680):
If you get an error like "missing trie node", the node is not archive. You need to switch to an archive endpoint. OnFinality provides archive endpoints for Ethereum and other networks; see our Ethereum network page for details.
curl -X POST https://eth-mainnet.public.blastapi.io -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_getBalance","params":["0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae", "0x989680"],"id":1}'Using eth_getLogs for Historical Event Data
eth_getLogs is the workhorse for querying historical event logs (e.g., token transfers, DEX trades). It works on full nodes because logs are stored in receipts, which are kept indefinitely. You can filter by address, topics, and block range.
Example: Get all USDT Transfer events (contract 0xdAC17F958D2ee523a2206206994597C13D831ec7) from block 15,000,000 to 15,000,100:
Note that eth_getLogs has limitations: most providers cap the block range (e.g., 10,000 blocks) and the response size. For large historical scans, use an indexer like The Graph or a dedicated data service. See our multi-chain RPC endpoints guide for provider options.
curl -X POST https://eth-mainnet.public.blastapi.io -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_getLogs","params":[{"fromBlock":"0xE4E1C0","toBlock":"0xE4E1C4","address":"0xdAC17F958D2ee523a2206206994597C13D831ec7","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"]}],"id":1}'Trace APIs for Deep Historical Analysis
For transaction-level replay, state diffs, and internal calls, you need a trace node with the trace_ module (Parity/OpenEthereum) or debug_ module (Geth). These are not standard JSON-RPC methods but are available on some providers.
Example: trace_replayTransaction returns all sub-calls and state changes for a transaction. This is useful for reconstructing historical state changes, but it's computationally expensive and often rate-limited.
Trace data is essential for indexers and analytics platforms. If you need this, ensure your RPC provider supports trace methods. OnFinality's API service offers trace endpoints for supported networks.
curl -X POST https://rpc.trace.example.com -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"trace_replayTransaction","params":["0xhash", ["trace", "stateDiff"]],"id":1}'Common Errors and Troubleshooting
When querying historical data, you may encounter specific errors. Here's a checklist:
"missing trie node"or"header not found"– the node is not archive. Use an archive endpoint.
"block not found"– the block number is out of range or the node is not synced.
"execution reverted"– the call reverted at that block; check the contract logic.
"query returned more than 10000 results"– reduce the block range ineth_getLogs.
"rate limit exceeded"– you're hitting provider limits; consider batching or using a dedicated endpoint.
For performance, use JSON-RPC batching best practices to combine multiple queries into one request.
- Always test with a known historical value to verify archive support.
- Use block numbers instead of timestamps for precision.
- For large scans, use an indexer or export data via a data service.
Tradeoffs: Full vs Archive vs Indexers
Choosing the right tool depends on your use case:
- Full node: cheapest, good for current state and recent history, but no historical state.
- Archive node: expensive, required for historical state queries, but limited by RPC rate limits and block range caps.
- Indexer (The Graph, SubQuery): best for large-scale historical queries, but requires setup and indexing time.
For occasional historical queries, an archive RPC endpoint is sufficient. For production analytics, consider an indexer. See our blockchain node hosting guide if you want to run your own node.
- Archive nodes are 10-100x larger than full nodes.
- RPC providers often charge more for archive access.
- Indexers provide GraphQL APIs for complex queries.
Next Steps and Further Reading
Now that you understand how to query historical data, you can start building. For Ethereum, test with a public archive endpoint or use OnFinality's Ethereum RPC. For Polkadot, historical queries work differently; see our Polkadot network page.
If you're building an application that needs reliable historical data, consider using a managed RPC service to avoid node maintenance. Check our pricing for archive plans.
For more RPC best practices, read our RPC endpoints guide and monitoring RPC endpoints.