-
Notifications
You must be signed in to change notification settings - Fork 5.3k
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: update mechanism via system call #8816
Changes from 5 commits
574bca1
28b247c
b6bc0f2
826e9f9
6b31bd4
d19422c
dd72f79
061d3cb
171de2c
75f6e89
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,7 @@ | |
eip: 2935 | ||
title: Serve historical block hashes from state | ||
description: Store and serve last 8192 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), Ignacio Hagopian (@jsign), Jochem Brouwer (@jochem-brouwer) | ||
author: Vitalik Buterin (@vbuterin), Tomasz Stanczak (@tkstanczak), Guillaume Ballet (@gballet), Gajinder Singh (@g11tech), Tanishq Jasoria (@tanishqjasoria), Ignacio Hagopian (@jsign), Jochem Brouwer (@jochem-brouwer), Sina Mahmoodi (@s1na) | ||
discussions-to: https://ethereum-magicians.org/t/eip-2935-save-historical-block-hashes-in-state/4565 | ||
status: Review | ||
type: Standards Track | ||
|
@@ -34,21 +34,42 @@ A side benefit of this approach could be that it allows building/validating proo | |
|
||
This EIP specifies for storing last `HISTORY_SERVE_WINDOW` block hashes in a ring buffer storage of `HISTORY_SERVE_WINDOW` length. Note that `HISTORY_SERVE_WINDOW` > `BLOCKHASH_SERVE_WINDOW` (which remains unchanged). | ||
|
||
At the start of processing any block where `block.timestamp >= FORK_TIMESTAMP` (ie. before processing any transactions), update the state directly in the following way: | ||
### Block processing | ||
|
||
```python | ||
def process_block_hash_history(block: Block, state: State): | ||
if block.timestamp >= FORK_TIMESTAMP: | ||
state.insert_slot(HISTORY_STORAGE_ADDRESS, (block.number-1) % HISTORY_SERVE_WINDOW , block.parent.hash) | ||
``` | ||
At the start of processing any block where `block.timestamp >= FORK_TIMESTAMP` (ie. before processing any transactions), call to `HISTORY_STORAGE_ADDRESS` as `SYSTEM_ADDRESS` with the 32-byte input of `block.parent.hash`, a gas limit of `30_000_000`, and `0` value. This will trigger the `set()` routine of the history contract. This is a system operation following the same convention as [EIP-4788](./eip-4788.md) and therefore: | ||
|
||
* the call must execute to completion | ||
* the call does not count against the block's gas limit | ||
* the call does not follow the [EIP-1559](./eip-1559.md) burn semantics - no value should be transferred as part of the call | ||
* if no code exists at `HISTORY_STORAGE_ADDRESS`, the call must fail silently | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd have preferred something which shows that this is not a "new thing", rather just a clarification of existing semantics. Something like
I also think it is incorrect to say that the call must fail. It's a no-op, it doesn't put a failure-symbol on any stack anywhere. |
||
|
||
Alternatively clients can also choose to do a system update via a system call to the contract `set` mechanism defined in the following sections. | ||
Note: Alternatively clients can choose to directly write to the storage of the contract but EVM calling the contract remains preferred. Refer to the rationale for more info. | ||
|
||
Note that, it will take `HISTORY_SERVE_WINDOW` blocks after `FORK_TIMESTAMP` to completely fill up the ring buffer. The contract will only contain the parent hash of the fork block and no hashes prior to that. | ||
|
||
As mentioned earlier the `BLOCKHASH` opcode semantics remains the same as before. However the clients which want to leverage the history from state may do so making sure the query is within `BLOCKHASH_SERVE_WINDOW` and is available in the contract. | ||
|
||
### Contract Implementation | ||
### EVM Changes | ||
|
||
The `BLOCKHASH` opcode semantics remains the same as before. | ||
|
||
### Block hash history contract | ||
|
||
The history contract has two operations: `get` and `set`. The `set` operation is invoked only when the `caller` is equal to the `SYSTEM_ADDRESS` as per [EIP-4788](./eip-4788.md). Otherwise the `get` operation is performed. | ||
|
||
#### `get` | ||
|
||
It is used from the EVM for looking up block hashes. | ||
|
||
* Callers provide the block number they are querying in a big-endian encoding. | ||
* If calldata is bigger than 2^64-1, revert. | ||
* For any output outside the range of [block.number-`HISTORY_SERVE_WINDOW`, block.number-1] return 0. | ||
|
||
#### `set` | ||
|
||
* Caller provides `block.parent.hash` as calldata to the contract. | ||
* Set the storage value at `block.number-1 % HISTORY_SERVE_WINDOW` to be `calldata[0:32]`. | ||
|
||
#### Bytecode | ||
|
||
Exact evm assembly that can be used for the history contract: | ||
|
||
|
@@ -136,10 +157,6 @@ stop | |
Corresponding bytecode: | ||
`0x3373fffffffffffffffffffffffffffffffffffffffe1460575767ffffffffffffffff5f3511605357600143035f3511604b575f35612000014311604b57611fff5f3516545f5260205ff35b5f5f5260205ff35b5f5ffd5b5f35611fff60014303165500` | ||
|
||
#### Contract `get` and `set` mechanism | ||
|
||
The update mechanism is the same as [EIP-4788](./eip-4788.md). While executing the system contract is not future-proof, this update method remains the favored one until the verkle fork. | ||
|
||
#### Deployment | ||
|
||
A special synthetic address is generated by working backwards from the desired deployment transaction: | ||
|
@@ -200,6 +217,23 @@ Second concern was how to best transition the BLOCKHASH resolution logic post fo | |
|
||
We choose to go with the former. It simplifies the logic greatly. It will take roughly a day to bootstrap the contract. Given that this is a new way of accessing history and no contract depends on it, it is deemed a favorable tradeoff. | ||
|
||
### Inserting the parent block hash | ||
|
||
Clients have generally two options for inserting the parent block hash into state: | ||
|
||
1. Performing a sytstem call to `HISTORY_STORAGE_ADDRESS` and letting that handle the storing in state. | ||
s1na marked this conversation as resolved.
Show resolved
Hide resolved
|
||
2. Avoid EVM processing and directly write to the state trie. | ||
|
||
The latter option is as follows: | ||
|
||
```python | ||
def process_block_hash_history(block: Block, state: State): | ||
if block.timestamp >= FORK_TIMESTAMP: | ||
state.insert_slot(HISTORY_STORAGE_ADDRESS, (block.number-1) % HISTORY_SERVE_WINDOW , block.parent.hash) | ||
``` | ||
|
||
The first option is recommended until the Verkle fork, to stay consistent with [EIP-4788](./eip-4788.md) and to issues for misconfigured networks where this EIP is activated but history contract hasn't been deployed. The recommendation may be reconsidered at the Verkle fork to avoid working around attaching the history contract to every block's Verkle witness. | ||
s1na marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure I fully get this. "The recommendation may be reconsidered at the Verkle fork to avoid working around attaching the history contract to every block's Verkle witness." Do you mean that at verkle, we may want to directly update the state rather than access the system contract in order to avoid having to include the system contract to every block's verkle witness? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It's not about supporting misconfigured networks rather reducing damage in case contract is not deployed timely on a network. Calling the contract vs setting the store will behave differently if the contract is not deployed causing a network split. And this and 4788 are the only EIPs that in addition to activating the EIP someone needs to do an extra action of sending the transaction. Easy to miss or get wrong. Not using this as an argument but as an anecdote: a bug in geth's dev mode for 4788 until recently.
This suggestion existed in the EIP (I believe put there by @gballet), I moved it to the rationale section as I believe it doesn't exist in the specification. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Makes sense concerning network split, thanks for explaining. Concerning the last sentence, can we clarify the phrasing? Maybe it's just me, but there's a double negative in there that confused me, what does "avoid working around attaching" mean? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think now it is simplified. |
||
|
||
## Backwards Compatibility | ||
|
||
This EIP introduces backwards incompatible changes to the block validation rule set. But neither of these changes break anything related to current user activity and experience. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does this mean? Why even specify it? Should evm-implementors use
30MGas
or some infinite supply? IMO this sentence only brings confusionThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The system call semantics are directly copied from EIP-4788 for consistency since they should behave in the same manner. It applies to your next comment as well. I personally think it doesn't harm as long as the limit is high enough. Also makes me feel better when there is a limit even tho the contract is very simple and has been fully vetted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't get me wrong: I think the limit is good. That's why I think the sentence "must execute to completion" is bad, because it goes against the limit. Because if it must execute to completion, that implies infinite gas. If someone places an infinite loop there, then, without this sentence here, all clients would agree to burn 30M gas and done. With the sentence "must execute to completion", it can arguably be correct to loop forever.