Skip to content

Commit

Permalink
Isthmus: L2 Withdrawals root spec updates (#396)
Browse files Browse the repository at this point in the history
  • Loading branch information
vdamle authored Oct 25, 2024
1 parent 2abe713 commit 0ca4559
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 10 deletions.
3 changes: 3 additions & 0 deletions specs/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]()
Expand Down
2 changes: 1 addition & 1 deletion specs/protocol/holocene/derivation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`).
Expand Down
77 changes: 71 additions & 6 deletions specs/protocol/isthmus/exec-engine.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

<!-- All glossary references in this file. -->

[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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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.
8 changes: 8 additions & 0 deletions specs/protocol/proposals.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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).
Expand Down
21 changes: 18 additions & 3 deletions specs/protocol/rollup-node-p2p.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
- [`blocksv1`](#blocksv1)
- [`blocksv2`](#blocksv2)
- [`blocksv3`](#blocksv3)
- [`blocksv4`](#blocksv4)
- [Block encoding](#block-encoding)
- [Block signatures](#block-signatures)
- [Block validation](#block-validation)
Expand Down Expand Up @@ -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`

Expand All @@ -266,6 +267,10 @@ Canyon/Delta blocks are broadcast on `/optimism/<chainId>/1/blocks`.

Ecotone blocks are broadcast on `/optimism/<chainId>/2/blocks`.

### `blocksv4`

Isthmus blocks are broadcast on `/optimism/<chainId>/3/blocks`.

### Block encoding

A block is structured as the concatenation of:
Expand All @@ -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.
Expand All @@ -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.

Expand All @@ -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
Expand Down Expand Up @@ -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.

Expand Down

0 comments on commit 0ca4559

Please sign in to comment.