L2 Execution Engine
Table of Contents
- Overview
- Timestamp Activation
L2ToL1MessagePasser
Storage Root in Header- Deposit Requests
- Block Body Withdrawals List
- EVM Changes
- Block Sealing
- Engine API Updates
- Fees
Overview
The storage root of the L2ToL1MessagePasser
is included in the block header's
withdrawalRoot
field.
Timestamp Activation
Isthmus, like other network upgrades, is activated at a timestamp.
Changes to the L2 Block execution rules are applied when the L2 Timestamp >= activation time
.
L2ToL1MessagePasser
Storage Root in Header
After Isthmus hardfork's activation, the L2 block header's withdrawalsRoot
field will consist of the 32-byte
L2ToL1MessagePasser
account storage root from the world state identified by the stateRoot
field in the block header. The storage root should be the same root that is returned by eth_getProof
at the given block number.
Header Validity Rules
Prior to isthmus activation:
- the L2 block header's
withdrawalsRoot
field must be:nil
if Canyon has not been activated.keccak256(rlp(empty_string_code))
if Canyon has been activated.
- the L2 block header's
requestsHash
field must be omitted.
After Isthmus activation, an L2 block header is valid iff:
- The
withdrawalsRoot
field- Is 32 bytes in length.
- Matches the
L2ToL1MessagePasser
account storage root, as committed to in thestorageRoot
within the block header
- The
requestsHash
field is equal tosha256('') = 0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
indicating no requests in the block.
Header Withdrawals Root
Byte offset | Description |
---|---|
[0, 32) | L2ToL1MessagePasser account storage root |
Rationale
Currently, to generate L2 output roots for historical blocks, an archival node is required. This directly places a burden on users of the system in a post-fault-proofs world, where:
- A proposer must have an archive node to propose an output root at the safe head.
- A user that is proving their withdrawal must have an archive node to verify that the output root they are proving their withdrawal against is indeed valid and included within the safe chain.
Placing the L2ToL1MessagePasser
account storage root in the withdrawalsRoot
field alleviates this burden
for users and protocol participants alike, allowing them to propose and verify other proposals with lower operating costs.
Genesis Block
If Isthmus is active at genesis block, the withdrawalsRoot
in the genesis block header is set to the
L2ToL1MessagePasser
account storage root.
State Processing
At the time of state processing, the header for which transactions are being validated should not make it's withdrawalsRoot
available to the EVM/application layer.
P2P
During sync, we expect the withdrawals list in the block body to be empty (OP stack does not make
use of the withdrawals list) and hence the hash of the withdrawals list to be the MPT root of an empty list.
When verifying the header chain using the final header that is synced, the header timesetamp is used to
determine whether Isthmus is active at the said block. If it is, we expect that the header withdrawalsRoot
MPT hash can be any non-null value (since it is expected to contain the L2ToL1MessagePasser
's storage root).
Backwards Compatibility Considerations
Beginning at Canyon (which includes Shanghai hardfork support) and prior to Isthmus activation,
the withdrawalsRoot
field is set to the MPT root of an empty withdrawals list. This is the
same root as an empty storage root. The withdrawals are captured in the L2 state, however
they are not reflected in the withdrawalsRoot
. Hence, prior to Isthmus activation,
even if a withdrawalsRoot
is present and a MPT root is present in the header, it should not be used.
Any implementation that calculates output root should be careful not to use the header withdrawalsRoot
.
After Isthmus activation, if there was never any withdrawal contract storage, a MPT root of an empty list
can be set as the withdrawalsRoot
Forwards Compatibility Considerations
As it stands, the withdrawalsRoot
field is unused within the OP Stack's header consensus format, and will never be
used for other reasons that are currently planned. Setting this value to the account storage root of the withdrawal
directly fits with the OP Stack, and makes use of the existing field in the L1 header consensus format.
Client Implementation Considerations
Various EL clients store historical state of accounts differently. If, as a contrived case, an OP Stack chain did not have
an outbound withdrawal for a long period of time, the node may not have access to the account storage root of the
L2ToL1MessagePasser
. In this case, the client would be unable to keep consensus. However, most modern
clients are able to at the very least reconstruct the account storage root at a given block on the fly if it does not
directly store this information.
Transaction Simulation
In response to RPC methods like eth_simulateV1
that allow simulation of arbitrary transactions within one or more blocks,
an empty withdrawals root should be included in the header of a block that consists of such simulated transactions. The same
is applicable for scenarios where the actual withdrawals root value is not readily available.
Deposit Requests
EIP-6110 shifts deposit to the execution layer, introducing a new EIP-7685 deposit request of type
DEPOSIT_REQUEST_TYPE
. Deposit requests then appear in the EIP-7685 requests list. The OP Stack needs to ignore these
requests. Requests generation must be modified to exclude EIP-6110 deposit requests. Note that since the EIP-6110
request type did not exist prior to Pectra on L1 and the Isthmus hardfork on L2, no activation time is needed since these
deposit type requests may always be excluded.
Block Body Withdrawals List
Withdrawals list in the block body is encoded as an empty RLP list.
EVM Changes
BLS Precompiles
Similar to the bn256Pairing
precompile in the granite hardfork,
EIP-2537 introduces a BLS
precompile that short-circuits depending on input size in the EVM.
The input size limits of the BLS precompile contracts are listed below:
- G1 multiple-scalar-multiply:
input_size <= 513760 bytes
- G2 multiple-scalar-multiply:
input_size <= 488448 bytes
- Pairing check:
input_size <= 235008 bytes
The rest of the BLS precompiles are fixed-size operations which have a fixed gas cost.
All of the BLS precompiles should be accelerated in fault proof programs so they call out to the L1 instead of calculating the result inside the program.
Block Sealing
To match EVM execution as closely as possible, the post-execution transactions to process logs and the request queue
included as part of EIP-6110, EIP-7002,
and EIP-7251 must be executed. However, the resulting
requests
is ignored because Isthmus block requests
must be an empty array.
Engine API Updates
Update to ExecutionPayload
ExecutionPayload
will contain an extra field for withdrawalsRoot
after Isthmus hard fork.
engine_newPayloadV4
API
Post Isthmus, engine_newPayloadV4
will be used.
The executionRequests
parameter MUST be an empty array.
Fees
New OP stack variants have different resource consumption patterns, and thus require a more flexible
pricing model. To enable more customizable fee structures, Isthmus adds a new component to the fee
calculation: the operatorFee
, which is parameterized by two scalars: the operatorFeeScalar
and the operatorFeeConstant
.
Operator Fee
The operator fee, is set as follows:
operatorFee = (gasUsed * operatorFeeScalar / 1e6) + operatorFeeConstant
Where:
gasUsed
is amount of gas used by the transaction.operatorFeeScalar
is auint32
scalar set by the chain operator, scaled by1e6
.operatorFeeConstant
is auint64
scalar set by the chain operator.
Configuring Parameters
operatorFeeScalar
and operatorFeeConstant
are loaded in a similar way to the baseFeeScalar
and
blobBaseFeeScalar
used in the L1Fee
.
calculation. In more detail, these parameters can be accessed in two interchangable ways.
- read from the deposited L1 attributes (
operatorFeeScalar
andoperatorFeeConstant
) of the current L2 block - read from the L1 Block Info contract (
0x4200000000000000000000000000000000000015
)- using the respective solidity getter functions (
operatorFeeScalar
,operatorFeeConstant
) - using direct storage-reads:
- Operator fee scalar as big-endian
uint32
in slot8
at offset0
. - Operator fee constant as big-endian
uint64
in slot8
at offset4
.
- Operator fee scalar as big-endian
- using the respective solidity getter functions (
Fee Vaults
These collected fees are sent to a new vault for the operatorFee
: the OperatorFeeVault
.
Like the existing vaults, this is a hardcoded address, pointing at a pre-deployed proxy contract.
The proxy is backed by a vault contract deployment, based on FeeVault
, to route vault funds to L1 securely.