diff --git a/EIPS/eip-4788.md b/EIPS/eip-4788.md index 24f65168b0a181..352cbb83a8a43a 100644 --- a/EIPS/eip-4788.md +++ b/EIPS/eip-4788.md @@ -1,10 +1,10 @@ --- 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 @@ -12,129 +12,98 @@ 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. -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 parent beacon block root. Even in the event of missed slots since the previous block root does not change, +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 +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 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/fa09d896484bbe240334fa21ffaa454bafe5842e/ssz/simple-serialize.md#merkleization) of the parent beacon block. -```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 parent 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. 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) + +end_timestamp = block_header.timestamp +end_slot = convert_to_slot(end_timestamp) + +parent_beacon_block_root = block_header.parent_beacon_block_root + +for slot in range(start_slot, end_slot): + sstore(HISTORY_STORAGE_ADDRESS, slot % SLOTS_PER_HISTORICAL_ROOT, parent_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. + #### 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 % SLOTS_PER_HISTORICAL_ROOT) ``` 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