Skip to content

Research notes

Ethereum Log Blooms vs Receipt Inclusion Proofs

A 16-bit example separates missing-bit exclusions from two false leads, then follows raw log evidence through receipt encoding to a trusted header.

A perforated ivory ceramic screen stands beside folded paper pieces and a recessed block with a lime peg.
Illustration for research notes; examples in this article are educational.

An Ethereum log bloom is a compact filter that can rule out some candidate blocks or receipts. A positive match only means the requested address or topic might be present. A receipt inclusion proof instead links exact receipt bytes, including their logs, to a block's receipts root. That proof depends on an independently trusted header and does not by itself establish finality or the meaning of an event.

The distinction matters when an event search returns a promising block. The bloom helps decide where to look next. Verifying a particular event requires inspecting its raw log and, when independent authentication is needed, checking the receipt against the block's commitment.

Ethereum's traditional logsBloom format has 2,048 bits, or 256 bytes. Each emitting address and each topic contributes three positions derived from Keccak-256. Setting those positions preserves a compact hint about membership; it discards the original values. The named BPO2 execution specification documents this bloom construction.

A receipt bloom covers its own logs. The block bloom combines the information across receipts. Geth v1.16.5's release-pinned implementation inserts addresses and topics, merges receipt blooms with bitwise OR, and checks whether all positions required by a query value are set.

For a correctly constructed bloom, a missing required bit excludes that value from the covered logs. An inserted value would have set that bit, so the construction has no false negatives for inserted values. This conclusion depends on using the right bytes, the right bloom and an authenticated source for it.

The reverse does not follow. Different values can set overlapping positions, producing a false positive. The filter also loses relationships: it cannot tell whether a particular address and topic occurred together in one log or whether a topic appeared in the requested position.

Log data is outside this bloom construction. A non-indexed amount in the data field cannot be confirmed or excluded by testing its number against logsBloom. Exact log filtering still has to respect the address, ordered topics and data being investigated.

Two false leads in a 16-bit filter

Consider an explicitly hypothetical filter with 16 bits and two positions per value. These are invented mappings, not shortened Ethereum hashes. Bit zero is the least significant bit. The example contains exactly two logs:

Toy receiptEmitter and topicPositions insertedData payload
0Address A, topic PA: 1, 6; P: 4, 940
1Address B, topic QB: 2, 7; Q: 5, 1270

The first receipt sets bits 1, 4, 6 and 9, giving the hexadecimal mask 0x0252. The second sets bits 2, 5, 7 and 12, giving 0x10a4. Their combined mask is 0x12f6, or 4,854 in decimal. Its eight set positions are 1, 2, 4, 5, 6, 7, 9 and 12.

Add two absent addresses to the lookup table. Address C maps to positions 1 and 9; address D maps to positions 3 and 14. Neither address emitted a log.

Now ask for an emitter and topic together:

QueryRequired positionsBloom resultActual matching log
A with P1, 4, 6, 9All setYes, in receipt 0
A with Q1, 5, 6, 12All setNone
C with P1, 4, 9All setNone
D with P3, 4, 9, 14Bits 3 and 14 missingNone; excluded

The A-with-Q query passes even though no such log exists. A appears in the first receipt and Q in the second. Both components really occur, but their requested relationship does not.

The C-with-P query shows a different failure. C never appears anywhere. Its two positions happen to be set by values that do appear, so the bloom cannot distinguish C from that combination.

D-with-P can be excluded because D needs two bits that remain clear. The absent bits give a definite answer within this example's scope; the positive results require further inspection.

The payloads 40 and 70 never enter the bit calculations. Changing a payload while keeping the same emitter and topics would leave this toy bloom unchanged. Ethereum's address-and-topic construction has the same limitation.

You can reproduce the combined mask by summing 2 raised to each listed set position: the result is 4,854. Three of the four queries pass, but only one has a matching log. This small set illustrates the mechanism; it does not estimate Ethereum's false-positive rate.

Connect the receipt to a trusted header

An exact scan of candidate logs can eliminate those false leads. Independent authentication then needs an anchor: the intended block's accepted header. A root copied from an unverified provider alongside its proof only shows that the two agree. Establish the header's chain membership through your verification process.

Ethereum commits receipts through a Merkle Patricia trie. A receipt proof supplies the nodes needed to check a particular receipt value at its key against a known receipts root. The Ethereum trie documentation describes this link between a path, its value and the root commitment.

Read receiptsRoot from that header. The state root commits different information; an account or storage proof cannot replace a receipt proof. The comparison of Ethereum state roots and receipts roots explains which commitment belongs to each question.

The receipt trie key uses Recursive Length Prefix (RLP) encoding of transactionIndex, the transaction's zero-based position in that block. It is not the transaction hash or the event's log index. For index 1, the key is the single byte 0x01 under the RLP encoding rules.

The value must also use the correct consensus encoding. EIP-2718 distinguishes legacy receipts from typed receipts, whose type byte precedes their type-specific payload. Omitting the required type byte or using the whole RPC JSON response as the leaf changes the value being proved.

Once the receipt bytes verify against the root, decode their retained logs and inspect the exact emitter, topic sequence and data. Those are the fields in the execution specification's raw log structure. An ordinary receipt proof authenticates the receipt containing the log; it does not prove a separate event leaf in an event-specific Merkle tree.

Changing an authenticated log's data while retaining the old proof should fail verification against the same root, subject to the cryptographic assumptions. A bloom could remain identical after that change because it never included the data. This is the practical difference between preserving a search hint and committing exact bytes.

Keep JSON context and event meaning separate

An RPC receipt contains useful contextual fields beyond the serialized receipt value. Do not assume the receipt branch authenticates every displayed field.

For example, Geth v1.16.5 derives transaction hashes, block context and global log indices from receipts plus their surrounding block and transaction information. A receipt proof at index 1 authenticates the receipt at that position. To independently bind it to a claimed transaction hash, also verify the corresponding transaction against transactionsRoot at the same index, or obtain it from a fully verified block.

Within an authenticated receipt, a log's position in its log list is clear. The block-wide logIndex additionally depends on how many logs precede that receipt. The transaction hash versus log index guide explains those different scopes. Keep the receipt position and receipt-local log position in your evidence instead of treating an unexplained RPC index as part of the leaf.

Authentication also leaves interpretation to the application. A familiar topic signature does not make an arbitrary emitter the intended token or protocol. Check the network, emitting address and relevant implementation before decoding business meaning. The distinction between contract addresses and code hashes helps frame that identity check.

An authenticated log establishes which bytes were retained in that block. It does not automatically establish a recipient's balance change, reconstruct every internal call or explain reverted branches. Those are the subjects of event logs versus internal transactions.

Choose evidence for the claim you need

Use this worksheet before accepting an event record or declaring that none exists.

CheckEvidence to retainDecision
Define the eventNetwork, emitter bytes, ordered topic conditions and data testKnow exactly what counts as a match
Fix the blockHeader, block hash and source of header trustScope every later result to that block
Test the bloomCorrectly encoded values and required bit positionsExclude on a justified missing-bit result; otherwise inspect
Match the raw logReceipt position, local log position, address, topics and dataReject candidates that fail an exact condition
Verify inclusionReceipt encoding, trie key, proof nodes and trusted receipts rootAccept only if the proof verifies
Interpret the resultContract identity, decoding rules and required settlement statusState only the conclusion those checks support

For real bloom inputs, use the raw 20-byte emitter address or 32-byte topic. Hashing printed hexadecimal text, a decoded event name or the wrong ABI representation changes the query. It can create an apparent absence caused by the implementation rather than the underlying logs.

Address alternatives also need care. If a query accepts either A or B, excluding A alone cannot exclude the query. Every allowed address must fail before that address condition rules out the block. Respect topic alternatives and wildcards in the same way; the JSON-RPC filter schema defines their grouping and order.

Do not treat an RPC response as a proof package merely because it contains a receipt. The ordinary receipt and log methods return records. EIP-1186's eth_getProof specification covers account and storage proofs against state, rather than receipt inclusion. Check the actual client's or provider's receipt-proof facilities and historical data availability. An unsupported method, missing history or timeout leaves evidence unavailable.

Finally, distinguish inclusion from completeness. One valid receipt proof establishes that receipt's contents; it cannot establish that no other receipt in the block contains a match. If the block bloom passes but exact inspection finds nothing, an independently verified absence claim needs broader evidence. One route is to retrieve the complete receipt set, reproduce its trie root and compare it with the trusted header before checking every log. A search over a range similarly needs coverage of every relevant block.

A valid proof can also refer to a block that your application no longer accepts. Apply the required canonical-chain and finality checks to the header anchoring the evidence.

Close a positive record with the block hash, verified receipt position and matching raw log. Close a negative record with the authenticated scope and the exclusion or complete-search evidence. A passing bloom test belongs in the search record; a verified receipt and trusted header belong behind the inclusion claim.

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.