--- rip: 7859 title: Expose L1 origin information inside L2 execution environment description: Standardize the commitment of L1 origin data and historic L2 block hashes in L2 state author: Ian Norden (@i-norden), Bo Du (@notbdu), Christian Angelopoulos (@christianangelopoulos), Mark Tyneway (@tynes) discussions-to: https://ethereum-magicians.org/t/rip-7859-expose-l1-origin-information-inside-l2-execution-environment/22855 status: Draft type: Standards Track category: Core created: 2025-01-10 requires: --- ## 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](./rip-7755.md), enforcing L2 consistency checks such as [RIP-7789](https://ethereum-magicians.org/t/rip-7789-cross-rollup-contingent-transactions/21402), and verifying reads from the L1 such as [RIP-7728](./rip-7728.md) and [ERC-3668](https://eips.ethereum.org/EIPS/eip-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](https://eips.ethereum.org/EIPS/eip-2935) 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_ORIGIN`s 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_ORIGIN`s 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. ```solidity interface L1OriginSource { function getL1OriginBlockHash() external view returns (bytes32 blockHash); function getL1OriginParentBeaconRoot() external view returns (bytes32 blockHash); function getL1OriginStateRoot() external view returns (bytes32 stateRoot); function getL1OriginReceiptRoot() external view returns (bytes32 receiptRoot); function getL1OriginTransactionRoot() external view returns (bytes32 transactionRoot); function getL1OriginBlockHeight() external view returns (uint256 blockHeight); function getL1OriginBlockHashAt(uint256 height) external view returns (bytes32 blockHash); function getL1OriginParentBeaconRootAt(uint256 height) external view returns (bytes32 blockHash); function getL1OriginStateRootAt(uint256 height) external view returns (bytes32 stateRoot); function getL1OriginReceiptRootAt(uint256 height) external view returns (bytes32 receiptRoot); function getL1OriginTransactionRootAt(uint256 height) external view returns (bytes32 transactionRoot); } ``` 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_ORIGIN`s. This provides a view of the latest 8191 `L1_ORIGIN`s, 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](../LICENSE.md).