Skip to content

Research notes

Transaction Hash vs Log Index: Identify the Event

A two-placement ledger separates repeated log delivery from a new canonical inclusion, with a recovery worksheet and replay checks for an event indexer.

Ivory ceramic trays with recessed slots are joined by a paper ribbon, with one lime bead on the front tray.
Illustration for research notes; examples in this article are educational.

An Ethereum transaction hash identifies a transaction, while logIndex identifies a log's position within one block. One transaction can emit several logs, and a reorganization can move its execution into a different block with different log positions. Identify each observed log occurrence with its network, block hash and log index, then track whether that occurrence belongs to the current canonical chain.

The identifier tells you which question to ask

A transaction is the container for an execution; an event log is one output of that execution. Looking up a transaction hash is a useful starting point, but treating that hash as an event ID collapses every log in its receipt into one record.

Ethereum's JSON-RPC documentation gives logs separate transaction, block and position fields, along with an emitting address, topics and data. In particular, logIndex is the position in the entire block, not the position within the originating transaction. Ethereum JSON-RPC log fields.

Field or contextQuestion it answersBoundary to retain
Network identityWhich chain are we observing?Keep a configured network namespace with the records.
transactionHashWhich transaction produced this log?Several logs can share it.
blockHashWhich particular block contains this occurrence?A height can have a different canonical hash after a reorganization.
transactionIndexWhere is the transaction inside that block?It does not identify an individual log.
logIndexWhere is this log inside that block?It needs a block and network namespace.
address, topics, dataWhich emitter and encoded event are represented?Decoding meaning requires the relevant contract schema.

Use a stable local network name tied to the expected chain configuration; record the chain ID as supporting context. Avoid letting two unrelated environments share a database namespace merely because someone configured the same numeric chain ID.

This is a different problem from exchange trade IDs versus order IDs. An exchange execution belongs to the venue's record system. An Ethereum log occurrence belongs to a particular block execution, whose place in accepted history can change.

One transaction, two block placements

Consider a hypothetical transaction T on one Ethereum network. It emits two transfer logs from a tracked token contract to a tracked recipient: 7 units and 5 units. Assume those are the only relevant logs, the token's decoding is already verified, and the same two amounts are emitted when T is included again. These are invented quantities and placements, not observations from an explorer.

At height 25,000,100, block A initially contains T. Other transactions produced 40 logs before T, so its two logs have indices 40 and 41. A reorganization replaces A with block B at the same height. In B, earlier transactions produced 13 logs, so T's logs have indices 13 and 14.

PlacementTransactionBlock hash aliaslogIndexTracked units
Initial branchTA407
Initial branchTA415
Replacement branchTB137
Replacement branchTB145

A, B and T are short teaching aliases, not valid hashes. Actual records should preserve the complete identifiers. The table has four distinct observed occurrences, but only two belong in the final canonical total after B replaces A.

Geth documents the relevant subscription behavior: logs previously sent from the old chain are sent again with removed set to true; logs from transactions on the new chain are emitted. Consequently, a subscriber can receive logs for the same transaction more than once. This is Geth's documented behavior, not a delivery guarantee for every hosted service. Geth real-time log subscriptions.

Now add ordinary repeated delivery to the hypothetical sequence. Each table row below represents a pair of log observations, one for each amount. The local ledger starts at zero and records only this example's contribution.

ObservationRequired local actionCanonical units after action
A:40 and A:41 addedRecord and activate both occurrences12
Same additions delivered againRetain observations; apply no extra quantity12
A:40 and A:41 marked removedDeactivate A's occurrences and reverse their contribution0
B:13 and B:14 addedRecord and activate B's occurrences12
B's pair found again during backfillMatch existing active occurrences12

The correct final quantity is 7 + 5 = 12 units. There are ten log observations: eight additions and two removals. Summing all positive observations gives 4 × 12 = 48 units. Deduplicating occurrences but never reversing A gives 12 + 12 = 24 units. Both totals are wrong for the final canonical view.

The observed index shift is 13 − 40 = −27 for the first log and 14 − 41 = −27 for the second. The unchanged transaction hash does not make the old indices permanent. This example's unchanged amounts are also an assumption. Ethereum's state-transition input includes the starting state as well as transactions; the indexing implication is that a matching transaction hash alone cannot establish matching output after a new inclusion. Inspect the new receipt. Ethereum state-transition model.

Keep observed occurrences and the current view separately

A practical local occurrence key for an included log is network namespace plus blockHash plus logIndex. Store transactionHash as a relationship and lookup field. This is an indexing design recommendation derived from the field scopes, not an Ethereum requirement for a database schema.

Require a non-null blockHash and logIndex before creating that occurrence key. The JSON-RPC log schema permits these placement fields to be null for pending logs. Keep such observations separately until inclusion; do not convert missing positions to zero. An actual logIndex of zero is a valid block position.

Keep the raw emitter, topics and data alongside that key. Store block number, transaction position, retrieval source and observation time as evidence. Separate the log's payload from its current canonical status and from the notification's removed flag. An addition and a removal can describe the same occurrence with opposite implications for the current view.

That separation supports three different operations:

  • A repeated addition with matching payload records another observation without adding units again.
  • A confirmed removal changes the existing occurrence's status and reverses any contribution currently applied from it.
  • A new canonical inclusion creates different occurrence records, even if transactionHash matches an older inclusion.

Make the status transition and its local accounting effect atomic where possible. A crash after adding units but before recording progress can otherwise cause a second addition on restart. Persist a checkpoint together with the records it claims to cover, or use a recovery procedure that can safely replay them.

Do not repair identity by using transactionHash alone or by renumbering each receipt's logs from zero and assuming those positions identify permanent economic events. Such receipt-local positions may help inspect one execution, but the canonical execution must still be reconciled. The receipt status versus token transfer guide explains the separate check that a decoded log supports the intended asset movement.

If a matching occurrence key arrives with a different address, topics or data, keep the conflicting evidence and investigate the endpoint and parser. Changing the key until both versions fit would conceal the discrepancy. Conversely, a changed removed flag is an expected lifecycle observation, not automatically a payload conflict.

External actions need their own treatment. Reversing a local displayed total cannot undo an email, exchange order or withdrawal already initiated from an early log. Define when those actions become eligible and how duplicates or later reversals are handled. A unique database key alone does not make external actions occur exactly once.

Recover the branch after a missed removal

A live connection is an observation channel, not a complete historical ledger. Geth says subscriptions cover current events and disappear when the underlying connection closes. Reopening a socket does not establish what happened during the gap. Geth subscription considerations.

EIP-234 describes the specific failure: a connection can drop before a reorganization, leaving the client unaware that earlier logs were removed. It also explains why querying a height can return logs from a different block, while an empty result provides no returned block hash to inspect. EIP-234 recovery rationale.

Use the following recovery procedure as a design worksheet:

  1. Read the stored checkpoint's height and hash, then compare that hash with the current canonical block at that height.
  2. If they differ, walk stored and retrieved ancestry back to a common ancestor. If retained history is insufficient, stop claiming complete coverage and obtain a wider backfill.
  3. Deactivate applied occurrences from displaced blocks. Preserve their observations for explanation and audit.
  4. Read the replacement branch's blocks and retrieve the relevant logs for each particular hash where the endpoint supports it.
  5. Re-read the canonical hash at the intended checkpoint height before committing progress. If it differs from the branch just retrieved, repeat reconciliation. Apply matching canonical occurrences once, then advance the checkpoint only after the checked branch and local records agree.

The blockHash filter introduced by EIP-234 targets a particular block rather than a height range; it is an alternative to fromBlock and toBlock. Validate support and retained history at the actual endpoint. A hash-specific answer identifies the queried block's results; it does not establish that this block remains canonical. EIP-234 specification.

A late addition for A must not reactivate A while its block is still off the canonical branch. If competing observations arrive out of order, reconcile block membership before changing the derived total. If A later becomes canonical again, recompute the affected view from the newly established branch rather than treating “removed once” as a permanent prohibition.

Keep chain settlement policy separate from occurrence identity. The confirmations versus finality guide covers that decision. A preview from eth_call or a gas estimate also cannot substitute for the receipt and block evidence of an actual inclusion.

A small replay test exposes the wrong key

Before trusting a dashboard or indexer, run the invented sequence through its record model. Record the expected result before executing the test. The first three rows below form one sequence; the remaining rows are separate fault injections.

Test variationExpected result
Deliver A's additions three timesTwo active occurrences; 12 units
Remove A twiceZero active occurrences; zero units, with no second subtraction
Add B and replay its backfillTwo active occurrences; 12 units
Miss removals while disconnectedBranch reconciliation removes A before the checkpoint claims B's history
Deliver an old A addition after recoveryA remains inactive while B is canonical
Change payload under the same occurrence keyVisible conflict; no invented extra transfer
Observe a null block hash or log indexPending observation stays outside the included-log key and total
Change the canonical hash during backfillCheckpoint does not advance until the replacement branch is reconciled
Reuse an index on another block or networkA distinct scoped occurrence, subject to that branch's status

For an incident record, save the network, transaction hash, both block hashes, log indices, observation times, canonical comparison and applied reversals. The useful conclusion is specific: “T has two active logs in B totaling 12 units; A's two occurrences are retained but inactive.” That statement distinguishes the transaction you searched for from the events your current ledger actually counts.

Keep the context with the example.

Each worked example is illustrative, with its sources and assumptions in the article. Read our editorial standards for authorship, verification and corrections.