diff --git a/EIPS/eip-4788.md b/EIPS/eip-4788.md index 492e12e23fca3d..b2d2cb378b6d93 100644 --- a/EIPS/eip-4788.md +++ b/EIPS/eip-4788.md @@ -18,7 +18,10 @@ Store each of these roots in a contract that lives in the execution state and ad ## Motivation -Roots of the beacon chain blocks are cryptographic 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. +Roots of the beacon chain blocks are cryptographic 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 @@ -34,7 +37,8 @@ Roots of the beacon chain blocks are cryptographic accumulators that allow proof 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. +in a canonical place in the execution state analogous to a `SSTORE` in the given contract's storage for each update. +Roots are 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). @@ -42,29 +46,46 @@ The method for exposing the root data via opcode is inspired by [EIP-2935](./eip Beginning at the execution timestamp `FORK_TIMESTAMP`, execution clients **MUST**: -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. +1. set 32 bytes of the execution block header after the `excess_data_gas` 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. -*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`. +2. set the 8 bytes after the previous 32 byte extension to the slot number as a big-endian uint64 of the current slot. + +*NOTE*: these fields are 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 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. +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`. -In pseudocode: +This data is keyed by the slots containing the parent beacon block root which in general could be multiple slots for the +same root. To find the starting slot of the range, read the storage slot `SLOTS_PER_HISTORICAL_ROOT` (interpreted as a +256-bit byte array) which contains the big-endian encoding of the last slot value written to. This encoding should be interpreted +as a 64-bit big-endian integer that yields the starting slot. +Clients then iterate from the start slot (inclusive) to the end slot (exclusive) found in the header +and write the beacon block root into each slot value. -```python -start_timestamp = get_block(block_header.parent_hash).header.timestamp -start_slot = convert_to_slot(start_timestamp) +*NOTE*: if the slot stored at `SLOTS_PER_HISTORICAL_ROOT` is all zeros, clients should use set the start slot to one less than the +slot in the header, e.g. `header.beacon_block_root_slot - 1`. + +After writing the block root in the relevant slots, store the current slot for use during the next update in the same storage slot +`SLOTS_PER_HISTORICAL_ROOT`. -end_timestamp = block_header.timestamp -end_slot = convert_to_slot(end_timestamp) +In Python pseudocode: + +```python +start_slot = to_uint64(sload(HISTORY_STORAGE_ADDRESS, SLOTS_PER_HISTORICAL_ROOT)) +end_slot = block_header.beacon_slot +if start_slot == 0: + start_slot = max(end_slot - 1, 0) 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) + +sstore(HISTORY_STORAGE_ADDRESS, SLOTS_PER_HISTORICAL_ROOT, end_slot) ``` When using any slot value as a key to the storage, the value under consideration must be converted to 32 bytes with big-endian encoding.