An Arbitrum Nitro archive node retains the full historical L2 state and the L1-derived batch data needed to reconstruct it, enabling historical eth_call, eth_getBalance, and eth_getLogs queries. Unlike a full node, which prunes old trie nodes, an archive node runs with the archive flag enabled and stores all state history. Syncing from genesis is faithful but slow; bootstrapping from a published snapshot is faster but requires trusting the snapshot source and verifying the head. Running a Nitro archive node requires a healthy Ethereum L1 RPC endpoint and a properly configured data directory. This article explains the mechanism, provides runnable verification examples, and outlines trade-offs and common failures.
Arbitrum Nitro Node Model and Archive Requirements
Arbitrum One is an optimistic rollup, and its Nitro node derives L2 state from two inputs: the L1 (Ethereum) inbox for sequenced batches and the L2 execution itself. This differs from a generic Ethereum L1 node, which derives state solely from its own chain. An archive node must therefore retain both the full L2 state history and the L1-derived batch data to reconstruct any historical state.
The node-role landscape includes full nodes, which keep recent state, and archive nodes, which retain historical state for the entire chain. Archive nodes are needed for eth_call and eth_getBalance against old blocks, historical eth_getLogs, and trace-style replay. The two roles differ in storage strategy and sync time, not in API surface. For a broader comparison, see Archive node vs full node.
- Full node: prunes old trie nodes, faster sync, lower storage.
- Archive node: retains all trie nodes, slower sync, higher storage.
- Both expose the same JSON-RPC API; archive nodes serve historical queries.
Nitro State Storage and the Archive Flag
Nitro stores state in a Merkle-ised trie committed via the WAVM/AEVM-produced state root, using a persistent state database derived from go-ethereum. This state store can run in 'full' or 'archive' retention mode. The archive flag decides whether old trie nodes are pruned; when enabled, the node retains all historical state.
The archive flag is set at node startup and cannot be changed without resyncing or migrating the database. According to the Arbitrum documentation on running an archive node, the node must be configured with the appropriate flags to enable archive mode. This ensures that historical state queries do not fail with 'missing trie node' errors.
- State root is produced by Nitro's execution engine (WAVM/AEVM).
- Persistent state database uses a go-ethereum-derived store.
- Archive mode disables pruning of old trie nodes.
Sync Paths: Genesis Replay, Snapshot Bootstrap, and Hosted Endpoints
There are three primary sync paths for an Arbitrum Nitro archive node. First, syncing from genesis by replaying the inbox is faithful but slow and IO-bound, as it processes every batch from L1. Second, bootstrapping from a published snapshot copies a known-good state database and then catches up the remaining inbox, which is far faster but requires trusting the snapshot source and verifying the head afterwards. Third, relying on a hosted archive endpoint removes the operational burden; for that choice, see Arbitrum RPC endpoints (RPC Assistant).
The snapshot flow is documented in the Arbitrum Nitro Docker guide. It involves downloading a snapshot archive, extracting it to the data directory, and starting the node with the archive flag. The node will then sync the remaining batches from L1. This method is significantly faster than genesis sync but introduces a trust assumption on the snapshot provider.
- Genesis sync: trustless, slow, high IO.
- Snapshot bootstrap: fast, requires trust in snapshot source.
- Hosted endpoint: no ops, but external dependency.
Running a Nitro Archive Node with Docker
The documented container deployment runs the Nitro node against an L1 Ethereum endpoint. The node reads L1 for batches and for the delayed inbox, so you must supply a healthy Ethereum L1 RPC in addition to the L2 data directory. The archive flag must be set so state is not pruned. The Arbitrum Nitro Docker guide provides the canonical Docker command and configuration.
A typical Docker invocation includes environment variables for the L1 endpoint, the L2 chain ID, and the archive flag. The data directory should be mounted on fast NVMe storage with high endurance, as archive storage grows continuously. After starting the container, monitor logs for sync progress and ensure the node is consuming the L1 inbox without throttling.
docker run -d --name nitro-archive \
-v /path/to/data:/home/user/.arbitrum \
-p 8547:8547 -p 8548:8548 \
offchainlabs/nitro-node:v3.0.0 \
--l1.url=https://eth-mainnet.example.com \
--l2.chain-id=42161 \
--node.archive=true \
--http.addr=0.0.0.0 \
--http.port=8547 \
--http.api=eth,net,web3,debugVerifying Archive Behaviour with Historical RPC Calls
To verify that an archive node is serving historical state, call eth_getBalance or eth_call with an explicit old block number—a block far before the node's recent window—and confirm it returns a value rather than a pruned-state error. Then compare eth_getBlockByNumber head advancement against the sequencer or L1 to confirm the node is live. The Arbitrum documentation on the sequencer feed explains how to monitor head progression.
Use the following curl example to query a historical balance. Replace the endpoint and block number with your own. The block number should be well before the node's recent window (e.g., block 1000000 on Arbitrum One). If the node is in archive mode, it will return the balance; otherwise, it will return an error indicating missing trie node.
curl -X POST https://your-archive-node.example.com \
-H "Content-Type: application/json" \
--data '{"jsonrpc":"2.0","method":"eth_getBalance","params":["0x0000000000000000000000000000000000000000","0xF4240"],"id":1}'Runnable Node.js Example for Historical Queries
The following Node.js snippet issues historical eth_call, eth_getBalance, and eth_getLogs requests and prints the block number used. It uses the built-in fetch API (Node.js 18+). Replace the endpoint and block number with your own. This example helps you verify archive behaviour programmatically.
The script first fetches the current block number to establish a reference, then queries a historical block. It prints the block number used and the result. If the node is not in archive mode, the historical queries will fail with an error.
const endpoint = 'https://your-archive-node.example.com';
const historicalBlock = 1000000; // Arbitrum One block number
async function rpcCall(method, params) {
const res = await fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ jsonrpc: '2.0', method, params, id: 1 })
});
return res.json();
}
(async () => {
const head = await rpcCall('eth_blockNumber', []);
console.log('Current head block:', parseInt(head.result, 16));
const balance = await rpcCall('eth_getBalance', ['0x0000000000000000000000000000000000000000', '0x' + historicalBlock.toString(16)]);
console.log(`Balance at block ${historicalBlock}:`, balance.result);
const call = await rpcCall('eth_call', [{ to: '0x0000000000000000000000000000000000000000', data: '0x' }, '0x' + historicalBlock.toString(16)]);
console.log(`eth_call at block ${historicalBlock}:`, call.result);
const logs = await rpcCall('eth_getLogs', [{ fromBlock: '0x' + historicalBlock.toString(16), toBlock: '0x' + historicalBlock.toString(16) }]);
console.log(`Logs at block ${historicalBlock}:`, logs.result.length);
})();Results Table for Reproducible Verification
To make your verification reproducible without inventing numbers, fill in the following table with your own endpoint measurements. Use a historical block number that is well before the node's recent window. Record the archive call result, the current head block, and the sync lag (head block minus the latest block known to the sequencer). This table helps you compare different endpoints or track your node's health over time.
The table should be filled with your own data. Do not rely on pre-filled numbers. For a deeper dive into historical data access, see Accessing historical blockchain data.
- Endpoint URL: [your endpoint]
- Historical block number used: [e.g., 1000000]
- eth_getBalance result: [value or error]
- eth_call result: [value or error]
- eth_getLogs result: [count or error]
- Current head block: [number]
- Sync lag (head vs sequencer): [number]
Common Failures and Troubleshooting
Common failures include 'missing trie node' or 'historical state unavailable' errors, which mean the archive flag is off or the node is still backfilling. Snapshot mismatch can yield a state root that the inbox disagrees with, causing the node to stall or reject blocks. L1 endpoint throttling can stall inbox consumption, preventing the node from advancing. To diagnose, check the node logs for errors and verify the L1 endpoint's health.
If you encounter 'missing trie node', ensure the node was started with the archive flag and that the data directory contains the full state history. If you used a snapshot, verify its integrity and that it matches the expected state root. For L1 throttling, consider using a dedicated L1 RPC provider or increasing rate limits. For sync status monitoring, see OP Stack node sync status via the Engine API, which covers similar concepts for OP Stack chains.
- Missing trie node: archive flag off or backfilling.
- Snapshot mismatch: state root disagreement with inbox.
- L1 throttling: inbox consumption stalls.
Trade-offs and Limitations of Self-Hosted Archive Nodes
Archive storage grows continuously and needs fast NVMe with high endurance. Snapshot trust is a real security decision: you are trusting the snapshot provider not to include malicious state. A self-hosted archive node is justified only when query volume, data-locality, or compliance beats the cost of a managed endpoint. For many use cases, a hosted archive endpoint is more cost-effective and requires less operational overhead.
Consider the total cost of ownership: hardware, bandwidth, monitoring, and upgrades. If your query volume is low or intermittent, a managed endpoint may be cheaper. If you need guaranteed data locality or have compliance requirements, self-hosting may be necessary. For pricing considerations, see RPC pricing.
- Storage: continuous growth, NVMe with high endurance.
- Security: snapshot trust assumption.
- Cost: self-hosting vs managed endpoint trade-off.
Next Steps: Choosing an Endpoint and Further Reading
After verifying your archive node, you may want to explore managed endpoints for production use. The Arbitrum RPC endpoints (RPC Assistant) page provides a list of providers. For a broader understanding of archive nodes, see Archive node vs full node and Accessing historical blockchain data.
To learn about OnFinality's API service, visit API service. For more tutorials, explore the OnFinality Learn hub. If you are working with other networks, see Base archive node and historical RPC and Arbitrum network page.
- Managed endpoints: Arbitrum RPC endpoints (RPC Assistant)
- Further reading: Archive node vs full node
- OnFinality services: API service, RPC pricing