Summary
Mainnet sync is blocked at block 2662438 due to a gas used mismatch:
block gas used mismatch: got 823153, expected 825454; gas spent by each transaction: [(0, 37195), (1, 823153)]
- Computed by morph-reth: 823153
- Expected (block header): 825454
- Difference: 2301 gas
Confirmed Root Cause
The mismatch is caused by BLOCKHASH opcode semantics, not precompile stub accounting.
For tx index 1 in block 2662438, canonical Morph geth behavior for BLOCKHASH(2662437) returns:
keccak(chain_id || block_number) where both are 8-byte big-endian values
- with chain_id=2818 and number=2662437 ->
0x6d2426d9b8d6f63ec1a38d3a4e7b88f318ebe9f0d837c4781852168c5bd2678e
morph-reth was still using header-hash-style lookup through host block_hash, producing a different value (0xa190...), which changed downstream branch/slot access and cold/warm gas charging distribution, leading to the observed 2301 gas drift.
Why Prover Didn't Catch It
prover path already implements Morph-specific BLOCKHASH in its DB adapters (block_hash_ref in both rpc-db and witness-db), so prover execution remained aligned with geth while node execution in morph-reth diverged.
Fix
In crates/revm/src/evm.rs, override opcode 0x40 with Morph behavior:
- return
keccak(chain_id(8-byte BE) || requested_block_number(8-byte BE))
- only within the last-256-block window (
[current-256, current)), otherwise zero
Also added unit tests:
- golden vector test for block 2662438 context
- window-boundary behavior test
Reproduction
Sync mainnet from genesis (or from a snapshot before block 2662438). Before fix, import fails repeatedly at block 2662438 with gas mismatch. After fix, block import succeeds.