RIP-7859: Expose L1 origin information inside L2 execution environment

Standardize the commitment of L1 origin data and historic L2 block hashes in L2 state


Metadata
Status: DraftStandards Track: CoreCreated: 2025-01-10
Authors
Ian Norden (@i-norden), Bo Du (@notbdu), Christian Angelopoulos (@christianangelopoulos), Mark Tyneway (@tynes)

Abstract


Pre-deployed smart contract for exposing inside the L2 execution environment information about the L1 blocks that the L2 most recently included bridge inputs from.

Motivation


L2 blocks are composed of both L2 transactions sent directly to the sequencer and transactions that derive from deposit transactions to the native bridge on the L1. The inclusion of L1 deposit inputs into an L2 block creates a mapping between L2 blocks and the L1 blocks they are produced from. The latest L1 block to contribute to the state of an L2 block is called that L2 block's L1_ORIGIN.

By including information about the L1_ORIGIN in the L2 execution environment and making it accessible to other contracts on the L2 we unlock the capability to verify arbitrary state claims about the L1.

This capability is useful for a wide range of standards including verification in cross-L2 communication such as RIP-7755, enforcing L2 consistency checks such as RIP-7789, and verifying reads from the L1 such as RIP-7728 and ERC-3668. In general, it is useful to any contract on the L2 that needs to verify arbitrary L1 state, storage, transaction, or receipt data inside the L2.

This extends to verifying claims about other L2s that settle their state into Ethereum, providing a simple mechanism for interoperability between L2s without additional trust assumptions. If these L2s also support an EIP-2985 ring buffer containing their historic block hashes then it becomes feasible to verify claims about their receipts and transactions even if the L2 only settles outputs sparsely.

Specification


This specification defines the contract interface for accessing information about the current and historic L1_ORIGINs inside the L2 execution environment as well as defines the invariants that constrain the meaning of L1_ORIGIN. It also specifies a pre-deployed contract address that should be the same across rollups.

The precise mechanism by which this information is loaded into the L2 during derivation is not specified nor is the underlying data structure or its layout in the contract. Specification at this level is avoided in order to better accommodate the architectural diversity present in rollup stacks, allowing them to implement this interface as best fit for their system.

However, it is necessary to define some constraints on the definition of the L1_ORIGIN for a given L2 block so that the protocols and contracts that leverage this interface can operate under a set of common assumptions across different rollup stacks.

L1 Origin invariants

We will begin by defining some invariants on the meaning of L1_ORIGIN

  1. Every L2 block has an L1_ORIGIN.
  2. An L1_ORIGIN must be a canonical L1 block.
  3. For an L2 block N with timestamp/height greater than L2 block M the L1_ORIGIN of N must have a height/timestamp greater than the L1 origin of M or share the same L1_ORIGIN.
  4. If an L2 block includes transactions derived from L1 inputs the L1_ORIGIN for that L2 block must be the latest (highest height/timestamp) L1 block that contributed L1 inputs.

Notice from the above rules that two L2 blocks can share the same L1_ORIGIN and, in fact, this is necessary for all rollups that have a shorter blocktime than the L1 blocktime.

Notice from the above that it is not necessary that sequential L1_ORIGINs are contiguous L1 blocks. That is, if an L2 block N has L1 block M as its L1_ORIGIN then the L1_ORIGIN for L2 block N+1 does not have to be L1 block M or L1 block M+1 but could be L1 block M+2, M+3, M+x, etc. This is in order to accommodate rollup stacks that do not maintain a strict mapping of every L2 blocks to an L1 block but instead only consider L1 blocks when they contain deposit transactions.

An important property that arises out of the above invariants is that the L1_ORIGIN for a given L2 block represents the latest L1 block whose reorg would necessitate a reorg of that L2 block.

Contract interface

With these invariants in place we now define the contract interface for exposing information about the L1_ORIGIN. In defining this interface we also define what information about the L1_ORIGIN must be exposed. We define 9 methods in total for the L1OriginSource interface. These methods provide access to current and historic L1_ORIGIN values that we believe are most useful: block hash, state root, receipt root, transaction root, and block height.


For these latter methods providing historic data access we specify that the data be available for at least the most recent 8192 L1 blocks.

Pre-deployed contract address

The contract exposing the interface defined above should exist at a common address across all rollups. This address is called the L1_ORIGIN_CONTRACT_ADDRESS and is set to (tdb).

Rationale


Access to individual fields vs block hash or beacon block root

Individual header fields are exposed in order to improve UX and reduce gas costs relative to exposing only L1 block hashes or beacon block roots. Opening a block hash or beacon root to access individual fields within them would require providing the entire preimage or SSZ proof in calldata. The preimage for a block hash is 708 bytes while the SSZ proof for a Merkle tree with depth of 3 or greater is 160+ bytes. SSZ Merkle trees for beacon blocks on average have a depth of 7 or 8 levels.

To retain the ability to verify other fields of the L1 execution header or beacon block we still expose the block hash and beacon block root.

Access to historic L1 origin information

In this RIP we propose an interface that allows for access not to only the current L1_ORIGIN information but also for the last 8190 L1_ORIGINs. This provides a view of the latest 8191 L1_ORIGINs, matching (and motivated by) the lengths of the ring buffers in EIP-4788 and EIP-2935. This number of L1 blocks represents one sync committee period (256 epochs, 8192 slots), assuming no slots are skipped.

Access to historic L1_ORIGIN information is useful for applications that need to verify claims about historic transactions or receipts since transaction and receipt roots do not accumulate across blocks. It is also useful for verifying state and storage at accounts or slots that are overwritten across blocks. Furthermore, it is useful for checking that two blocks from two distinct rollups are built off the same branch of Ethereum history regardless of if they share the same current L1_ORIGIN.

Enforcing correctness of L1_ORIGIN

Correct L1_ORIGIN selection and storage in the contract underlying the L1OriginSource interface must be enforced at rollup settlement time according to the invariant rules. This is accomplished by these rules being part of the derivation and STF logic codified into the L2 output verification logic (e.g. fault proof program or SNARK circuit).

Tradeoffs vs L1SLOAD with verification deferred to settlement time

L1_ORIGIN info exposed in the EVM could be used to verify L1SLOAD calls but another way to verify L1SLOAD calls is by deferring verification to settlement time in a manner analogous to how the correctness of the L1_ORIGIN view is enforced at settlement time. This approach provides a tremendous gas cost saving as inclusion proofs or SNARKs do not need to be provided and evaluated inside the L2 EVM.

If L1SLOAD is implemented with deferred verification then it provides a clear cost advantage versus using the L1_ORIGIN to read L1 storage data. Nonetheless, access to the L1_ORIGIN as described here exhibits some properties that L1SLOAD does not:

  1. It can be used to verify claims about accounts, receipts/logs, and transactions.
  2. It can be used to verify claims about historic L1 data.
  3. It can be used to check whether two rollups are building off the same Ethereum fork and enforce this as a condition for executing cross-rollup transactions (RIP-7789).
  4. L1SLOAD cannot operate if the L2 sequencer loses connectivity to the L1.
  5. Exposing an explicit linkage between L2 and L1 blocks in the VM can make it simpler for rollups to keep track of when they need to rollback due to a reorg on the L1.
  6. Exposing an explicit linkage between L2 and L1 blocks in the VM can make it simpler for external apps/entities (outside the L2 VM) to tell what the L1 origin is.
  7. L1SLOAD includes in its rationale a request for rollups to support an L1_ORIGIN view in order to enforce consistency of the returned values.

Taken together, we believe that it would be best to both support L1SLOAD and expose an L1_ORIGIN view inside the VM.

Meaning in the context of an L3

An L3 can be thought of as an L2 of an L2, in this context the L1_ORIGIN is instead an L2_ORIGIN. The same information about the L2_ORIGIN can be exposed inside the L3 execution environment and the invariant rules for the meaning of the L2_ORIGIN are the same as defined for the L1_ORIGIN here.

Inside the L3 it is possible to access L1_ORIGIN info through the L2_ORIGIN view since it commits to the L1_ORIGIN inside the L2. This access will be specific to how the L1_ORIGIN data is represented in the L2's storage and will require providing MMPT proofs opening the L2_ORIGIN state root down to where the L1_ORIGIN information is stored. For this reason, it may be advantageous to support both an L1_ORIGIN and L2_ORIGIN contract interface inside an L3.

Backwards Compatibility


This RIP is backwards compatible as it is only adding a new pre-deployed contract to the EVM.

Potential implementation approaches


We avoid specifying a single implementation for this RIP but use this section to provide some examples for how it may be implemented.

One approach would be to use ring buffer constructions as in EIP-4788 and EIP-2935 where these ring buffers store the L1_ORIGIN field values. These buffers can be updated with a system call or transaction at the top of the L2 block using the L1 context available to the sequencer as it processes deposits from the L1_ORIGIN into the nascent L2 block.

Another approach would be to use a contract such as Optimism's L1Block contract that stores each L1 header fields in its own contract variable. This contract is the original inspiration for this RIP. In this approach the sequencer includes at the top of a nascent L2 block an L1 attributes transaction that calls the L1Block contract with information about the current L1_ORIGIN.

Test Cases


N/A

Reference Implementation


N/A

Security Considerations


N/A

Copyright


Copyright and related rights waived via CC0.