Skip to content

Research notes

Ethereum State Root vs Receipts Root

One block can retain two successful receipts while its final state contains only the last write. Match each claim to the correct Ethereum proof.

A closed ivory ceramic capsule and an open cradle of blank paper slips joined by a lime thread.
Illustration for research notes; examples in this article are educational.

Ethereum's state root commits to account and storage state after a block, while its receipts root commits to the transaction receipts in that block. Use an account and storage proof for a stored value, and a receipt inclusion proof for a recorded execution outcome or log. Both proofs need a trusted block header; neither root alone supplies the underlying data or explains a contract's economic meaning.

Two commitments inside one block

The roots answer different questions about the same execution. The execution header's state root summarizes the resulting world state. Its receipts root summarizes records produced by the block's transactions. Ethereum's block documentation distinguishes these execution payload fields. This article follows the documented Merkle Patricia trie format and post-Byzantium receipt semantics.

A root is a compact cryptographic commitment. A trie is the authenticated key-and-value tree behind it. A proof supplies the relevant tree nodes so a verifier can check that a particular fact belongs under that commitment. Receiving a root and a plausible value in the same server response does not perform that check.

Keep the execution state root distinct from the consensus layer's beacon state root. Similar names do not make the objects interchangeable. Also keep a contract's storage root distinct from the global state root: the former is reached through that contract's account.

QuestionCommitment to useEvidence the verifier needs
What was this account's native ETH balance at block end?State rootAccount proof
What integer occupied this contract's storage slot?State root, through the account's storage rootAccount proof and storage proof
Did this receipt record success or a particular log?Receipts rootReceipt bytes, position and inclusion proof
Which signed transaction occupied that position?Transactions rootTransaction bytes and inclusion proof

Follow two writes through the same block

Consider a hypothetical contract with one unsigned integer, counter, deliberately assigned to persistent storage slot zero. Its setter writes the supplied integer and emits CounterChanged(oldValue, newValue). The fixture has no other way to change this slot.

At the parent block's end, the counter is zero. The next block contains two transactions to the setter, in order. Both complete successfully. The gas amounts below are invented accounting inputs, not measured costs or estimates for a deployed contract.

Point in the timelineCounter after executionRecorded eventReceipt statusCumulative gas
Parent block end0Outside this block——
Transaction index 0 writes 44CounterChanged(0, 4)145,000
Transaction index 1 writes 99CounterChanged(4, 9)176,000
New block end9Both receipts retained—76,000

The new block's state root can anchor a proof that slot zero contains 9. It cannot directly anchor a block-end proof that the same slot contains 4. Four was an intermediate value, overwritten before the block finished.

The receipts root can anchor the first receipt, including its event reporting the write to 4. The second receipt remains a separate leaf at index 1. Keeping both records does not create two simultaneous block-end values. Conversely, proving the final value 9 does not by itself identify the first transaction or recover its event.

The receipt's cumulative gas is also a sequence value. The execution receipt API defines it as the gas used by that transaction and all preceding transactions in the block. The second transaction's gas is therefore 76,000 − 45,000 = 31,000. Adding both cumulative values would produce 121,000 and count the first transaction twice. The transaction gas total is 45,000 + 31,000 = 76,000.

For a second fixture, let the final setter write zero instead of 9. This slot returns to its opening value while both successful receipts still exist. That does not imply the entire state root returns to the parent's root: sender nonces, fees and other account state also matter. A single unchanged slot is a much narrower claim than unchanged world state.

A storage proof passes through the account

In Ethereum's documented trie layout, the global state trie maps a hashed account address to an encoded account. That account contains its nonce, native balance, storage root and code hash. Each account's persistent storage has its own trie. These relationships are described in the Ethereum trie reference.

For the counter fixture, verification has two linked stages. First, verify the contract's account against the selected header's state root. Obtain its authenticated storage root from the verified account. Then verify slot zero and its value against that storage root. A storage proof checked against an unrelated root does not establish anything about the intended contract.

EIP-1186 explains this account-plus-storage proof structure. The current eth_getProof API reference takes the account address, storage keys and selected block, then returns separate account and storage proofs. Treat its summary values as claims until the proof has been checked.

In the zero-ending variant, writing zero removes the storage key under the BPO2 execution specification. The proof can therefore establish the slot's zero value through authenticated absence, using EIP-1186's non-inclusion rules. A missing RPC result or unavailable proof is not a verified absence.

The fixture deliberately specifies the slot. In a real contract, find the correct layout before requesting a proof. Solidity 0.8.30 documents packing, inheritance, mappings and dynamic arrays in its storage layout rules. A user-facing function name does not reveal a universal slot number. An ERC-20 balance might occupy a mapping entry; a getter might combine several slots or external calls.

That distinction matters for vaults too. A proof of a stored share count establishes that stored quantity. Converting it into underlying assets requires the vault's conversion logic and relevant state, as explained in ERC-4626 shares versus assets. Cryptographic inclusion does not choose the correct unit or valuation rule.

A receipt proof follows the transaction's position

The receipt trie uses the transaction's index within the block as its key, encoded with Ethereum's Recursive Length Prefix (RLP) format. It does not use the transaction hash as that trie key. EIP-2718 defines this relationship and requires the receipt type to match the transaction type at the corresponding index.

For legacy receipts after Byzantium, the committed payload contains status, cumulative gas, a logs bloom and logs. Typed receipts carry a type prefix and their type-specific payload. A verifier must reconstruct the correct consensus encoding; hashing the JSON response as received is not equivalent.

The RPC receipt includes useful context such as transaction hash, block hash and individual gas used. Those convenient response fields should not all be assumed to be fields inside the consensus receipt bytes. The receipt response schema also returns data without a receipt trie proof field. Fetching that response is one step toward collecting evidence, rather than a completed inclusion verification.

If the claim names a transaction hash, authenticate the transaction at the same index against the block's transactions root as well, or state the trusted source supplying that association. A verified receipt leaf establishes the receipt at a position; the matching transaction evidence establishes which transaction occupied it.

An old guide may show a state root inside each receipt. EIP-658 replaced that intermediate state root with the top-level success or failure status from Byzantium onward. A successful receipt in this format does not contain a proof of every storage value immediately after its transaction.

Valid evidence can still answer the wrong question

An inclusion proof authenticates the bytes being proved. Interpreting those bytes remains a separate task.

The event is authentic, but its meaning is overstated. In the counter fixture, the setter's defined behavior makes its event useful evidence of a write. An arbitrary contract could emit an event without making the expected storage change. For token-delivery investigations, use the separate receipt success versus token transfer guide. Proof of an emitted record does not automatically prove the intended payment.

A possible log match is treated as inclusion. A bloom filter helps narrow a search; it is not the receipt's trie path. The log bloom versus proof comparison explains what additional evidence establishes an actual log's inclusion. Finding a candidate block and proving the relevant receipt are different operations.

A failed transaction is treated as though it never existed. A reverted transaction can still have a receipt reporting failure. EIP-140 specifies that reverted execution loses its state changes and logs. That rollback does not remove the transaction's place in the block or turn its receipt into evidence of a successful write.

The requested state is older than the provider's proof history. A header can remain available when the necessary historical trie nodes are unavailable. Geth's archive documentation specifically distinguishes historical state access from historical proof support, including version and trie-retention requirements for path-based archives. Test the required block and method; an archive label alone is insufficient.

The proof belongs to a different block. A correct path under one root will not establish the claim under another. Record the block hash with the roots and all collected evidence. The RPC batch versus block snapshot guide covers consistent state selection when several reads are involved. A root supplied by the same untrusted server as the proof still needs an independently trusted header anchor.

Write the claim before collecting the proof

Use a compact verification record so that another reader can reproduce the conclusion. Specify the network and block confidence policy alongside the block hash; inclusion beneath an anchored header and the confidence attached to that header are separate checks.

Record fieldCounter example
Exact claimSlot zero contains 9 at the selected block's end
Object identityIntended network and contract address
Time scopeSelected block hash and block-end state
Root to trustExecution state root from the accepted header
Evidence pathAccount proof, authenticated storage root, slot proof
InterpretationSlot layout identifies the counter; integer units
Accepted conclusionVerified slot value is 9 in that state

For the event claim, change the record: identify the receipt index, receipt encoding, log position, emitter and decoding rules, then verify against the receipts root. Add transaction evidence if the conclusion names a particular transaction. Preserve raw evidence and the verifier's result, including errors.

Exercise the boundary with the hypothetical timeline. A verifier should accept the block-end value 9 with valid state evidence, reject 4 as that final slot value, and separately accept the first receipt's authentic event. Alter the value, index or selected root and require verification to fail. These are validation targets, not claims that a live proof-verification test was performed here.

Finish each investigation with one bounded sentence: this account or storage value was verified at this block end, or this receipt and log were verified at this position in this block. If the available evidence supports only one sentence, leave the other claim open.

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.