The first indexer I ever shipped had a bug that took two weeks to find. Balances were drifting by tiny amounts on a handful of accounts, never enough to look like a crash, just enough to be wrong. The cause was EVM event ordering. I had assumed logs came out in a clean, obvious sequence, and they do not. If you build anything that reads on-chain events and reconstructs state, this is the detail that quietly breaks you.
What people assume about event ordering
Most engineers picture a block as a list of transactions, each transaction emitting a few events, all neatly stacked in the order things happened. That mental model is close enough to feel safe and wrong enough to cause real damage. The trap is thinking a block timestamp or a transaction hash gives you order. It does not. Two events in the same block share the same timestamp. Transaction hashes are random, so sorting by them shuffles your history into nonsense.
The other common assumption is that a single transaction emits its events top to bottom in source-code order. That part is mostly true, but only mostly, because internal calls and nested contract interactions can interleave in ways that surprise you the first time you trace one.
The ordering that actually exists
On chain, every log has three numbers that together give you a total order: block number, transaction index within the block, and log index within the block. The log index is the one people forget. It increments across the entire block, not per transaction, so it already encodes the position of every event relative to every other event in that block. If you sort by (block number, log index) you get the true sequence in which the EVM produced those events.
I want to stress how important that combination is. Block number alone collides for everything in a block. Transaction index plus a per-transaction counter works, but it is more moving parts than you need. Log index is monotonic across the block, which means the single cleanest sort key is block number first, then log index. Get that right and a whole category of bugs disappears.
Why the wrong sort corrupts state
Say you are tracking a token. A transfer out followed by a transfer in nets to zero change in a balance. Reverse those two events and you can momentarily compute a negative balance, which either throws, clamps to zero, or silently writes a bad row depending on how defensive your code is. None of those outcomes is what happened on chain. The chain is the source of truth, and the chain ordered those events a specific way.
This gets worse with anything path dependent. Liquidity pool math, staking reward accrual, vesting schedules, auction state, any logic where the order of operations changes the result. If your indexer processes events in the wrong order, you are not computing the protocol's real state, you are computing a plausible looking fiction.
How I handle it in practice
The rule I follow on every indexing project is to treat ordering as a property of the data layer, not the business logic. The code that reads logs should hand the rest of the system a stream that is already correctly ordered, so no downstream handler ever has to think about it. That keeps the ordering logic in one place where it can be tested instead of scattered across every event handler.
- Sort every batch of logs by (block number, log index) before you touch them, never by timestamp or transaction hash
- When you fetch logs across a range, do not assume the RPC returns them sorted, sort them yourself
- Persist the last processed (block number, log index) cursor so a restart resumes at the exact right spot and never replays or skips
- Treat a same-block, lower-log-index event as strictly earlier, even if it lives in a later transaction index, because nested calls can produce that
Both The Graph and Subsquid give you these fields, and both will hand you events in order if you ask correctly, but I still verify it rather than trust it. A five line test that feeds two out-of-order logs and asserts the resulting state matches the chain has saved me more grief than almost any other check I write.
The mental shift worth making
The thing I wish someone had told me early is that an EVM block is a tape of events, not a folder of transactions. Once you see it that way, the log index stops being an obscure field and becomes the thing you sort on by default. Transactions are still useful for grouping and for gas accounting, but for reconstructing what the protocol did and in what sequence, the log index is the spine of the whole thing.
If your indexer has ever produced numbers that are almost right, check your sort key first. Nine times out of ten that is where the drift is hiding, and it is a one line fix once you know where to look.
Building something where this matters?
I am open to senior full-stack, Web3, or AI engineering roles, fully remote and any timezone. If the hard part of your product is fighting you, that is the work I like.
Get in touch →