Skip to content

Commit

Permalink
Update EIP-2935: update for Prague devnet1
Browse files Browse the repository at this point in the history
Merged by EIP-Bot.
  • Loading branch information
s1na authored May 17, 2024
1 parent 9ed9638 commit 3948040
Showing 1 changed file with 12 additions and 44 deletions.
56 changes: 12 additions & 44 deletions EIPS/eip-2935.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ created: 2020-09-03

## Abstract

Store last `HISTORY_SERVE_WINDOW` historical block hashes in a contract, and modify the `BLOCKHASH (0x40)` opcode to read and serve from this contract storage.
Store last `HISTORY_SERVE_WINDOW` historical block hashes in the storage of a system contract as part of the block processing logic.

## Motivation

Currently `BLOCKHASH` opcode accesses history to resolve hash of the block number in EVM. However a more stateless client friendly way is to maintain and serve these hashes from state.
EVM implicitly assumes the client has the recent block (hashes) at hand. This assumption is not future-proof given the prospect of stateless clients. Including the block hashes in the state will allow bundling these hashes in the witness provided to a stateless client. This is already possible in the MPT and will become more efficient post-Verkle.

Although this is possible even in Merkle trie state, but Verkle trie state further allows bundling the `BLOCKHASH` witnesses (along with other witnesses) in an efficient manner making it worthwhile to have these in state.
Extending the range of blocks BLOCKHASH can serve is a semantics change. Using the contract storage would allow extending that range in a soft-transition. Rollups can benefit from the longer history window through directly querying this contract.

A side benefit of this approach could be that it allows building/validating proofs related to last `HISTORY_SERVE_WINDOW` ancestors directly against the current state.

Expand All @@ -33,38 +33,17 @@ 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.


At the start of processing any block where `block.timestamp >= FORK_TIMESTAMP` (ie. before processing any transactions), update the history in the following way:

```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)

# If this is the fork block, add the parent's direct `HISTORY_SERVE_WINDOW - 1` ancestors as well
if block.parent.timestamp < FORK_TIMESTAMP:
ancestor = block.parent
for i in range(HISTORY_SERVE_WINDOW - 1):
# stop at genesis block
if ancestor.number == 0:
break

ancestor = ancestor.parent
state.insert_slot(HISTORY_STORAGE_ADDRESS, ancestor.number % HISTORY_SERVE_WINDOW, 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` > `BLOCKHASH_OLD_WINDOW` ancestors (up until genesis).
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.

For resolving the `BLOCKHASH` opcode this fork onwards (`block.timestamp >= FORK_TIMESTAMP`), switch the logic to:

```python
def resolve_blockhash(block: Block, state: State, arg: uint64):
# check the wrap around range
if arg >= block.number or (arg + HISTORY_SERVE_WINDOW) < block.number
return 0

return state.load_slot(HISTORY_STORAGE_ADDRESS, arg % HISTORY_SERVE_WINDOW)
```
The `BLOCKHASH` opcode semantics remains the same as before.

### Contract Implementation

Expand Down Expand Up @@ -170,21 +149,16 @@ The sender of the transaction can be calculated as `0xa4690f0ed0d089faa1e0ad94c8
Some activation scenarios:

* 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.
* for activation at block `10000`, block `1808-9999`'s hashes will be persisted in the slot and `BLOCKHASH` for `1807` or less would resolve to `0` as only `HISTORY_SERVE_WINDOW` are persisted.
* for activation at block `1`, only genesis hash will be written at slot `0`.
* for activation at block `32`, block `31`'s hash will be written to slot `31`. Every other slot will be `0`.

### [EIP-158](./eip-158.md) handling

This address is currently exempt from [EIP-158](./eip-158.md) cleanup in Kaustinen Verkle Testnet but we plan to address this in the following way:

* Deploy a contract à la [EIP-4788](./eip-4788.md) which just supports `get` method to resolve the BLOCKHASH as per the logic defined in `resolve_blockhash` (and use the generated address as the BLOCKHASH contract address).
* While the clients are expected to directly read from state (or maintain and serve from memory) to resolve BLOCKHASH opcode, this contract's `get` could be invoked by transaction (via another contract or directly) leading to a normal contract execution (and gas consumption) as per the semantics of the contract call.

The bytecode above will be deployed à la [EIP-4788](./eip-4788.md). It just supports a `get` method to resolve block hashes. As such the account at `HISTORY_STORAGE_ADDRESS` will have code and a nonce of 1, and will be exempt from EIP-158 cleanup.

### Gas costs and witnesses
### Gas costs

Since now `BLOCKHASH` is served from state, the clients now **additionally** charge the corresponding warm or cold `SLOAD` costs. For verkle based networks this would imply doing and bundling corresponding accesses (and gas charges) of `SLOAD`.
The gas cost of the `BLOCKHASH` opcode is unchanged. Importantly the processing at the beginning of the block, i.e. `process_block_hash_history`, will not warm the `HISTORY_STORAGE_ADDRESS` account or its storage slots as per [EIP-2929](./eip-2929.md) rules. As such the first call to the contract will pay for warming up the account and storage slots it accesses.

## Rationale

Expand All @@ -201,22 +175,16 @@ Second concern was how to best transition the BLOCKHASH resolution logic post fo
1. Either waiting for `HISTORY_SERVE_WINDOW` blocks for the entire relevant history to persist
2. Storing of all last `HISTORY_SERVE_WINDOW` block hashes on the fork block.

We choose to go with later as it alleviates the need to detect fork activation height to transition to the new logic in backward compatible manner as the entire `BLOCKHASH` requisite 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 relatively limited. Most clients write this into their flat db/memory caches and just requires reading last `HISTORY_SERVE_WINDOW` from the chain history.
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.

## Backwards Compatibility

The behavior of `BLOCKHASH` opcode gets extended in backward compatible manner as the history it can serve will get extended upto `HISTORY_SERVE_WINDOW` on the fork block. However the gas charges will also get bumped as per the additional `SLOAD` costs.
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.

## Test Cases

TBD

## Reference Implementation

* PR 28878 of go-ethereum
* Active on verkle-gen-devnet-5 for its verkle implementation

## Security Considerations

Having contracts (system or otherwise) with hot update paths (branches) poses a risk of "branch" poisioning attacks where attacker could sprinkle trivial amounts of eth around these hot paths (branches). But it has been deemed that cost of attack would escalate significantly to cause any meaningful slow down of state root updates.
Expand Down

0 comments on commit 3948040

Please sign in to comment.