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: key beacon roots by root #7107

Merged
merged 1 commit into from
May 31, 2023
Merged
Changes from all commits
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
54 changes: 17 additions & 37 deletions EIPS/eip-4788.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,11 @@ restaking constructions, smart contract bridges, MEV mitigations and more.

## Specification

| constants | value | units
|--- |--- |---
| `FORK_TIMESTAMP` | TBD |
| `HISTORY_STORAGE_ADDRESS` | `0xfffffffffffffffffffffffffffffffffffffffd` |
| `G_beacon_root` | 2100 | gas
| `SLOTS_PER_HISTORICAL_ROOT` | 8192 | slot(s)
| constants | value | units
|--- |--- |---
| `FORK_TIMESTAMP` | TBD |
| `HISTORY_STORAGE_ADDRESS` | `0xfffffffffffffffffffffffffffffffffffffffd` |
| `G_beacon_root` | 2100 | gas

### Background

Expand All @@ -43,7 +42,7 @@ To bound the amount of storage this construction consumes, a ring buffer is used

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

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.
1. set 32 bytes of the execution block header after the last header field as of `FORK_TIMESTAMP` 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`.

Expand All @@ -54,56 +53,37 @@ Beginning at the execution timestamp `FORK_TIMESTAMP`, execution clients **MUST*
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 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.

*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`.
The root itself is used as a key into the contract's storage and the timestamp of the header is written as the key's value.
The timestamp (a 64-bit unsigned integer value) is encoded as 32 bytes in big-endian format.

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
timestamp = to_uint256_be(block_header.timestamp)

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)
sstore(HISTORY_STORAGE_ADDRESS, parent_beacon_block_root, timestamp)
```

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.

#### New stateful precompile

Beginning at the execution timestamp `FORK_TIMESTAMP`, the code and storage at `HISTORY_STORAGE_ADDRESS` constitute a "stateful" precompile.

Callers of the precompile should provide the `slot` they are querying encoding a 64-bit unsigned integer as 256-bit big-endian data.
Recall this `slot` number should be reduced modulo the `SLOTS_PER_HISTORICAL_ROOT` constant to derive the correct key into the ring buffer structure.
Callers of the precompile should provide the `root` they are querying encoded as 32 bytes.

Alongside the existing gas for calling the precompile, there is an additional gas cost of `G_beacon_root` cost to reflect the implicit `SLOAD` from
the precompile's state. The root is returned as 32 bytes in the caller's provided return buffer.
the precompile's state. The timestamp of the corresponding root is returned as 32 bytes in the caller's provided return buffer and represents the
64-bit unsigned integer from the header in big-endian format.

In pseudocode:

```python
slot = to_uint64(evm.calldata[:32])
root = sload(HISTORY_STORAGE_ADDRESS, slot % SLOTS_PER_HISTORICAL_ROOT)
evm.returndata[:32].set(root)
root = evm.calldata[:32]
timestamp = sload(HISTORY_STORAGE_ADDRESS, root)
evm.returndata[:32].set(timestamp)
```

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

## Rationale

Expand Down