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-4788: Move to Draft #6859

Merged
merged 8 commits into from
Apr 11, 2023
Merged
Changes from 1 commit
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
127 changes: 48 additions & 79 deletions EIPS/eip-4788.md
Original file line number Diff line number Diff line change
@@ -1,140 +1,109 @@
---
eip: 4788
title: Beacon state root in the EVM
description: Expose beacon chain state roots in the EVM
author: Alex Stokes (@ralexstokes), Danny Ryan (@djrtwo)
discussions-to: https://ethereum-magicians.org/t/eip-4788-beacon-state-root-in-evm/8281
status: Stagnant
title: Beacon block root in the EVM
description: Expose beacon chain roots in the EVM
author: Alex Stokes (@ralexstokes), Ansgar Dietrichs (@adietrichs), Danny Ryan (@djrtwo)
discussions-to: https://ethereum-magicians.org/t/eip-4788-beacon-root-in-evm/8281
status: Draft
type: Standards Track
category: Core
created: 2022-02-10
---

## Abstract

Commit to the state root of the beacon chain in the `ommers` field in the post-merge execution block. Reflect the changes in the `ommersHash` field of the execution block header.
Commit to the (hash tree) root of each beacon chain block in the corresponding execution payload header.
Copy link
Contributor

Choose a reason for hiding this comment

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

Why the parenthesis? Might just actually also say "SSZ hash tree root"


Store each beacon chain state root into a contract and add a new opcode that reads this contract.
Store each of these roots in a contract that lives in the execution state and add a new opcode that reads this contract.

## Motivation

Exposing the beacon chain state root allows for proofs about the beacon state to be verified inside the EVM. This functionality supports a wide variety of use cases in smart contracts involving validator status and finality produced by the consensus layer.

In particular, this functionality is required for beacon chain validator withdrawals to the EVM.
Roots of the beacon chain blocks are crytographic accumulators that allow proofs of arbitrary consensus state. Exposing these roots inside the EVM allows for trust-minimized access to the consensus layer. This functionality supports a wide variety of use cases that improve trust assumptions of staking pools, restaking constructions, smart contract bridges, MEV mitigations and more.

## Specification

| constants | value | units
|--- |--- |---
| `FORK_TIMESTAMP` | TBD |
| `FORK_EPOCH` | TBD |
| `HISTORY_STORAGE_ADDRESS` | `0xfffffffffffffffffffffffffffffffffffffffd` |
| `OPCODE_VALUE` | `0x48` |
| `G_beacon_state_root` | 20 | gas
| constants | value | units
|--- |--- |---
| `FORK_TIMESTAMP` | TBD |
| `HISTORY_STORAGE_ADDRESS` | `0xfffffffffffffffffffffffffffffffffffffffd` |
| `OPCODE_VALUE` | `0x48` |
| `G_beacon_root` | 20 | gas
| `SLOTS_PER_HISTORICAL_ROOT` | 8192 | slot(s)

### Background

The method of injecting the beacon state root in this EIP follows the general strategy of [EIP-4399](./eip-4399.md) to make a post-merge change to the EVM integrating information from the beacon chain. This EIP along with [EIP-3675](./eip-3675.md) should be taken as relevant background to understand the particular approach of this EIP.

The method for exposing the state root data via opcode is inspired by [EIP-2935](./eip-2935.md).
The high-level idea is that each execution block contains the previous beacon block root. In the event of missed slots, the previous block root does not change
and so we only need a constant amount of space to represent this "oracle" in each execution block. To improve the usability of this oracle, block roots are stored
ralexstokes marked this conversation as resolved.
Show resolved Hide resolved
in a canonical place in the execution state analogous to a `SSTORE` in given contract's storage for each update. Roots are stored keyed by the slot(s) they pertain to.
To bound the amount of storage this construction consumes, a ring buffer is used that mirrors a block root accumulator on the consensus layer.
The method for exposing the root data via opcode is inspired by [EIP-2935](./eip-2935.md).

### Block structure and validity

Beginning at the execution timestamp `FORK_TIMESTAMP`, execution clients **MUST**:

1. set the value of the `ommers` field in the block to an RLP list with one element: the 32 byte [hash tree root](https://github.com/ethereum/consensus-specs/blob/dev/ssz/simple-serialize.md#merkleization) of the [beacon state](https://github.com/ethereum/consensus-specs/blob/dev/specs/bellatrix/beacon-chain.md#beaconstate) from the previous slot to this block.

2. set the value of the `ommersHash` field in the block header to the Keccak256 hash of the `ommers` field.
1. set the 32 bytes of the execution block header after the `withdrawals_root` to the 32 byte [hash tree root](https://github.com/ethereum/consensus-specs/blob/dev/ssz/simple-serialize.md#merkleization) of the beacon block from the previous slot(s) to this block.
ralexstokes marked this conversation as resolved.
Show resolved Hide resolved

```python
beaconStateRoot = <32 byte value> # provided by consensus client
ommers = RLP([beaconStateRoot]) # in the block body
ommersHash = Keccak256(ommers) # in the block header
```

3. Add the block validation that the `ommersHash` does indeed match the expected commitment given the `ommers` value.
*NOTE*: this field is appended to the current block header structure with this EIP so that the size of the header grows after (and including) the `FORK_TIMESTAMP`.

### EVM changes

#### Block processing

At the start of processing any execution block where `block.timestamp >= FORK_TIMESTAMP` (i.e. before processing any transactions), write the beacon state root provided in the block into the storage of the smart contract at `HISTORY_STORAGE_ADDRESS`. This data is keyed by the block number.
At the start of processing any execution block where `block.timestamp >= FORK_TIMESTAMP` (i.e. before processing any transactions), write the beacon root provided in the block header into the storage of the contract at `HISTORY_STORAGE_ADDRESS`. This data is keyed by the slot number.
ralexstokes marked this conversation as resolved.
Show resolved Hide resolved

In pseudocode:

```python
beacon_state_root = block.ommers[0]
sstore(HISTORY_STORAGE_ADDRESS, block.number, beacon_state_root)
start_timestamp = get_block(block_header.parent_hash).header.timestamp
start_slot = convert_to_slot(start_timestamp)
Copy link
Contributor

@mkalinin mkalinin Apr 12, 2023

Choose a reason for hiding this comment

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

We can re-use nonce field to put a slot number into it allowing to avoid EL to be aware of timestamp to slot conversion. It also opens an opportunity to add SLOT EVM instruction in the future.

Copy link
Member Author

Choose a reason for hiding this comment

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

yeah I wasn't sure if this was a new capability, e.g. post-4844 and if so then we can do something like what you suggest to also provide slot data with the root


end_timestamp = block_header.timestamp
end_slot = convert_to_slot(end_timestamp)

beacon_block_root = block_header.beacon_block_root
ralexstokes marked this conversation as resolved.
Show resolved Hide resolved

for slot in range(start_slot, end_slot):
sstore(HISTORY_STORAGE_ADDRESS, slot % SLOTS_PER_HISTORICAL_ROOT, beacon_block_root)
```

When using any slot value as a key to the storage, the value under consideration should be converted to 32 bytes with big-endian encoding.
Copy link
Contributor

Choose a reason for hiding this comment

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

MUST, rather than should


#### New opcode

Beginning at the execution timestamp `FORK_TIMESTAMP`, introduce a new opcode `BEACON_STATE_ROOT` at `OPCODE_VALUE`. This opcode consumes one word from the stack encoding the block number for the root. The opcode has a gas cost of `G_beacon_state_root`.
Beginning at the execution timestamp `FORK_TIMESTAMP`, introduce a new opcode `BEACON_ROOT` at `OPCODE_VALUE`.
This opcode consumes one word from the stack encoding the slot number for the desired root under big-endian discipline.
The opcode has a gas cost of `G_beacon_state_root`.

The result of executing this opcode leaves one word on the stack corresponding to a read of the history contract's storage; in pseudocode:

```python
block_number = evm.stack.pop()
sload(HISTORY_STORAGE_ADDRESS, block_number)
slot = evm.stack.pop()
sload(HISTORY_STORAGE_ADDRESS, slot)
```

If there is no root stored at the requested block number, the opcode follows the existing EVM semantics of `sload` returning `0`.

## Rationale

### General strategy

See the rationale for EIP-4399 for discussion about this general strategy of reusing execution block elements for beacon chain data.

### Fork mechanics

This EIP requires the consensus layer and execution layer to execute a network upgrade in lockstep.
To carry out this task, a `FORK_EPOCH` (of the beacon chain) will be chosen and then used to compute a timestamp `FORK_TIMESTAMP`.
This `FORK_TIMESTAMP` can be used in the execution layer to identify when the protocol change should be deployed.

This technique works because the timestamps in post-merge execution blocks are aligned to beacon chain slots and thus serve as a proxy for the slot number.

Another option for the fork definition would be to pick a beacon chain epoch and an execution payload block number.
This design however is not reliable due to the presence of skipped slots on the beacon chain.

### Execution layer validations

By including the beacon state root in the execution block in the deprecated `ommers` field, execution clients can still verify the chain in a self-contained way without relying on an available consensus client.
This property is important during syncing (and likely other phases of execution node operation).

### Minimizing client code change

By including the `ommersHash` validation, clients can use existing code with only minimal changes (supplying the actual state root) during block production and verification.
Having the beacon state root value in the `ommers` field means that it is fairly straightforward to provide the value from the block data to the EVM execution context for client implementations as they stand today.

### Gas cost of opcode

The suggested gas cost is just using the value for the `BLOCKHASH` opcode as `BEACON_STATE_ROOT` is an analogous operation.
The suggested gas cost is just using the value for the `BLOCKHASH` opcode as `BEACON_ROOT` is an analogous operation.

### Why not repurpose `BLOCKHASH`?

The `BLOCKHASH` opcode could be repurposed to provide a beacon state root instead of the current execution block hash.
The `BLOCKHASH` opcode could be repurposed to provide the beacon root instead of some execution block hash.
To minimize code change and simplify deployment to mainnet, this EIP suggests leaving `BLOCKHASH` alone and adding a new opcode with the desired semantics.

### Why not bound history of state roots?

Marginal state growth; adding every single root results in an additional ~84MB of state growth per year compared to ~30 GB of state overall.

TODO: say something about statelessness
TODO: get latest numbers on state size, and compare against predicted growth

### Beacon state root instead of block root

Each slot on the beacon chain containing a block has both a block root and a state root (reflecting the state after applying said block).
The beacon block includes the state root so a proof about the state could also be authored against a block root at the cost of a few additional hashes.
Given that most use cases want to prove data encapsulated in a given state, rather than a given block, this EIP suggests exposing state roots over block roots.
### Beacon block root instead of state root

### Block number in lieu of slot
Block roots are preferred over state roots so there is a constant amount of work to do with each new execution block. Otherwise, skipped slots would require
a linear amount of work with each new payload. While skipped slots are quite rare on mainnet, it is best to not add additional load under what would already
be nonfavorable conditions.

The state roots are keyed by the `number` of the execution block.
Another option is to key roots by the beacon chain slot they belong to.
While at first pass this may seem more direct, the beacon chain can have "skipped" slots where a beacon proposer failed to produce a block that was included at a given slot.
Handling roots of skipped slots would complicate the EVM mechanism so this EIP suggests to use the execution block number where each distinct block number is guaranteed to have a distinct root.
Use of block root over state root does mean proofs will require a few additional nodes but this cost is negligible (and could be amortized across all consumers,
e.g. with a singleton state root contract that caches the proof per slot).

## Backwards Compatibility

Expand Down