For developers / JSON-RPC

History and retention

Live

The node is an RPC index, not an archive. It keeps roughly two minutes of blocks, and a query past that answers null rather than erroring - which is why this page exists before you write an indexer rather than after.

What the node keeps

DataRetained
EVM block history~2 minuteskeep_evm_blocks() = 120,000 / EVM_BLOCK_MS - 480 blocks at the default. Older blocks return null and their logs become unqueryable
Mini-block history20,000KEEP_MINI_BLOCKS, roughly 200 seconds at the 10 ms scheduling target
Receipts and bodies200,000MAX_RECEIPTS, evicted first-in-first-out
Full historynot on the nodeit lives in Postgres behind the explorer, fed by the sequencer's spool. This RPC is an index, not an archive

The block window is derived rather than fixed: keep_evm_blocks() = 120_000 / EVM_BLOCK_MS, which at the default cadence is 480 blocks. Logs are evicted with their block. Receipts and transaction bodies have their own first-in-first-out cap and can disappear before their block does.

Reads are served from a published copy-on-write snapshot and never take the execution lock, which is why a slow eth_call cannot stall block production and a stalled block cannot stall reads. That is the design that makes the window acceptable: the node optimises for serving the present quickly, and hands history to something built for it.

How this fails

Outside the window you get null, not an error

eth_getTransactionReceipt, eth_getTransactionByHash and every block lookup answer null for anything evicted. Nothing distinguishes "never existed" from "existed and has been dropped".

Three patterns that look correct and are not:

  • Polling for a receipt with a long backoff. Wait long enough between attempts and the receipt can be evicted between two polls. It exists within about 10 ms of sending - poll early and often, not patiently.
  • Backfilling from block 0. earliest is the earliest retained block, and it moves forward as you work. A backfill loop chasing it never reaches a fixed start.
  • Reconciling a ledger nightly. By the time a nightly job runs, every block it wanted is gone.

What to do instead

The full history exists - it is streamed from the sequencer into Postgres behind the explorer, which is built to be an archive. So:

  • For the present - the last few seconds - use the node. Subscriptions and eth_getLogs over a narrow range are what it is fast at.
  • For the past, read the explorer. It has call traces too, which the node does not expose at all since there is no debug namespace.
  • To keep your own history, subscribe and persist as events arrive rather than querying for them later. The mini-block stream is the highest-fidelity source; a slow handler is dropped from it, so push to a queue and process elsewhere.
javascript
// Right: capture as it happens, persist immediately.
socket.send(JSON.stringify({
  jsonrpc: "2.0", id: 1, method: "eth_subscribe",
  params: ["logs", { address: MY_CONTRACT }],
}));

// Wrong on this chain: a range that has already scrolled out of the window
// returns an empty array rather than an error, so this looks like "no events".
await client.getLogs({ address: MY_CONTRACT, fromBlock: 0n, toBlock: "latest" });

And if you key an event store on (blockNumber, logIndex), include the transaction hash: logIndex here is enumerated per transaction, so that pair is not unique.

Receipts across a restart

State survives a restart; the receipt index does not

The receipt and log index is held in memory. An acknowledged transfer's receipt has been observed disappearing after a sequencer restart while the balance change survived correctly - so a receipt is not a durable record of anything. Treat the explorer as the record of what happened, and the node's receipt as a fast, perishable acknowledgement.

State resets

Phase 0 chain state may be reset without notice, which deletes balances, contracts and history. If you deploy something you care about, keep the deployment script rather than the address - and expect to redeploy.

On a stack you run yourself, docker compose down -v is the deliberate version of the same thing: it wipes the sequencer state, the data-availability objects, the local L1 and the explorer's Postgres together.