---
eip: 7709
title: Read BLOCKHASH from Storage and Update Cost
description: Read the `BLOCKHASH (0x40)` opcode from the EIP-2935 system contract storage and adjust its gas cost to reflect storage access.
author: Vitalik Buterin (@vbuterin), Tomasz Stanczak (@tkstanczak), Guillaume Ballet (@gballet), Gajinder Singh (@g11tech), Tanishq Jasoria (@tanishqjasoria), Ignacio Hagopian (@jsign), Jochem Brouwer (@jochem-brouwer), Gabriel Rocheleau (@gabrocheleau)
discussions-to: https://ethereum-magicians.org/t/eip-7709-read-blockhash-opcode-from-storage-and-adjust-gas-cost/20052
status: Draft
type: Standards Track
category: Core
created: 2024-05-18
requires: 2935
---

## Abstract

Update the `BLOCKHASH (0x40)` opcode to read and serve from the system contract storage and charge the **additional** (cold or warm) storage costs.

## Motivation

The `BLOCKHASH (0x40)` opcode currently assumes that the client has access to recent block history. This makes it a protocol special case: it depends on historical chain data, but it is not modeled like other state-backed reads.

With [EIP-2935](./eip-2935.md), recent block hashes are stored in the system contract storage. This allows in-window `BLOCKHASH` lookups to be modeled as storage-backed accesses while preserving the existing `BLOCKHASH` return-value semantics.

## Specification

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119) and [RFC 8174](https://www.rfc-editor.org/rfc/rfc8174).

| Parameter                 | Value  |
| ------------------------- | ------ |
| `FORK_TIMESTAMP`          | TBD    |
| `HISTORY_STORAGE_ADDRESS` | `0x0000F90827F1C53a10cb7A02335B175320002935` |
| `BLOCKHASH_SERVE_WINDOW`  | `256`  |
| `HISTORY_SERVE_WINDOW`    | `8191` |

The `BLOCKHASH` opcode semantics remains the same as before. From the `fork_block` (defined as `fork_block.timestamp >= FORK_TIMESTAMP and fork_block.parent.timestamp < FORK_TIMESTAMP`), the `BLOCKHASH` instruction should be updated to resolve block hash in the following manner:

```python
def resolve_blockhash(block: Block, state: State, arg: uint64):
  # note that outside the BLOCKHASH_SERVE_WINDOW we continue to return 0
  # despite the 2935 history contract being able to serve more hashes
  if arg >= block.number or (arg + BLOCKHASH_SERVE_WINDOW) < block.number:
    return 0

  # performs an sload on arg % HISTORY_SERVE_WINDOW including gas charges,
  # warming effects as well as state-access recording
  #
  # note that the `BLOCKHASH_SERVE_WINDOW` and the 2935 ring buffer window
  # `HISTORY_SERVE_WINDOW` for slot calculation are different
  return state.load_slot(HISTORY_STORAGE_ADDRESS, arg % HISTORY_SERVE_WINDOW)
```

If the `arg` is within the correct `BLOCKHASH` window, clients MAY choose to either

* do a direct `SLOAD` from state, or
* do a system call to [EIP-2935](./eip-2935.md) contract via its `get` mechanism (caller other than `SYSTEM_ADDRESS`) or
* serve from memory or as per current designs if maintaining requisite history (full clients for e.g.)

Regardless of the chosen resolution method, clients MUST apply the entire semantics and effects of the `SLOAD` operation as defined by the active fork if the `arg` is within the correct `BLOCKHASH` window:

* `SLOAD` gas costs (cold or warm) for the `arg % HISTORY_SERVE_WINDOW` slot.
* `SLOAD` after effects on the slot (warming the slot)
* Any state-access recording required by the active fork for the corresponding `SLOAD`.

### Activation

This EIP specifies the transition to the new logic assuming that [EIP-2935](./eip-2935.md) has been activated:

* sufficiently ahead of this EIP's activation (>= `BLOCKHASH_SERVE_WINDOW`) or
* at genesis for testnets/devnets where this EIP could also be activated at genesis.

### Gas costs

As described above, if the `arg` to be resolved is within the correct window, the corresponding `SLOAD` charges and accesses are to be applied for the slot `arg % HISTORY_SERVE_WINDOW`. Note that the `HISTORY_SERVE_WINDOW` and `BLOCKHASH_SERVE_WINDOW` are different.

### Reading from the System contract

Even if the clients choose to resolve `BLOCKHASH` through system call to [EIP-2935](./eip-2935.md) contract, the gas cost for the system code execution is not applied. Only the effect of `SLOAD` is applied as described above.

## Rationale

* The updated gas cost matches the accessed resource, which is equivalent to reading from storage.
* Charging normal `SLOAD`-like cold and warm costs keeps `BLOCKHASH` aligned with existing state-access pricing instead of adding a separate gas rule for recent block hashes.
* A client can alternatively prove recent block hashes through a chain of parent headers, but that keeps `BLOCKHASH` as a special case in witness construction rather than using the state access machinery provided by [EIP-2935](./eip-2935.md).
* Always charging the warm `SLOAD` cost, or introducing a custom `BLOCKHASH` price, could reduce compatibility risk but would introduce a new special-case gas rule.
* The [EIP-2935](./eip-2935.md) system contract execution charges and accesses are not applied to keep the gas low and to keep things simple for clients which choose to resolve `BLOCKHASH` in other ways (directly or through memory/maintained history).

Note that `BLOCKHASH` opcode only serves a limited `BLOCKHASH_SERVE_WINDOW` to be backward compatible (and to not extend the above exemptions). For deeper accesses one will need to directly call [EIP-2935](./eip-2935.md) system contract which will lead to a normal contract execution (as well as charges and accesses).

## Backwards Compatibility

This EIP does not change the return-value semantics of `BLOCKHASH`.

This EIP introduces a significant increase in the cost of in-window `BLOCKHASH` queries, which could break use-cases that rely on the previous gas cost. Also, this EIP introduces a breaking change in the case where less than `BLOCKHASH_SERVE_WINDOW` elapse between the [EIP-2935](./eip-2935.md) fork and this EIP's fork (unless [EIP-2935](./eip-2935.md) is activated in genesis for e.g. in testnets/devnets) as the [EIP-2935](./eip-2935.md) system contract would not have saved the required history.

## Test Cases

* If `BLOCKHASH` is called with an argument outside the last `BLOCKHASH_SERVE_WINDOW` ancestors, or with an argument greater than or equal to the current block number, it returns `0` without applying additional storage access effects.
* If `BLOCKHASH` is called for an in-window ancestor and the corresponding storage slot is cold, the opcode charges the base `BLOCKHASH` cost plus the cold `SLOAD` cost.
* If `BLOCKHASH` is called more than once in the same transaction for a block number that maps to the same storage slot, the later lookup charges the warm `SLOAD` cost.
* The gas cost for each `BLOCKHASH` operation should still be charged, in addition to the `SLOAD` cost of each lookup (if performed).
* If the [EIP-2935](./eip-2935.md) contract is called directly (i.e. not through `BLOCKHASH`), then the gas costs, state-access recording, and any other effects are applied as per normal contract execution of the current fork.
* `BLOCKHASH` should be consistently resolved if this EIP is activated correctly `>= BLOCKHASH_SERVE_WINDOW` after [EIP-2935](./eip-2935.md).

## Security Considerations

No security considerations other than the ones contained in [EIP-2935](./eip-2935.md) are determined as of now.

## Copyright

Copyright and related rights waived via [CC0](../LICENSE.md).
