[Bug] EIP-2935 ring buffer uses window 8192 while the preinstalled history contract uses 8191, so get(n) returns the wrong block hash
- Dominant language
- Go
- Stars
- 164
- Forks
- 213
- Avg merge
- 3d 58m
- Merged PRs (30d)
- 12
Description
## Type
- [x] Bug
- [ ] Feature
- [ ] Proposal / Discussion
## Summary
The EIP-2935 ring buffer is written by the keeper with a window of **8192** slots, but the preinstalled EIP-2935 system contract (`params.HistoryStorageCode` from go-ethereum) reads with the spec's window of **8191** (`0x1fff` in the bytecode, `params.HistoryServeWindow = 8191`). Once the chain passes height 8191 the two disagree: calling the contract at `0x0000F90827F1C53a10cb7A02335B175320002935` with `get(n)` returns the hash of a *different* block (or zero), while the `BLOCKHASH` opcode (`GetHashFn -> GetHeaderHash`, same `% 8192`) is self-consistent and returns the right one.
- https://github.com/cosmos/evm/blob/469142db41aee479102cf878f8ac2bbe2b0c7a22/x/vm/types/params.go#L51 — `DefaultHistoryServeWindow = 8192 // same as EIP-2935` (EIP-2935 is 8191)
- https://github.com/cosmos/evm/blob/469142db41aee479102cf878f8ac2bbe2b0c7a22/x/vm/keeper/keeper.go#L441 — `ringIndex := uint64(ctx.BlockHeight()) % window`
- https://github.com/cosmos/evm/blob/469142db41aee479102cf878f8ac2bbe2b0c7a22/x/vm/keeper/keeper.go#L460 — same in `GetHeaderHash`
- https://github.com/cosmos/evm/blob/469142db41aee479102cf878f8ac2bbe2b0c7a22/x/vm/types/preinstall.go#L34-L38 — default preinstall deploys the upstream bytecode, which does `sload(n % 8191)`
## Reproduction (for bugs)
Unit-level repro on `main` (`469142d`), added as a `KeeperTestSuite` method under `tests/integration/x/vm`:
```go
func (s *KeeperTestSuite) TestEIP2935WindowMismatch() {
s.SetupTest()
k := s.Network.App.GetEVMKeeper()
ctx := s.Network.GetContext()
// deploy the standard EIP-2935 contract code at the history storage address
codeHash := crypto.Keccak256(params.HistoryStorageCode)
s.Require().NoError(k.SetAccount(ctx, params.HistoryStorageAddress, statedb.Account{Nonce: 1, CodeHash: codeHash}))
k.SetCode(ctx, codeHash, params.HistoryStorageCode)
hashA := common.BytesToHash(crypto.Keccak256([]byte("block-8192")))
hashB := common.BytesToHash(crypto.Keccak256([]byte("block-8193")))
ctxA := ctx.WithBlockHeight(8192).WithHeaderHash(hashA.Bytes())
k.SetHeaderHash(ctxA)
ctxB := ctx.WithBlockHeight(8193).WithHeaderHash(hashB.Bytes())
k.SetHeaderHash(ctxB)
// BLOCKHASH path at height 8193 asking for 8192 -> hashA
s.Require().Equal(hashA, k.GetHashFn(ctxB)(8192))
// direct call to the EIP-2935 contract at height 8193: get(8192)
input := common.LeftPadBytes(big.NewInt(8192).Bytes(), 32)
to := params.HistoryStorageAddress
from := s.Keyring.GetAddr(0)
data := hexutil.Bytes(input)
args, _ := json.Marshal(&types.TransactionArgs{From: &from, To: &to, Data: &data})
res, err := k.EthCall(ctxB, &types.EthCallRequest{Args: args, GasCap: config.DefaultGasCap})
s.Require().NoError(err)
s.Require().Empty(res.VmError)
s.Require().Equal(hashA, common.BytesToHash(res.Ret))
}
```
Output:
```
BLOCKHASH(8192)=0x1097b914fd7629a6c03be2b1e8d40194a137124183121e732d7beabacb5bf951
contract.get(8192)=0x63e4cfb18e58d6bc1a279137dd90feb9965a9bf970ca4775229de62e5b4d72a7 <- this is hashB (block 8193)
Error: Not equal
```
The keeper stored hash(8192) at slot `8192 % 8192 = 0` and hash(8193) at slot 1; the contract reads `8192 % 8191 = 1` and returns hash(8193).
## Impact
Anything that reads history through the EIP-2935 contract instead of `BLOCKHASH` (that is the whole point of the EIP: hashes older than 256 blocks, used by bridges / light-client verifiers / proof contracts) gets a wrong hash on every chain that is past height 8191 with the default preinstalls. Since the value is read on-chain the result is consensus-visible, so a fix changes ring indices and needs a coordinated upgrade (the constant is also exposed as the `history_serve_window` param, default 8192).
Suggested fix: use 8191 (`params.HistoryServeWindow`) for the default and in `SetHeaderHash`/`GetHeaderHash`, plus a migration/upgrade handler for existing chains. Happy to send a PR once the upgrade strategy is decided.
## Related
- #406 / #407 (EIP-2935 implementation)
## Checklist
- [x] Linked to a GitHub Issue (or this is the Issue)
- [x] Repro steps included (for bugs)
- [x] Impact described
- [x] I understand minor typo/style doc fixes will not be accepted
Contributor guide
Research direction
Start with x/vm/types/params.go, x/vm/keeper/keeper.go, and x/vm/types/preinstall.go to trace the configured window, ring indexing, and preinstalled bytecode. Run the KeeperTestSuite reproduction under tests/integration/x/vm and compare BLOCKHASH with contract.get. Done requires consistent EIP-2935 reads and a decided migration or upgrade path for existing chains.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- blockchain
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100