diff --git a/specs/SUMMARY.md b/specs/SUMMARY.md index 323c972b7..95f0741fd 100644 --- a/specs/SUMMARY.md +++ b/specs/SUMMARY.md @@ -54,6 +54,9 @@ - [Derivation](./protocol/holocene/derivation.md) - [Execution Engine](./protocol/holocene/exec-engine.md) - [System Config](./protocol/holocene/system-config.md) + - [Isthmus](./protocol/isthmus/overview.md) + - [Execution Engine](./protocol/isthmus/exec-engine.md) + - [Superchain Config](./protocol/isthmus/superchain-config.md) - [Governance]() - [Governance Token](./governance/gov-token.md) - [Experimental]() diff --git a/specs/protocol/holocene/derivation.md b/specs/protocol/holocene/derivation.md index d9b31892c..73d047906 100644 --- a/specs/protocol/holocene/derivation.md +++ b/specs/protocol/holocene/derivation.md @@ -135,7 +135,7 @@ invalid. due to the new strict batch ordering rules. - If `span_end.timestamp < next_timestamp`, the span batch is set to have `past` validity, as it doesn't contain any new batches (this would also happen if applying timestamp checks to each derived -singular batch individually). See below in the [Batch Queue][#batch-queue] section about the new +singular batch individually). See below in the [Batch Queue](#batch-queue) section about the new `past` validity. - Note that we still allow span batches to overlap with the safe chain (`span_start.timestamp < next_timestamp`). diff --git a/specs/protocol/isthmus/exec-engine.md b/specs/protocol/isthmus/exec-engine.md index dc4df4752..e1eaf905a 100644 --- a/specs/protocol/isthmus/exec-engine.md +++ b/specs/protocol/isthmus/exec-engine.md @@ -10,11 +10,25 @@ - [Header Validity Rules](#header-validity-rules) - [Header Withdrawals Root](#header-withdrawals-root) - [Rationale](#rationale) + - [Genesis Block](#genesis-block) + - [State Processing](#state-processing) + - [P2P](#p2p) + - [Backwards Compatibility Considerations](#backwards-compatibility-considerations) - [Forwards Compatibility Considerations](#forwards-compatibility-considerations) - [Client Implementation Considerations](#client-implementation-considerations) + - [Transaction Simulation](#transaction-simulation) +- [Block Body Withdrawals List](#block-body-withdrawals-list) +- [Engine API Updates](#engine-api-updates) + - [Update to `ExecutableData`](#update-to-executabledata) + - [`engine_newPayloadV3` API](#engine_newpayloadv3-api) + + +[l2-to-l1-mp]: ../../protocol/predeploys.md#L2ToL1MessagePasser +[output-root]: ../../glossary.md#l2-output-root + ## Overview The storage root of the `L2ToL1MessagePasser` is included in the block header's @@ -27,19 +41,19 @@ Changes to the L2 Block execution rules are applied when the `L2 Timestamp >= ac ## `L2ToL1MessagePasser` Storage Root in Header -After Holocene's activation, the L2 block header's `withdrawalsRoot` field will consist of the 32-byte -[`L2ToL1MessagePasser`][l2-to-l1-mp] account storage root _after_ the block has been executed, and _after_ the -insertions and deletions have been applied to the trie. In other words, the storage root should be the same root -that is returned by `eth_getProof` at the given block number. +After Isthmus hardfork's activation, the L2 block header's `withdrawalsRoot` field will consist of the 32-byte +[`L2ToL1MessagePasser`][l2-to-l1-mp] 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 holocene activation, the L2 block header's `withdrawalsRoot` field must be: +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. -After Holocene activation, an L2 block header's `withdrawalsRoot` field is valid iff: +After Isthmus activation, an L2 block header's `withdrawalsRoot` field is valid iff: 1. It is exactly 32 bytes in length. 1. The [`L2ToL1MessagePasser`][l2-to-l1-mp] account storage root, as committed to in the `storageRoot` within the block @@ -63,6 +77,36 @@ places a burden on users of the system in a post-fault-proofs world, where: Placing the [`L2ToL1MessagePasser`][l2-to-l1-mp] 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`][l2-to-l1-mp] 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 @@ -76,3 +120,24 @@ an outbound withdrawal for a long period of time, the node may not have access t [`L2ToL1MessagePasser`][l2-to-l1-mp]. 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. + +## Block Body Withdrawals List + +Withdrawals list in the block body is encoded as an empty RLP list. + +## Engine API Updates + +### Update to `ExecutableData` + +`ExecutableData` will contain an extra field for `withdrawalsRoot` after Isthmus hard fork. + +### `engine_newPayloadV3` API + +Post Isthmus, `engine_newPayloadV3` will be used with the additional `ExecutionPayload` attribute. This attribute +is omitted prior to Isthmus. diff --git a/specs/protocol/proposals.md b/specs/protocol/proposals.md index 0d05d3ab8..ab965828c 100644 --- a/specs/protocol/proposals.md +++ b/specs/protocol/proposals.md @@ -19,6 +19,7 @@ [g-rollup-node]: ../glossary.md#rollup-node [g-mpt]: ../glossary.md#merkle-patricia-trie +[header-withdrawals-root]: ../protocol/isthmus/exec-engine.md#l2tol1messagepasser-storage-root-in-header ## Overview @@ -112,6 +113,13 @@ where: then the withdrawal against that storage root), we can prove against the L2toL1MessagePasser's storage root directly, thus reducing the verification cost of withdrawals on L1. + After Isthmus hard fork, the `withdrawal_storage_root` is present in the + [block header as `withdrawalsRoot`][header-withdrawals-root] and can be used directly, instead of computing + the storage root of the L2toL1MessagePasser contract. + + Similarly, if Isthmus hard fork is active at the genesis block, the `withdrawal_storage_root` is present + in the [block header as `withdrawalsRoot`][header-withdrawals-root]. + ## L2 Output Oracle Smart Contract L2 blocks are produced at a constant rate of `L2_BLOCK_TIME` (2 seconds). diff --git a/specs/protocol/rollup-node-p2p.md b/specs/protocol/rollup-node-p2p.md index 0e3ca19c5..fbb5306f4 100644 --- a/specs/protocol/rollup-node-p2p.md +++ b/specs/protocol/rollup-node-p2p.md @@ -30,6 +30,7 @@ - [`blocksv1`](#blocksv1) - [`blocksv2`](#blocksv2) - [`blocksv3`](#blocksv3) + - [`blocksv4`](#blocksv4) - [Block encoding](#block-encoding) - [Block signatures](#block-signatures) - [Block validation](#block-validation) @@ -252,7 +253,7 @@ The extended validator emits one of the following validation signals: ## Gossip Topics -There are three topics for distributing blocks to other nodes faster than proxying through L1 would. These are: +Listed below are the topics for distributing blocks to other nodes faster than proxying through L1 would. These are: ### `blocksv1` @@ -266,6 +267,10 @@ Canyon/Delta blocks are broadcast on `/optimism//1/blocks`. Ecotone blocks are broadcast on `/optimism//2/blocks`. +### `blocksv4` + +Isthmus blocks are broadcast on `/optimism//3/blocks`. + ### Block encoding A block is structured as the concatenation of: @@ -277,6 +282,11 @@ A block is structured as the concatenation of: - `signature`: A `secp256k1` signature, always 65 bytes, `r (uint256), s (uint256), y_parity (uint8)` - `parentBeaconBlockRoot`: L1 origin parent beacon block root, always 32 bytes - `payload`: A SSZ-encoded `ExecutionPayload`, always the remaining bytes. +- V4 topic + - `signature`: A `secp256k1` signature, always 65 bytes, `r (uint256), s (uint256), y_parity (uint8)` + - `parentBeaconBlockRoot`: L1 origin parent beacon block root, always 32 bytes + - `withdrawalsRoot`: L2 withdrawals root, always 32 bytes. + - `payload`: A SSZ-encoded `ExecutionPayload`, always the remaining bytes. All topics use Snappy block-compression (i.e. no snappy frames): the above needs to be compressed after encoding, and decompressed before decoding. @@ -288,8 +298,10 @@ The `signature` is a `secp256k1` signature, and signs over a message: - `domain` is 32 bytes, reserved for message types and versioning info. All zero for this signature. - `chain_id` is a big-endian encoded `uint256`. -- `payload_hash` is `keccak256(payload)`, where `payload` is the `payload` in V1 and V2, - and `parentBeaconBlockRoot ++ payload` in V3. +- `payload_hash` is `keccak256(payload)`, where `payload` is: + - the `payload` in V1 and V2, + - `parentBeaconBlockRoot ++ payload` in V3. + - `parentBeaconBlockRoot ++ withdrawalsRoot ++ payload` in V4. The `secp256k1` signature must have `y_parity = 1 or 0`, the `chain_id` is already signed over. @@ -312,6 +324,7 @@ An [extended-validator] checks the incoming messages as follows, in order of ope - `[REJECT]` if the block is on a topic >= V3 and has an excess blob gas value that is not zero - `[REJECT]` if the block is on a topic <= V2 and the parent beacon block root is not nil - `[REJECT]` if the block is on a topic >= V3 and the parent beacon block root is nil +- `[REJECT]` if the block is on a topic >= V3 and the l2 withdrawals root is nil - `[REJECT]` if more than 5 different blocks have been seen with the same block height - `[IGNORE]` if the block has already been seen - `[REJECT]` if the signature by the sequencer is not valid @@ -403,6 +416,8 @@ Implementations may opt for a different limit, since this sync method is optiona matching the `ExecutionPayload` SSZ definition of the L1 Merge, L2 Bedrock and L2 Regolith, L2 Canyon versions. - `1`: SSZ-encoded `ExecutionPayloadEnvelope` with Snappy framing compression, matching the `ExecutionPayloadEnvelope` SSZ definition of the L2 Ecotone version. +- `2`: SSZ-encoded `ExecutionPayload` with Snappy framing compression, + matching the `ExecutionPayload` SSZ definition of the L2 Isthmus version. The request is by block-number, enabling parallel fetching of a chain across many peers.