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.
Store each beacon chain state root into a contract and add a new opcode that reads this contract.
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.
constants | value | units |
---|---|---|
FORK_TIMESTAMP |
TBD | |
FORK_EPOCH |
TBD | |
OPCODE_VALUE |
0x48 |
|
G_beacon_state_root |
20 | gas |
WINDOW_LENGTH |
256 | slots |
The method of injecting the beacon state root in this EIP follows the general strategy of EIP-4399 to make a post-merge change to the EVM integrating information from the beacon chain. This EIP along with EIP-3675 should be taken as relevant background to understand the particular approach of this EIP.
The method exposing the state root data is inspired by EIP-2935.
beginning with FORK_BLOCK_NUMBER
, execution clients MUST:
ommers
field in the block to an RLP list with one element: the 32 byte hash tree root of the beacon state from the previous slot to this block.ommersHash
field in the block header to the Keccak256 hash of the ommers
field.beaconStateRoot = <32 byte value> # provided by consensus client
ommers = RLP([beaconStateRoot]) # in the block body
ommersHash = Keccak256(ommers) # in the block header
ommersHash
does indeed match the expected committment given the ommers
value.beginning with FORK_BLOCK_NUMBER
, introduce a new opcode BEACON_STATE_ROOT
at OPCODE_VALUE
.
This opcode must return the value of the single element in the ommers
field of the execution block. This value is set in the EVM execution context before processing a given execution block. The opcode consumes no words from the stack and places one word on the stack. The opcode has a gas cost of G_beacon_state_root
.
For example, assume the beacon state root at slot 3,000,000 is 0x8ff077430957727dbace82d868a4f514d3720594727deaaafded1ee2e9e486f1
. If processing a transaction that executes BEACON_STATE_ROOT
at slot 3,000,001, the EVM should place on the stack this value of the state root 0x8ff0...86f1
.
The Engine API PayloadAttributes
message will be updated with an additional field to send the relevant beacon state root from the consensus client to the execution client when preparing a block.
This field MUST be sent beginning with FORK_EPOCH
at the consensus layer.
See the rationale for EIP-4399 for discussion about this general strategy of reusing execution block elements for beacon chain data.
The ExecutionPayload
and ExecutionPayloadHeader
will be updated with a 32 byte field beacon_state_root
for the relevant beacon state root for the corresponding execution block. This way the beacon state root committed to in the block can be verified against the beacon state root recorded in the beacon chain (e.g. state.state_roots[state.slot-1]
). A future PR to the consensus-specs
repo with this change is forthcoming.
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).
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.
the suggested gas cost is just using the value for the BLOCKHASH
opcode as BEACON_STATE_ROOT
is an analogous operation.
Thanks to Danny Ryan for helpful feedback and discussions.