Skip to content
Merged
Show file tree
Hide file tree
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
129 changes: 119 additions & 10 deletions specs/interop/predeploys.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@

- [Overview](#overview)
- [CrossL2Inbox](#crossl2inbox)
- [Access-list](#access-list)
- [type 1: Lookup identity](#type-1-lookup-identity)
- [type 2: Chain-ID extension](#type-2-chain-id-extension)
- [type 3: Checksum](#type-3-checksum)
- [Functions](#functions)
- [validateMessage](#validatemessage)
- [`ExecutingMessage` Event](#executingmessage-event)
Expand Down Expand Up @@ -86,6 +90,103 @@ To ensure safety of the protocol, the [Message Invariants](./messaging.md#messag

[`Identifier`]: ./messaging.md#message-identifier

### Access-list

Execution of messages is statically pre-declared in transactions,
to ensure the cross-chain validity can be verified outside the single-chain EVM environment constraints.

After pre-verification of the access-list, the `CrossL2Inbox` can allow messages
to execute when there is a matching pre-verified access-list entry.

Each executing message is declared with 3 typed access-list entries:
- 1: Lookup identity
- 2: Chain-ID extension
- 3: Checksum

The type of entry is encoded in the first byte.
Type 0 is reserved, so valid access-list entries are always non-zero.

Note that the access-list entries may be de-duplicated:
the same message may be executed multiple times.

The access-list content might not always be a multiple of 3.

The access-list content is ordered:
- after type 1, a type 2 or 3 entry is expected.
- after type 2, a type 3 entry is expected.

Note that type 1 and 2 are only enforced out-of-protocol:
these provide a hint, for viable block-building,
to lookup data to determine the validity of the checksum without prior transaction execution.

Not every access-list entry may be executed:
access-list content must not be used by applications to interpret results of transactions,
the `ExecutingMessage` event describes in detail what is executed.

To prevent cross-contamination of access-list contents,
the checksum entry commits to the contents of the other entries.
The checksum will be invalid if the wrong entries are interpreted with it.

The `CrossL2Inbox` only checks the checksum is present in the access-list:
the presence of other needed entries is enforced during pre-validation.

#### type 1: Lookup identity

Packed attributes for message lookup.
This type of entry serves as hint of the message identity,
for verification of the `checksum` and is not verified in the protocol state-transition or fork-choice.

```text
0..1: type byte, always 0x01
1..4: reserved, zeroed by default
4..12: big-endian uint64 chain ID
12..16: big-endian uint64, block number
16..24: big-endian uint64, timestamp
24..32: big-endian uint32, log index
```

Chain IDs larger than `uint64` are supported, with an additional chain-ID-extension entry.
The lower 64 bits of the chain-ID are always encoded in the lookup entry.

#### type 2: Chain-ID extension

Large `uint256` Chain IDs are represented with an extension entry,
included right after the lookup identity entry.
Like the lookup identity entry, this entry-type is not verified in the protocol state-transition or fork-choice.

This extension entry does not have to be included for chain-IDs that fit in `uint64`.

```text
0..1: type byte, always 0x02
1..8: zero bytes
8..32: upper 24 bytes of big-endian uint256 chain-ID
```

#### type 3: Checksum

The checksum is a versioned hash, committing to implied attributes.
These implied attributes are compared against the full version
of the executing message by recomputing the checksum from the full version.
The full version is retrieved based on the preceding lookup entry and optional chain-ID extension.

The checksum is iteratively constructed:
this allows services to work with intermediate implied data.
E.g. the supervisor does not persist the `origin` or `msgHash`,
but does store a `logHash`.

```text
# Syntax:
# H(bytes): keccak256 hash function
# ++: bytes concatenation
logHash = H(bytes20(idOrigin) ++ msgHash)
# This matches the trailing part of the lookupID
idPacked = bytes12(0) ++ idBlockNumber ++ idTimestamp ++ idLogIndex
idLogHash = H(logHash ++ idPacked)
bareChecksum = H(idLogHash ++ idChainID)
typeByte = 0x03
checksum = typeByte ++ bareChecksum[1:]
```

### Functions

#### validateMessage
Expand All @@ -95,10 +196,10 @@ Emits the `ExecutingMessage` event to signal the transaction has a cross chain m

The following fields are required for validating a cross chain message:

| Name | Type | Description |
| ---------- | ---------- | -------------------------------------------------------------------------- |
| `_id` | Identifier | A [`Identifier`] pointing to the initiating message. |
| `_msgHash` | `bytes32` | The keccak256 hash of the message payload matching the initiating message. |
| Name | Type | Description |
|------------|--------------|----------------------------------------------------------------------------|
| `_id` | `Identifier` | A [`Identifier`] pointing to the initiating message. |
| `_msgHash` | `bytes32` | The keccak256 hash of the message payload matching the initiating message. |

```solidity
function validateMessage(Identifier calldata _id, bytes32 _msgHash)
Expand Down Expand Up @@ -130,15 +231,23 @@ hash comparison.
A simple implementation of the `validateMessage` function is included below.

```solidity
function validateMessage(Identifier calldata _id, bytes32 _msgHash) external {
// We need to know if this is being called on a depositTx
if (IL1BlockInterop(Predeploys.L1_BLOCK_ATTRIBUTES).isDeposit()) revert NoExecutingDeposits();
function validateMessage(Identifier calldata _id, bytes32 _msgHash) external {
bytes32 checksum = calculateChecksum(_id, _msgHash);

emit ExecutingMessage(_msgHash, _id);
}
(bool _isSlotWarm,) = _isWarm(checksum);

if (!_isSlotWarm) revert NonDeclaredExecutingMessage();

emit ExecutingMessage(_msgHash, _id);
}
```

`calculateChecksum` implements the checksum computation (including type-byte) as defined
in the [access-list checksum computation](#type-3-checksum) spec.

`_isWarm` checks that the access-list prepared the `checksum` storage key to be warm.
**No other contract function may warm up this storage without message validation.**

An example of a custom entrypoint utilizing `validateMessage` to consume a known
event. Note that in this example, the contract is consuming its own event
from another chain, however **any** event emitted from **any** contract is consumable!
Expand Down Expand Up @@ -253,7 +362,7 @@ In both cases, the source chain's chain id is required for security. Executing m
assume the identity of an account because `msg.sender` will never be the identity that initiated the message,
it will be the `L2ToL2CrossDomainMessenger` and users will need to callback to get the initiator of the message.

The `_destination` MUST NOT be the chainid of the local chain and a locally defined `nonce` MUST increment on
The `_destination` MUST NOT be the chain-ID of the local chain and a locally defined `nonce` MUST increment on
every call to `sendMessage`.

Note that `sendMessage` is not `payable`.
Expand Down
109 changes: 73 additions & 36 deletions specs/interop/supervisor.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,6 @@
- [`SuperRootResponse`](#superrootresponse)
- [`SafetyLevel`](#safetylevel)
- [Methods](#methods)
- [`supervisor_checkMessage`](#supervisor_checkmessage)
- [`supervisor_checkMessages`](#supervisor_checkmessages)
- [`supervisor_checkMessagesV2`](#supervisor_checkmessagesv2)
- [`supervisor_crossDerivedToSource`](#supervisor_crossderivedtosource)
- [`supervisor_localUnsafe`](#supervisor_localunsafe)
- [`supervisor_crossSafe`](#supervisor_crosssafe)
Expand All @@ -33,6 +30,11 @@
- [`supervisor_superRootAtTimestamp`](#supervisor_superrootattimestamp)
- [`supervisor_syncStatus`](#supervisor_syncstatus)
- [`supervisor_allSafeDerivedAt`](#supervisor_allsafederivedat)
- [`supervisor_checkAccessList`](#supervisor_checkaccesslist)
- [Access-list contents](#access-list-contents)
- [Access-list execution context](#access-list-execution-context)
- [Access-list checks](#access-list-checks)
- [`supervisor_checkAccessList` contents](#supervisor_checkaccesslist-contents)

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

Expand Down Expand Up @@ -73,7 +75,10 @@ Describes the context for message verification.
Specifically, this helps apply message-expiry rules on message checks.

Object:
- `timestamp`: `HexUint64`
- `timestamp`: `HexUint64` - expected timestamp during message execution.
- `timeout`: `HexUint64` - optional, requests verification to still hold at `timestamp+timeout` (inclusive).
The message expiry-window may invalidate messages.
Default interpretation is a `0` timeout: what is valid at `timestamp` may not be valid at `timestamp+1`.

#### `HexUint64`

Expand Down Expand Up @@ -144,44 +149,14 @@ Corresponds to a verifier [SafetyLevel](./verifier.md#safety).

`STRING`, one of:
- `invalid`
- `unsafe`
- `unsafe`: equivalent to safety of the `latest` RPC label.
- `cross-unsafe`
- `local-safe`
- `safe`
- `safe`: matching cross-safe, named `safe` to match the RPC label.
- `finalized`

### Methods

#### `supervisor_checkMessage`

Checks the safety level of a specific message based on its identifier and message hash.
This RPC is useful for the block builder to determine if a message should be included in a block.

Parameters:
- `identifier`: `Identifier`
- `payloadHash`: `Hash`
- `executingDescriptor`: `ExecutingDescriptor`

Returns: `SafetyLevel`

#### `supervisor_checkMessages`

Parameters:
- `messages`: ARRAY of `Message`
- `minSafety`: `SafetyLevel`

#### `supervisor_checkMessagesV2`

Next version `supervisor_checkMessage`,
additionally verifying the message-expiry, by referencing when the execution happens.

Parameters:
- `messages`: ARRAY of `Message`
- `minSafety`: `SafetyLevel`
- `executingDescriptor`: `ExecutingDescriptor` - applies as execution-context to all messages

Returns: RPC error the minSafety is not met by one or more of the messages, with

#### `supervisor_crossDerivedToSource`

Parameters:
Expand Down Expand Up @@ -243,3 +218,65 @@ Parameters:
Returns: derived blocks, mapped in a `OBJECT`:
- key: `ChainID`
- value: `BlockID`

#### `supervisor_checkAccessList`

Verifies if an access-list, as defined in [EIP-2930], references only valid messages.
Message execution in the [`CrossL2Inbox`] that is statically declared in the access-list will not revert.

[EIP-2930]: https://eips.ethereum.org/EIPS/eip-2930

##### Access-list contents

Only the [`CrossL2Inbox`] subset of the access-list in the transaction is required,
storage-access by other addresses is not included.

Note that an access-list can contain multiple different storage key lists for the `CrossL2Inbox` address.
All storage keys applicable to the `CrossL2Inbox` MUST be joined together (preserving ordering),
missing storage-keys breaks inbox safety.

**ALL storage-keys in the access-list for the `CrossL2Inbox` MUST be checked.**
If there is any unrecognized or invalid key, the access-list check MUST fail.

[`CrossL2Inbox`]: ./predeploys.md#crossl2inbox

##### Access-list execution context

The provided execution-context is used to determine validity relative to the provided time constraints,
see [timestamp invariants](./derivation.md#invariants).

Since messages expire, validity is not definitive.
To reserve validity for a longer time range, a non-zero `timeout` value can be used.
See [`ExecutingDescriptor`](#executingdescriptor) documentation.

As block-builder a `timeout` of `0` should be used.

As transaction pre-verifier, a `timeout` of `86400` (1 day) should be used.
The transaction should be re-verified or dropped after this time duration,
as it can no longer be safely included in the block due to message-expiry.

##### Access-list checks

The access-list check errors are not definite state-transition blockers, the RPC based checks can be extra conservative.
I.e. a message that is uncertain to meet the requested safety level may be denied.
Specifically, no attempt may be made to verify messages that are initiated and executed within the same timestamp,
these are `invalid` by default.
Advanced block-builders may still choose to include these messages by verifying the intra-block constraints.

##### `supervisor_checkAccessList` contents

Parameters:
- `inboxEntries`: `ARRAY` of `Hash` - statically declared `CrossL2Inbox` access entries.
- `minSafety`: `SafetyLevel` - minimum required safety, one of:
- `unsafe`: the message exists.
- `cross-unsafe`: the message exists in a cross-unsafe block.
- `local-safe`: the message exists in a local-safe block, not yet cross-verified.
- `safe`: the message exists in a derived block that is cross-verified.
- `finalized`: the message exists in a finalized block.
- Other safety levels are invalid and result in an error.
- `executingDescriptor`: `ExecutingDescriptor` - applies as execution-context to all messages.

Returns: RPC error if the `minSafety` is not met by one or more of the access entries.

The access-list entries represent messages, and may be incomplete or malformed.
Malformed access-lists result in an RPC error.