Problem
MakeReceipt (execution/protocol/state_processor.go:160-165) only ever sets Status on the receipt, never PostState. So WriteReceiptCacheV2 → ReceiptForStorage only persists the 1-byte status.
For pre-Byzantium blocks (mainnet < 4,370,000), the canonical consensus receipt RLP is [postState (32 bytes), cumGas, bloom, logs] — see EIP-658 and Yellow Paper §4.3.1. Without PostState, RCache cannot reconstruct the canonical receipt root for those blocks: Receipt.statusEncoding() returns 1 byte where the original encoding had 32 bytes, so DeriveSha(receipts) produces a different root than the header.
Impact
- The new
ReceiptRootIntegrity check (PRs landing on dwightbot/receipt-root-range-opt, commits 7cceabe25e / 1eb5b2af3b) cannot validate pre-Byzantium blocks. As a workaround we now start it at ByzantiumBlock (commit 0b7381a8e9).
- Any other consumer that needs to reconstruct pre-Byzantium consensus receipt encoding from RCache alone will be similarly blocked.
- Today the only path that recovers
PostState is rpc/jsonrpc/receipts/receipts_generator.go:421,625, which re-derives it by replaying the transaction and computing commitment from history — only works on datadirs with commitment history enabled.
Suggested fixes (not exhaustive)
- Compute and persist
PostState per tx in MakeReceipt for pre-Byzantium blocks (cheap conceptually, but requires intermediate-state-root commitment per tx — currently only available via commitment history).
- Backfill
PostState for pre-Byzantium ranges from snapshots once (one-shot migration), so RCache becomes complete.
- Document that RCache is only complete from Byzantium onwards, and gate any consumers (like the integrity check) on
cc.IsByzantium(blockNum).
References
- EIP-658: https://eips.ethereum.org/EIPS/eip-658
execution/types/receipt.go:342-350 — statusEncoding() returns r.PostState if non-empty, otherwise 1-byte status.
execution/protocol/state_processor.go:160-165 — MakeReceipt only sets Status.
db/integrity/rcache_receipt_root.go — current workaround skipping pre-Byzantium.
Problem
MakeReceipt(execution/protocol/state_processor.go:160-165) only ever setsStatuson the receipt, neverPostState. SoWriteReceiptCacheV2→ReceiptForStorageonly persists the 1-byte status.For pre-Byzantium blocks (mainnet < 4,370,000), the canonical consensus receipt RLP is
[postState (32 bytes), cumGas, bloom, logs]— see EIP-658 and Yellow Paper §4.3.1. WithoutPostState, RCache cannot reconstruct the canonical receipt root for those blocks:Receipt.statusEncoding()returns 1 byte where the original encoding had 32 bytes, soDeriveSha(receipts)produces a different root than the header.Impact
ReceiptRootIntegritycheck (PRs landing ondwightbot/receipt-root-range-opt, commits7cceabe25e/1eb5b2af3b) cannot validate pre-Byzantium blocks. As a workaround we now start it atByzantiumBlock(commit0b7381a8e9).PostStateisrpc/jsonrpc/receipts/receipts_generator.go:421,625, which re-derives it by replaying the transaction and computing commitment from history — only works on datadirs with commitment history enabled.Suggested fixes (not exhaustive)
PostStateper tx inMakeReceiptfor pre-Byzantium blocks (cheap conceptually, but requires intermediate-state-root commitment per tx — currently only available via commitment history).PostStatefor pre-Byzantium ranges from snapshots once (one-shot migration), so RCache becomes complete.cc.IsByzantium(blockNum).References
execution/types/receipt.go:342-350—statusEncoding()returnsr.PostStateif non-empty, otherwise 1-byte status.execution/protocol/state_processor.go:160-165—MakeReceiptonly setsStatus.db/integrity/rcache_receipt_root.go— current workaround skipping pre-Byzantium.