Free 30-min Web3 Consultation
Book Now
Smart Contract Security Audits
Learn More
Custom DeFi Protocol Development
Explore
Full-Stack Web3 dApp Development
View Services
Free 30-min Web3 Consultation
Book Now
Smart Contract Security Audits
Learn More
Custom DeFi Protocol Development
Explore
Full-Stack Web3 dApp Development
View Services
Free 30-min Web3 Consultation
Book Now
Smart Contract Security Audits
Learn More
Custom DeFi Protocol Development
Explore
Full-Stack Web3 dApp Development
View Services
Free 30-min Web3 Consultation
Book Now
Smart Contract Security Audits
Learn More
Custom DeFi Protocol Development
Explore
Full-Stack Web3 dApp Development
View Services
LABS
Guides

How to Support Historical State Queries

A developer guide to implementing historical state queries for EVM and Solana blockchains. Covers archival nodes, indexing strategies, and verifying state with Merkle proofs.
Chainscore © 2026
introduction
BLOCKCHAIN DATA

Introduction to Historical State Queries

Learn how to access and verify the state of a blockchain at any point in its history, a fundamental capability for developers building advanced dApps and analytics tools.

A blockchain's state—the collective data of all account balances, smart contract storage, and nonces—is constantly updated with each new block. While the current state is readily available from any node, accessing the state as it existed at a historical block height is a more complex challenge. This capability, known as a historical state query, is essential for applications like on-chain analytics, dispute resolution, auditing smart contract interactions, and reconstructing transaction histories. Without it, verifying the conditions that led to a specific event becomes impossible.

Supporting these queries requires nodes to maintain more than just the latest state. Full nodes typically use a Merkle Patricia Trie (MPT) to store state data efficiently. To enable historical lookups, the node must archive the state root—the cryptographic hash representing the entire state—for every block. When you query for an account's balance at block #15,000,000, the node retrieves the state root stored at that block and traverses the corresponding historical trie to find and prove the value. Services like Chainscore provide indexed APIs that abstract this complexity, offering fast historical state queries without requiring you to run an archive node.

The most common method for accessing historical state is via the eth_getProof RPC method, introduced in EIP-1186. This method returns an account's state and a Merkle proof for a specified block. The proof allows you to cryptographically verify that the returned data is correct by checking it against the known state root from the block header. For example, to check USDC balance of an address on Ethereum Mainnet at block 18,000,000, you would call: eth_getProof(["0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"], ["0x0"], "0x112a880"). The response includes the storage value and the proof path.

Implementing this yourself requires running an archive node (like Geth with --gcmode=archive), which retains all historical state data, consuming significant storage (over 12TB for Ethereum). For most developers, using a dedicated provider like Chainscore, Alchemy, or Infura is more practical. Their APIs handle the infrastructure overhead, providing reliable historical state queries. When choosing a provider, consider factors like supported block ranges, rate limits, data freshness, and whether they offer pruned state (for recent blocks) or full archive access.

Practical use cases for historical state queries are extensive. They are used to: - Audit DeFi protocols by verifying liquidity and collateralization ratios at the time of a hack. - Resolve disputes in prediction markets or insurance contracts by proving on-chain conditions. - Calculate accurate APY for staking or lending by analyzing historical token distributions. - Power blockchain explorers that allow users to view any address at any past block. - Enable zero-knowledge proofs where a prover needs to validate a past state without trusting a third party.

When working with historical data, be aware of chain reorganizations (reorgs). A state query for a block that is later orphaned will return data that is no longer part of the canonical chain. For high-stakes applications, it's crucial to confirm the block's finality. On Ethereum, wait for at least 15 confirmations (approx. 3 minutes) for probabilistic finality, or use the finalized block tag ("finalized") in your RPC call where supported. Always verify the state root in the proof against the block header from a trusted source to ensure data integrity.

prerequisites
PREREQUISITES AND CORE CONCEPTS

How to Support Historical State Queries

Learn the foundational concepts and technical requirements for implementing historical state queries in blockchain applications.

A historical state query allows an application to retrieve the state of the blockchain—such as an account balance, smart contract storage value, or event log—at a specific block height in the past. This is distinct from querying the latest state. Supporting these queries is essential for analytics dashboards, block explorers, audit tools, and applications that need to verify past conditions, like proving a user held certain assets for an airdrop snapshot. The core challenge is that most blockchain nodes are optimized for the current state and do not retain the full historical data required for these lookups by default.

To enable historical queries, you must first understand the data structures involved. The state is typically stored in a Merkle Patricia Trie (MPT) for networks like Ethereum. Each block header contains a state root, which is a cryptographic commitment to the entire state trie at that block. To query a past state, you need the corresponding state root and the ability to traverse the historical trie. This requires access to archive node data, which retains all historical states, unlike full nodes which prune old state data. Services like Infura's Archive API, Alchemy's Enhanced APIs, or running a self-hosted archive node (e.g., Geth with --gcmode=archive) provide this foundational access.

Implementing the query logic involves interacting with the node's JSON-RPC API. For Ethereum-compatible chains, the primary method is eth_getProof. This method takes an account address, storage keys, and a block number as parameters. It returns a Merkle proof that can be used to verify the account's state and storage values against the known state root from that block's header. The proof's validity is checked by recomputing the Merkle root from the proof leaves and comparing it to the official state root. Libraries like ethers.js or web3.js can facilitate these calls, but understanding the low-proof verification is key for robust implementations.

A practical implementation often involves a multi-step process. First, fetch the target block header to obtain the stateRoot. Next, call eth_getProof for the desired account and storage slots at that block number. The node returns the account's nonce, balance, storageHash, codeHash, and any requested storage values along with sibling hashes for the proof. Your client must then verify this proof by hashing the provided data and traversing the Merkle tree structure. For efficiency, consider caching verified state proofs or using specialized indexers like The Graph, which can pre-index historical events and states into queryable subgraphs, abstracting away the direct RPC complexity.

Key prerequisites for developers include a strong grasp of Merkle tree cryptography, familiarity with your node provider's historical data capabilities, and understanding the performance trade-offs. Querying archive data is computationally and financially more expensive than querying latest-state endpoints. Always estimate costs and implement rate limiting and caching strategies. For production systems, evaluate using dedicated historical data services (TrueBlocks, Etherscan API) or middleware layers that manage state proofs, ensuring your application remains responsive and cost-effective while providing verifiable historical insights.

key-concepts
HISTORICAL DATA

Key Architectural Components

Efficiently querying past blockchain state requires specific architectural patterns. These components are essential for building performant explorers, analytics dashboards, and compliance tools.

05

Time-Series Databases & OLAP

Specialized databases (e.g., ClickHouse, TimescaleDB) optimized for aggregating and analyzing massive volumes of sequential blockchain data.

  • Performance: Execute analytical queries (SUM, GROUP BY over time) on billions of rows in seconds.
  • Typical Stack: Data is ETL'd from archival nodes into columnar storage.
  • Output: Powers dashboards showing metrics like historical TVL, transaction fee trends, and active address charts.
>1B rows
Typical Table Size
<100ms
Aggregation Query Speed
06

Checkpointing & State Snapshots

A technique to reduce historical query load by periodically saving and serving serialized state snapshots at specific block heights.

  • Mechanism: A node can save a full state object to disk every 10,000 blocks. Queries for intermediate states are derived by replaying forward from the nearest snapshot.
  • Benefit: Dramatically reduces disk I/O and speeds up queries for "recent history" (e.g., last 30 days).
  • Implementation: Used by Erigon and other clients to optimize resource usage.
evm-implementation
DEVELOPER GUIDE

Implementing Historical State Queries for EVM Chains (Geth, Erigon)

This guide explains how to implement and query historical state data on EVM clients like Geth and Erigon, a critical feature for analytics, debugging, and compliance.

Historical state queries allow you to retrieve the state of the Ethereum Virtual Machine—account balances, contract storage, and code—at any past block height, not just the latest one. This is essential for applications like block explorers, tax reporting tools, and on-chain analytics that need to reconstruct past conditions. Unlike standard RPC calls to eth_getBalance, which default to the "latest" block, historical queries require specifying an explicit block number or hash. Both Geth and Erigon support this functionality, but their underlying architectures and performance characteristics differ significantly.

Geth, the official Go implementation, stores historical state using a Merkle Patricia Trie and a state journal for recent blocks. To query old state, Geth must execute a process called state rewinding, which reconstructs the trie by replaying state changes from a persisted snapshot or the genesis block. This can be resource-intensive for very old blocks. Enable the --gcmode archive flag during node synchronization to store all historical state, but be prepared for massive storage requirements—often over 12 TB for mainnet. For queries, use the eth_getBalance, eth_getStorageAt, or eth_getCode RPC methods with a specific block parameter like "0x10d4c".

Erigon, a performance-focused client, uses a flat key-value storage model and stages synchronization. Its design allows for more efficient historical queries by storing state changes in a sequence of database 'layers'. Erigon can serve historical data significantly faster than Geth for many use cases and with lower storage overhead in archive mode (approximately 2.5 TB for mainnet). Use the same standard JSON-RPC methods. The key difference is enabling the --prune htc flag (history and transaction indexes) during initial sync to retain the necessary data, as Erigon prunes more aggressively by default.

For developers, implementing a query involves constructing a JSON-RPC request. Here is an example using curl to get an account's Ether balance at block 18,000,000: curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_getBalance","params":["0x742d35Cc6634C0532925a3b844Bc9e90F1A902e7", "0x112a880"], "id":1}' http://localhost:8545. Replace the address and block number (in hex) as needed. For programmatic access, use Web3 libraries like web3.js or ethers.js, where the block tag parameter is passed to all state-reading functions.

When choosing between clients, consider your application's needs. Geth's archive node is the most universally compatible but requires the most storage. Erigon's archive node is more storage-efficient and faster for historical reads but may have less ecosystem tooling. For a balanced approach, some services use a hybrid architecture: a primary Geth node for latest block processing and a separate Erigon archive node dedicated to serving historical queries. Always monitor node disk I/O and memory when serving frequent historical requests, as they are more demanding than queries against the latest state.

Advanced use cases, like tracing all state changes for a contract over time, require deeper integration. Tools like Erigon's erigon_watchTheBurn or the debug_traceBlockByNumber RPC method can provide this. Furthermore, indexing solutions like The Graph or TrueBlocks can pre-compute and cache historical state relationships off-chain, providing API endpoints that are more scalable for front-end applications than direct node queries. Always verify the historical data's consistency by comparing results from multiple nodes or using known checkpoints from block explorers.

solana-implementation
GUIDE

Implementing for Solana

This guide explains how to implement support for historical state queries on Solana, a critical feature for developers building analytics dashboards, explorers, and compliance tools.

Unlike Ethereum's archive nodes, Solana's architecture requires a different approach to accessing historical data. The core blockchain only retains recent state for performance, making direct queries for old account states or transaction details impossible without additional infrastructure. To support historical queries, you must interact with external data providers that index and store the chain's history. The primary methods are using RPC providers with extended capabilities or building your own indexing service using tools like Geyser or Helius.

For most developers, the fastest path is to use an RPC service that offers historical query endpoints. Services like Helius, Triton, and QuickNode provide enhanced APIs. For example, to fetch an account's state at a specific slot, you would use a provider-specific RPC call. A typical request to Helius might look like: curl https://api.helius.xyz/v0/accounts/...?api-key=YOUR_KEY. These services handle the complexity of data retention and indexing, offering endpoints for historical balances, transactions by address, and parsed transaction data.

For complete control or unique query needs, you can implement a custom indexer using Solana's Geyser plugin interface. A Geyser plugin allows your program to subscribe to real-time updates from a validator, capturing accounts, slots, and transactions as they are confirmed. You would write a plugin (in Rust or C++) that streams this data to your own database (e.g., PostgreSQL or TimescaleDB). The core steps involve: implementing the GeyserPlugin trait, connecting to a local validator with the --geyser-plugin-config flag, and designing a schema to store the historical state efficiently.

When designing your query layer, consider the data access patterns. Common historical queries include: getAccountHistory(address, startSlot, endSlot), getSignaturesForAddress(address, before, limit), and getTransactionWithMeta(signature). Your API or database must be optimized for time-range scans on slot numbers. Using a columnar database or indexing slot and write_version fields is essential for performance. Remember that account data is stored as raw byte arrays; you'll need the appropriate program-derived-address (PDA) seeds and deserialization logic to interpret it.

Always verify the integrity of historical data. When using an external provider, check their data sourcing and consensus alignment. For custom indexers, implement a verification routine that cross-references hashes with the ledger or uses light client proofs. Tools like solana-ledger-tool can help verify slot ranges. Historical state is foundational for audits, tax reporting, and advanced DeFi analytics; ensuring its accuracy is non-negotiable for production systems.

ARCHIVAL STRATEGIES

Node Type Comparison for Historical Data

Comparison of node configurations for accessing historical blockchain state, including transaction data, logs, and contract storage.

Feature / MetricFull Archive NodePruned NodeExternal Indexer (e.g., The Graph)

Historical State Access

Historical Transaction Data

Historical Event Logs

Storage Requirements

2 TB (Ethereum)

~ 500 GB

< 100 GB

Query Latency

< 100 ms

N/A

50-200 ms

Setup & Maintenance

Complex, self-hosted

Moderate, self-hosted

Managed service or self-hosted subgraph

Data Freshness

Real-time

Real-time

Indexing lag (1-2 blocks)

Query Flexibility

Full RPC access

Limited to recent state

Structured by subgraph schema

Typical Cost (Monthly)

$200-500 (infra)

$50-150 (infra)

$0-200+ (service fees)

indexing-strategies
GUIDE

How to Support Historical State Queries

Historical state queries allow applications to access blockchain data from any past block, enabling analytics, audits, and complex transaction simulations. This guide covers indexing strategies and optimization techniques for building efficient historical data systems.

Supporting historical state queries requires a fundamental shift from the typical latest-state model used by most nodes. A standard Ethereum execution client, for instance, uses a Merkle Patricia Trie where only the current state root is stored; past states are pruned to save space. To query a balance or contract storage slot from block #15,000,000, you cannot simply ask the node—it lacks the data. The core challenge is designing a system that captures, stores, and efficiently retrieves this temporal data. Solutions typically involve creating a dedicated indexing layer that processes and persists state changes from every block.

The most common architectural pattern is the state diffs indexer. Instead of storing full state snapshots for every block (which is prohibitively expensive), this method records only the changes, or diffs, between blocks. For example, when a transaction modifies a StorageSlot from value A to value B at block N, the indexer stores the tuple (block_number, contract_address, slot, old_value, new_value). To reconstruct the state for any historical block, the system replays these diffs forward from a known snapshot (a checkpoint) or backward from the current state. This approach balances storage efficiency with query performance.

Optimizing these systems involves several key strategies. First, checkpointing is critical: periodically taking and compressing a full state snapshot (e.g., every 10,000 blocks) drastically reduces the number of diffs that must be replayed for a query. Second, columnar database design using systems like Apache Parquet or ClickHouse allows for efficient compression and fast analytical queries over massive datasets of state changes. Third, pre-computing and caching frequent query patterns, such as an address's historical token holdings, can reduce latency. Finally, leveraging RPC methods like eth_getProof for specific blocks can help validate historical state without maintaining a full index.

For developers, implementing a basic historical state query endpoint involves listening to blockchain events. Using Ethers.js and a node provider, you can process logs to build a simple index. However, for production systems at scale, consider specialized solutions. The TrueBlocks toolkit creates local indexes of every appearance of an address. For Ethereum, Erigon's archive node mode retains all historical state, and its RPC daemon can serve historical calls. The Chainstack platform also offers historical state APIs, abstracting the infrastructure complexity. The choice between building and buying depends on your required query latency, data granularity, and maintenance resources.

When designing your system, prioritize data integrity. Always verify historical state against known block hashes using cryptographic proofs where possible. Be mindful of chain reorganizations; your indexing logic must handle orphaned blocks by invalidating or updating diffs. Storage costs will be your primary constraint—archiving all state diffs for Ethereum can require tens of terabytes. A practical approach is to start by indexing only the specific contracts and events relevant to your application, rather than the entire chain state, and expand coverage as needed.

verification-proofs
ARCHIVAL NODE FUNCTIONALITY

Verifying Historical State with Proofs

Learn how to query and cryptographically verify the state of a blockchain at any point in its history using Merkle proofs and archival nodes.

Blockchains are often described as immutable ledgers, but standard full nodes only store the current state and a limited history of recent blocks. To query the state of a smart contract—like an account balance or a stored variable—from a past block, you need access to historical state data. This is the domain of archival nodes, which retain the full history of all states. However, trusting a single node's response is insufficient for decentralized verification. This is where cryptographic proofs, specifically Merkle Patricia Trie proofs, become essential for trustless validation of historical information.

The core mechanism enabling this verification is the state root. For each block, Ethereum and other EVM chains compute a cryptographic hash (the state root) that commits to the entire global state. This root is stored in the block header. The state is organized as a Merkle Patricia Trie, where each piece of data (e.g., an account's storage slot) has a unique path. To prove a value existed at a certain block, a node provides a merkle proof: the series of hashes needed to reconstruct the path from the value to the state root. By recomputing the root from the proof and comparing it to the one in the trusted block header, a client can verify the data's authenticity without storing the entire state history.

To perform a verification, you need two things: a trusted block header (whose hash is confirmed by consensus) and the merkle proof for your query. Libraries like @ethereumjs/trie can verify these proofs. For example, to verify a historical ERC-20 balance, you would query an archival node's RPC method like eth_getProof for the account and storage slot at a specific block number. This returns the account proof and storage proof. Your client code then verifies these proofs against the known state root from that block.

Developers interact with this primarily through RPC calls. The eth_getProof method, specified in EIP-1186, is the standard for fetching proofs. A call returns the account's nonce, balance, storageHash, codeHash, and any requested storage values, along with the merkle proofs needed to verify each piece of data. Light clients and bridges use this mechanism for secure, trust-minimized cross-chain communication, proving that an event occurred or an asset was locked on another chain.

Implementing verification requires careful handling. You must ensure the block header you're using is finalized and canonical. For Ethereum, you can retrieve historic block headers from a trusted beacon chain light client or a service like the eth_getBlockByNumber RPC. The verification logic must also handle the specific trie format (e.g., Hexary Patricia Trie for Ethereum). Incorrect proof verification is a major security risk, so using audited libraries is critical. This capability forms the foundation for advanced use cases like historical data oracles, proof-of-reserves, and stateless client protocols.

tools-resources
HISTORICAL STATE QUERIES

Tools and External Resources

Accessing historical blockchain data requires specialized tools. This section covers the primary methods and services for querying past state.

HISTORICAL STATE

Frequently Asked Questions

Common questions and solutions for developers working with historical blockchain data queries using Chainscore's APIs.

Historical state refers to the complete state of a blockchain—account balances, smart contract storage, and event logs—at a specific block height in the past. Querying it is difficult because most RPC nodes (like Alchemy, Infura) only provide access to the current, latest state. Reconstructing past states requires replaying all transactions from genesis to the target block, which is computationally intensive and requires specialized archival infrastructure.

Chainscore solves this by maintaining indexed, queryable snapshots of historical state, allowing you to fetch data from any block with a simple API call, bypassing the need for a full archival node.

conclusion
IMPLEMENTATION GUIDE

Conclusion and Next Steps

You have learned the core concepts and methods for supporting historical state queries in your blockchain application. This section summarizes the key takeaways and provides a path forward for implementation and further learning.

Supporting historical state queries is a critical feature for applications requiring auditability, data analysis, or complex user interactions. The primary methods are on-chain archival nodes, indexing services, and decentralized storage solutions. Each approach offers a different trade-off between decentralization, cost, query speed, and data availability. For example, running your own archival Geth node provides maximum data sovereignty but requires significant infrastructure, while using a service like The Graph offers fast, flexible queries at the cost of relying on a third-party indexer.

Your implementation path depends on your specific needs. For a dApp needing frequent, complex historical queries (e.g., a portfolio tracker), integrating with an indexer is often the most practical first step. If you require guaranteed data permanence for legal or compliance reasons, archiving snapshots to IPFS or Arweave should be part of your strategy. For protocol developers, building events with rich, standardized data (using the Ethereum ABI-encoded event format) is essential to make future historical analysis efficient and reliable for all users of your contracts.

To proceed, start by auditing your smart contracts. Ensure all state changes of interest emit descriptive events. Next, prototype a query using a service like The Graph's hosted service or Covalent's API. For a more decentralized approach, explore running a light client with an archive service flag or contributing to an indexer subgraph. The ecosystem tools are mature; the key is to integrate historical data queries into your application's design from the beginning, rather than treating them as an afterthought.

How to Support Historical State Queries in Blockchain | ChainScore Guides