Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update EIP-2935: Move to Draft #8166

Merged
merged 20 commits into from
Apr 20, 2024
Merged
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 55 additions & 10 deletions EIPS/eip-2935.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
---
eip: 2935
title: Save historical block hashes in state
author: Vitalik Buterin (@vbuterin), Tomasz Stanczak (@tkstanczak)
description: store previous block hashes as storage slots of a system contract to allow for stateless execution
author: Vitalik Buterin (@vbuterin), Tomasz Stanczak (@tkstanczak), Guillaume Ballet (@gballet), Gajinder Singh (@g11tech), Tanishq Jasoria (@tanishqjasoria)
discussions-to: https://ethereum-magicians.org/t/eip-2935-save-historical-block-hashes-in-state/4565
status: Stagnant
status: Draft
type: Standards Track
category: Core
created: 2020-09-03
---

## Simple Summary
## Abstract

Store historical block hashes in a contract, and modify the `BLOCKHASH (0x40)` opcode to read this contract.

Expand All @@ -26,22 +27,65 @@

| Parameter | Value |
| - | - |
| `FORK_BLKNUM` | TBD |
| `FORK_TIMESTAMP` | TBD |
| `HISTORY_STORAGE_ADDRESS` | `0xfffffffffffffffffffffffffffffffffffffffe`|
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This conflicts with the system address from EIP-4788.

| `HISTORY_SERVE_WINDOW` | `256` |

At the start of processing any block where `block.number > FORK_BLKNUM` (ie. before processing any transactions), run `sstore(HISTORY_STORAGE_ADDRESS, block.number - 1, block.prevhash)`.
At the start of processing any block where `block.timestamp > FORK_TIMESTAMP` (ie. before processing any transactions), update the history in the following way:
g11tech marked this conversation as resolved.
Show resolved Hide resolved

When `block.number > FORK_BLKNUM + 256`, change the logic of the `BLOCKHASH` opcode as follows: if `FORK_BLKNUM <= arg < block.number`, return `sload(HISTORY_STORAGE_ADDRESS, arg)`. Otherwise return 0.
```python
def process_block_hash_history(block :Block, state: State):
if block.timestamp >= FORK_TIMESTAMP:
state.insert_slot(HISTORY_STORAGE_ADDRESS, block.number-1, block.parent.hash)

# If this is the first fork block, add the parent's direct 255 ancestors as well
if block.parent.timestamp < FORK_TIMESTAMP:
ancestor = block.parent
for i in range(HISTORY_SERVE_WINDOW - 1):
# stop at genesis block
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The wording here is confusing, I suggest the following:

Suggested change
# stop at genesis block
# if ancestor.number == 0, this means we inserted the
# genesis block hash in the previous iteration. Stop here.

if ancestor.number == 0:
break
g11tech marked this conversation as resolved.
Show resolved Hide resolved

ancestor = ancestor.parent
state.insert_slot(HISTORY_STORAGE_ADDRESS, ancestor.number, ancestor.hash)
```

Note that if this is the fork block, then it persists the additional requisite history that could be needed while resolving `BLOCKHASH` opcode for all of the `HISTORY_SERVE_WINDOW` ancestors (up untill genesis).
g11tech marked this conversation as resolved.
Show resolved Hide resolved

For resolving the `BLOCKHASH` opcode this fork onwards (`block.timestamp > FORK_TIMESTAMP`), switch the logic to:
g11tech marked this conversation as resolved.
Show resolved Hide resolved

```python
def resolve_blockhash(block: Block, state: State, arg: uint64):
assert(arg < block.number)
g11tech marked this conversation as resolved.
Show resolved Hide resolved
return state.load_slot(HISTORY_STORAGE_ADDRESS, arg)
```

Edge cases:

* For the fork to be activated at genesis, no history is written to the genesis state, and at the start of block `1`, genesis hash will be written as a normal operation to slot `0`.
* for activation at block `1`, only genesis hash will be written at slot `0` as there is no additional history that needs to be persisted.
* for activation at block `32`, block `31`'s hash will be written to slot `31` and additonal history for `0..30`'s hashes will be persisted, so all in all `0..31`'s hashes.

### EIP-158 exception

Check failure on line 69 in EIPS/eip-2935.md

View workflow job for this annotation

GitHub Actions / EIP Walidator

the first match of the given pattern must be a link

error[markdown-link-first]: the first match of the given pattern must be a link --> EIPS/eip-2935.md | 69 | ### EIP-158 exception | = info: the pattern in question: `(?i)(?:eip|erc)-[0-9]+` = help: see https://ethereum.github.io/eipw/markdown-link-first/

This address is currently exempt from [EIP-158](./eip-158.md) cleanup in Kaustinen Verkle Testnet but there are two ways this could be addressed before this EIP is adopted by ACD:

* Update the nonce to 1 in the fork block, or
* Deploy a contract à la [EIP-4788](./eip-4788.md) with `BLOCKHASH` opcode delegating call to this contract with appropriate args.

While the second option looks more elegant, but it has higher complexity as well as gas consumption considerations.
g11tech marked this conversation as resolved.
Show resolved Hide resolved

## Rationale

Very similar ideas were proposed before in EIP-98 and EIP-210. This EIP is a simplification, removing two sources of needless complexity:
Very similar ideas were proposed before in [EIP-210](./eip-210.md) et al. This EIP is a simplification, removing two sources of needless complexity:

1. Having a tree-like structure with multiple layers as opposed to a single list
2. Writing the EIP in EVM code

The former was intended to save space. Since then, however, storage usage has increased massively, to the point where even eg. 5 million new storage slots are fairly negligible compared to existing usage. The latter was intended as a first step toward "writing the Ethereum protocol in EVM" as much as possible, but this goal has since been de-facto abandoned.

Storing of all last `HISTORY_SERVE_WINDOW` block hashes alleviates the need to detect fork activation height to transition to the new logic as the entire required history will be available from the first block of the fork itself. The cost of doing so is marginal considering the `HISTORY_SERVE_WINDOW` being small.

## Backwards Compatibility

The range of `BLOCKHASH` is increased by this opcode, but behavior within the previous 256-block range remains unchanged.
Expand All @@ -50,13 +94,14 @@

TBD

## Implementation
## Reference Implementation

TBD
* PR 28878 of go-ethereum
* Active on verkle-gen-devet-3 for its verkle implementation

## Security Considerations

Adding ~2.5 million storage slots per year bloats the state somewhat (but not much relative to the hundreds of millions of existing state objects). However, this EIP is not intended to be permanent; when eth1 is merged into eth2, the BLOCKHASH opcode would likely be repurposed to use eth2's built-in history accumulator structure (see [phase 0 spec](https://github.com/ethereum/annotated-spec/blob/master/phase0/beacon-chain.md#slots_per_historical_root)).
Adding ~2.5 million storage slots per year bloats the state somewhat but not much relative to the hundreds of millions of existing state objects.

## Copyright

Expand Down
Loading