This guide explains how to query historical Base state and logs using OP-Stack execution JSON-RPC. It covers archive node mechanics on Base, the difference between full and archive nodes, and provides a runnable Node.js script to test an endpoint's historical data support. It also includes troubleshooting tips and tradeoffs.
Direct Answer: How to Query Historical Base State and Logs
To query historical Base state and logs, you need an archive node or an RPC provider that offers archive data. Base is an OP-Stack optimistic L2, and its execution layer (op-geth or op-reth) serves Ethereum JSON-RPC. Historical reads such as eth_getBalance at an old block, eth_call at a past block, eth_getLogs over a past range, and eth_getProof for state proofs all require the execution client to have retained the state for that block. A full (pruned) node only keeps recent state, while an archive node stores all historical state. This guide explains the mechanics and provides a script to test any Base RPC endpoint's archive support.
If you are looking for a managed Base archive endpoint, see the Base node infrastructure (RPC Assistant) page. For general background on archive vs full nodes, see Archive node vs full node.
- Base is an OP-Stack L2: op-node (consensus/derivation) + execution client (op-geth/op-reth).
- Historical state availability depends on execution client mode (full vs archive) and snapshot availability.
- Key JSON-RPC methods:
eth_getBlockByNumber,eth_getBalance,eth_call,eth_getCode,eth_getStorageAt,eth_getLogs,eth_getProof.
How Base Archive Nodes Work on OP-Stack
Base is an OP-Stack optimistic rollup. The network consists of an op-node (the consensus client that derives L2 blocks from L1 data) and an execution client (op-geth or op-reth) that stores the L2 state and serves Ethereum JSON-RPC. When you query a historical block, the execution client must have the state trie for that block. A full node prunes state older than a certain recent window (e.g., 128 blocks for op-geth's default), while an archive node retains all state snapshots.
Base launched in 2023, so its chain depth is modest compared to Ethereum L1. However, the same archive mechanics apply. To run an archive node, you typically need to start with an archive snapshot or sync from genesis with archive mode enabled. The exact configuration varies by client and deployment; see the Base node documentation for specifics.
For a deeper dive into the OP-Stack architecture, refer to the OP-Stack source-run docs.
- op-geth: use
--gcmode=archiveto retain full state. - op-reth: use
--full(default) or--debug.tip? Actually, reth uses--fullfor full node and--archivefor archive? Check docs. - Snapshot providers offer archive snapshots; sizes vary by provider and date.
Historical Read Methods on Base
The Ethereum JSON-RPC API on Base supports standard methods for historical queries. Here's how each works:
eth_getBlockByNumber with a block number or tag returns the block header and transactions. For historical blocks, this works even on a full node if the block is within the retained range, but for older blocks you need archive.
eth_getBalance, eth_call, eth_getCode, eth_getStorageAt accept a block parameter. On a full node, they only work for recent blocks; on an archive node, they work for any historical block.
eth_getLogs filters logs by address and topics over a block range. This requires the node to have the logs for that range, which is typically available even on full nodes for recent ranges, but for older ranges you need archive.
eth_getProof (EIP-1186) returns the account and storage proofs for a given address and storage keys at a specific block. This is crucial for verifying state claims and for OP-Stack withdrawal proofs. It requires archive state.
- Block tags:
latest,earliest,pending, or a hex block number. - For
eth_call, you can simulate a transaction at a historical block. eth_getProofis used to prove L1->L2 deposits and withdrawals.
Runnable Example: Testing Archive Support on a Base RPC Endpoint
The following Node.js script uses ethers v6 to query a Base RPC endpoint. It resolves a past block (e.g., block 1000000), reads a known account's balance at that block and at latest, and attempts to page eth_getLogs over a bounded window. It also attempts eth_getProof to see if the endpoint supports proofs. The script prints results and a table for you to fill in.
Assumptions: Node.js 18+, ethers v6, and a Base mainnet RPC URL. Replace YOUR_RPC_URL with your endpoint. The script uses a fixed block number and a known address (e.g., the Base bridge contract). Run it to see if your endpoint returns archive data or errors.
// test-archive.js
const { ethers } = require('ethers');
const RPC_URL = process.env.RPC_URL || 'YOUR_RPC_URL';
const provider = new ethers.JsonRpcProvider(RPC_URL);
const ADDRESS = '0x4200000000000000000000000000000000000016'; // Example: L2 standard bridge
const BLOCK_NUMBER = 1000000; // Past block
async function main() {
console.log('Testing archive support on Base RPC');
console.log('RPC URL:', RPC_URL);
// 1. Get block by number
const block = await provider.getBlock(BLOCK_NUMBER);
console.log('Block', BLOCK_NUMBER, 'exists:', !!block);
// 2. Get balance at historical block
try {
const balance = await provider.getBalance(ADDRESS, BLOCK_NUMBER);
console.log('Balance at block', BLOCK_NUMBER, ':', balance.toString());
} catch (e) {
console.log('Balance at block failed:', e.message);
}
// 3. Get balance at latest
const latestBalance = await provider.getBalance(ADDRESS, 'latest');
console.log('Balance at latest:', latestBalance.toString());
// 4. eth_getLogs paging (example: Transfer events from the bridge)
const filter = {
address: ADDRESS,
fromBlock: BLOCK_NUMBER,
toBlock: BLOCK_NUMBER + 1000,
topics: [ethers.id('Transfer(address,address,uint256)')]
};
try {
const logs = await provider.getLogs(filter);
console.log('Logs in range:', logs.length);
} catch (e) {
console.log('getLogs failed:', e.message);
}
// 5. eth_getProof (EIP-1186)
try {
const proof = await provider.send('eth_getProof', [ADDRESS, [], '0x' + BLOCK_NUMBER.toString(16)]);
console.log('eth_getProof success. Account proof length:', proof.accountProof.length);
} catch (e) {
console.log('eth_getProof failed:', e.message);
}
}
main().catch(console.error);
// Expected output shape:
// Testing archive support on Base RPC
// RPC URL: ...
// Block 1000000 exists: true
// Balance at block 1000000 : 123456789
// Balance at latest: 987654321
// Logs in range: 5
// eth_getProof success. Account proof length: 7
// If archive is not supported, you may see errors like "missing trie node" or "header not found".Results Table and Decision Checklist
Fill in the table below with the results from your test to determine if your endpoint is archive-capable.
- | Test | Result (Success/Error) | Notes |
- |------|------------------------|-------|
- | getBlock at old block | | |
- | getBalance at old block | | |
- | getBalance at latest | | |
- | getLogs in past range | | |
- | eth_getProof | | |
Common Failures and Fixes
When querying historical data on Base, you may encounter errors. Here are common ones and how to fix them.
Error: 'missing trie node' – This indicates the node does not have the state for that block. Solution: use an archive node or a provider with archive data.
Error: 'header not found' – The block number is beyond the node's sync limit. Solution: ensure the node is fully synced or use a different endpoint.
Error: 'range too large' for eth_getLogs – The block range exceeds the node's limit. Solution: page through smaller ranges (e.g., 1000 blocks at a time).
eth_getProof not supported – Some providers disable this method. Solution: use a provider that supports EIP-1186, or run your own archive node.
- Always check the block number is within the chain's range.
- Use
eth_blockNumberto confirm the latest block. - For logs, use pagination to avoid timeouts.
Tradeoffs and Limitations
Archive nodes on Base require significantly more disk space and memory than full nodes. The exact size varies by client and snapshot, but it's a known tradeoff. Running an archive node also increases sync time and operational overhead.
Base's history is relatively short (since 2023), so archive nodes are more feasible than on Ethereum L1. However, as the chain grows, storage requirements will increase.
Some RPC providers offer archive endpoints at a premium. Compare RPC pricing to decide if a managed service is cost-effective.
For latency considerations, see the Base RPC latency guide. For rate limits, see Base RPC rate limits and reliability.
Next Steps and Further Reading
Now that you understand how to query historical Base state, you can apply this to your dApp or analytics. For a broader understanding of historical data queries on EVM chains, see the Querying historical blockchain data (EVM cookbook).
If you're building on Base, explore the Base network overview and the OnFinality Learn hub for more guides. For node infrastructure decisions, refer to the Base node infrastructure (RPC Assistant).
For production use, consider using OnFinality's API service which provides reliable archive endpoints. Check RPC pricing for details.