The backend misses contract events
“Most transfers show up, but some never reach the database. Backfills fail with range or result-limit errors, and the indexer's cursor has moved past blocks it never actually read.”
THE PRINCIPLE
The cursor is a promise that every block up to it was read in full. Never move it past a range whose query failed or was cut short.
FIRST MOVES
- Read your provider's limits for eth_getLogs. Infura caps a query at 10,000 results and 10 seconds (error -32005); Alchemy caps the block range by plan and chain and responses at 150 MB.
- Scan in fixed block windows from a stored cursor to a chosen head. On a limit or timeout error, halve the window and retry the same range instead of skipping it.
- Commit the logs and the new cursor in one database transaction, and make inserts idempotent on (chain_id, tx_hash, log_index) so re-scanning a range is safe.
- Scan up to a safe or finalized block, or keep block hashes and roll back on reorgs; logs near the tip can disappear.
- Filter by contract address and topics, and compare per-range counts against a second provider or an explorer to find gaps.
TOOLS: THEN → NOW
One getLogs call from the deploy block to latestWindowed scans with adaptive range sizes and a durable cursor
Self-written log pollersStill viable; indexing frameworks such as Ponder, The Graph, or Envio handle ranges, retries, and reorgs for you
PATTERN SNAPSHOT
// Read [from, head] completely; shrink the window when the provider refuses.
async function scan(from: bigint, head: bigint, step = 2_000n) {
while (from <= head) {
const to = from + step - 1n < head ? from + step - 1n : head;
try {
const logs = await client.getLogs({ address, event, fromBlock: from, toBlock: to });
await db.transaction(async (tx) => {
await insertLogs(tx, logs); // ON CONFLICT (chain_id, tx_hash, log_index) DO NOTHING
await setCursor(tx, to); // same transaction as the writes
});
from = to + 1n;
} catch (err) { if (!isLimitError(err) || step === 1n) throw err; step /= 2n; }
}
}CLOSE THE AI. EXPLAIN THIS.
Your scanner catches a getLogs timeout, logs it, and moves on to the next window. Which events are now missing, and what should advance the cursor instead?HOW IT WORKS UNDERNEATH
RELATED SYMPTOMS
SOURCES
Guide reviewed