RIP-7859: Store L1 origin data and historic L2 block hashes in L2 state

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)

Abstract


In every L2 block store the block hash for the L1 block that the L2 block derives inputs from in a ring buffer inside a pre-deployed smart contract. This ring buffer will hold the last L1_ORIGIN_BUFFER_LENGTH number of origin hashes and will be referred to as the L1 origin storage contract.

Additionally, in every L2 block store the last L2_HISTORY_BUFFER_LENGTH historical L2 block hashes in a ring buffer inside a pre-deployed smart contract. This contract will be referred to as the L2 history storage contract.

Motivation


This RIP is the combination of EIP-2935 unmodified and EIP-4788 with minor modification to store a hash of a subset of an L2 block's L1 origin execution block header fields instead of beacon block roots.

In combination these two improvements enable L2s that settle and derive from Ethereum to verify arbitrary claims about Ethereum and all the other L2s that settle to Ethereum.

L1 origin storage contract

Maintaining inside the L2 execution environment a view of the L1 state that a given L2 block derives from- that L2 block's L1 origin- is broadly useful for L1<->L2 and L2<->L2 interoperability and composability. Some specific examples of where this is useful include: RIP-7755, RIP-7789, RIP-7728, ERC-3668, and verifying AVS validator set state on the L1 for purposes of EigenDA or Lagrange state committees or other AVS protocols. 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.

L2 history storage contract

Maintaining inside the L2 execution environment historic L2 block hashes is useful for the stated side-benefit provided as motivation for EIP-2935:

"A side benefit of this approach could be that it allows building/validating proofs related to last L2_HISTORY_BUFFER_LENGTH ancestors directly against the current state."

The value provided by this capability is increased in the context of L2s as most L2s only submit L2 outputs to the L1 at sparse intervals. If the L2_HISTORY_BUFFER_LENGTH is long enough to span the L2 outputs on the L1 then it is made possible for any contract on the L1 to verify arbitrary L2 state, storage, transaction, or receipt data at any L2 height using the historical L2 block hashes stored in the L2 history storage contract of an L2 output.

Specification


Specification mirrors the specifications of EIP-2935 and EIP-4788 with modification of EIP-4788 such that the ring buffer stores a keccak256 hash of a subset of L1 execution block header fields instead of beacon block roots.

Some L2s already support EIP-4788 so to avoid a conflict we must select a different address for the pre-deployed contract L1_ORIGIN_STORAGE_ADDRESS.

If an L2 already supports EIP-2935 no modification to that contract is needed to support the L2 history storage contract. To fulfill this RIP's specification they only need to add support for the L1 origin storage contract.

L1 origin storage contract

NameValue
L1_ORIGIN_BUFFER_LENGTH8192
L1_ORIGIN_SYSTEM_ADDRESS0xfffffffffffffffffffffffffffffffffffffffe
L1_ORIGIN_STORAGE_ADDRESStbd
L1_ORIGIN_FORK_TIMESTAMPvariable - defined by the L2

The L1 origin storage contract has two operations: get and set. The input itself is not used to determine which function to execute, for that the result of caller is used. If caller is equal to L1_ORIGIN_STORAGE_ADDRESS then the operation to perform is set. Otherwise, get.

get

  • Callers provide the timestamp they are querying encoded as 32 bytes in big-endian format.
  • If the input is not exactly 32 bytes, the contract must revert.
  • If the input is equal to 0, the contract must revert.
  • Given timestamp, the contract computes the storage index in which the timestamp is stored by computing the modulo timestamp % L1_ORIGIN_BUFFER_LENGTH and reads the value.
  • If the timestamp does not match, the contract must revert.
  • Finally, the keccak256([l1Origin.block.height, l1Origin.block.stateRoot, l1Origin.block.transactionRoot, l1Origin.block.receiptRoot]) associated with the timestamp is returned to the user. It is stored at timestamp % L1_ORIGIN_BUFFER_LENGTH + L1_ORIGIN_BUFFER_LENGTH.

set

  • Caller (the sequencer) provides keccak256([l1Origin.block.height, l1Origin.block.stateRoot, l1Origin.block.transactionRoot, l1Origin.block.receiptRoot]) as calldata to the contract.
  • Set the storage value at header.timestamp % L1_ORIGIN_BUFFER_LENGTH to be header.timestamp
  • Set the storage value at header.timestamp % L1_ORIGIN_BUFFER_LENGTH + L1_ORIGIN_BUFFER_LENGTH to be calldata[0:32]

Bytecode

The exact contract bytecode is shared below.


Deployment

The L1 origin storage contract can be deployed as a pre-deployed contract.

Block processing

At the top of every L2 block where block.timestamp >= L1_ORIGIN_FORK_TIMESTAMP the sequencer will call L1_ORIGIN_STORAGE_ADDRESS as L1_ORIGIN_SYSTEM_ADDRESS with the 32-byte input of keccak256([l1Origin.block.height, l1Origin.block.stateRoot, l1Origin.block.transactionRoot, l1Origin.block.receiptRoot]), a gas limit of 30_000_000, and 0 value. This will trigger the set() routine of the beacon roots contract. This is a system operation and therefore:

  • the call must execute to completion
  • the call does not count against the block's gas limit
  • the call does not follow the EIP-1559 burn semantics - no value should be transferred as part of the call
  • if no code exists at L1_ORIGIN_STORAGE_ADDRESS, the call must fail silently

If this EIP is active in a genesis block, no system transaction may occur.

l1origin is defined as the L1 block from which inputs were taken in order to derive the nascent L2 block. If an L2 block is built without L1 inputs but the L2 maintains a strict mapping of L2->L1 blocks based on timestamps then the L1 origin is the L1 block that satisfies this mapping. If an L2 block is built without L1 inputs and the L2 maintains no strict mapping of the L2->L1 blocks then the L1 origin remains the same as the L1 origin of the previous L2 block.

The invariants are

  1. Every L2 block has an L1 origin
  2. 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
  3. 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

The correctness of an L2 block according to the above invariants must be enforced at settlement time. This can be accomplished by codifying these constraints into an optimistic rollup's fault proof program or into a zk-rollup's validity circuit.

Additional constraints on the L1 origin may be enforced, such as a maximum difference between an L2 block's timestamp and the timestamp of its L1 origin block, but these are not required.

L2 history storage contract

NameValue
L2_HISTORY_BUFFER_LENGTH8192
L2_HISTORY_SYSTEM_ADDRESS0xfffffffffffffffffffffffffffffffffffffffe
L2_HISTORY_STORAGE_ADDRESStbd
L2_HISTORY_FORK_TIMESTAMPvariable - defined by the L2

Note: an L2 could perform this upgrade incrementally such that L1_ORIGIN_FORK_TIMESTAMP != L2_HISTORY_FORK_TIMESTAMP

The history contract has two operations: get and set. The set operation is invoked only when the caller is equal to the L2_HISTORY_SYSTEM_ADDRESS. Otherwise, the get operation is performed.

get

It is used from the EVM for looking up block hashes.

  • Callers provide the block number they are querying in a big-endian encoding.
  • If calldata is bigger than 2^64-1, revert.
  • For any output outside the range of [block.number-L2_HISTORY_BUFFER_LENGTH, block.number-1] return 0.

set

  • Caller provides l2Block.parent.hash as calldata to the contract.
  • Set the storage value at block.number-1 % L2_HISTORY_BUFFER_LENGTH to be calldata[0:32].

Bytecode

The exact contract bytecode is shared below.


Deployment

The L2 history storage contract can be deployed as a pre-deployed contract.

Block processing

At the top of every L2 block where block.timestamp >= L2_HISTORY_FORK_TIMESTAMP the sequencer will call to L2_HISTORY_STORAGE_ADDRESS as L2_HISTORY_SYSTEM_ADDRESS with the 32-byte input of l2Block.parent.hash, a gas limit of 30_000_000, and 0 value. This will trigger the set() routine of the history contract.

  • the call must execute to completion
  • the call does not count against the block's gas limit
  • the call does not follow the EIP-1559 burn semantics - no value should be transferred as part of the call
  • if no code exists at L2_HISTORY_STORAGE_ADDRESS, the call must fail silently

Note that, it will take L2_HISTORY_SYSTEM_ADDRESS blocks after the EIP's activation to completely fill up the ring buffer. Initially the contract will only contain the parent hash of the fork block and no hashes prior to that.

Rationale


Hash of a subset of execution block fields instead of beacon block root of full execution block hash

keccak256([l1Origin.block.height, l1Origin.block.stateRoot, l1Origin.block.transactionRoot, l1Origin.block.receiptRoot]) is committed into the ring buffer in order to reduce gas costs relative to using either beacon block roots or execution block hashes.

The preimage to this hash is the 128 byte concatenation of the four fields [l1Origin.block.height, l1Origin.block.stateRoot, l1Origin.block.transactionRoot, l1Origin.block.receiptRoot]. This is smaller than the 708 byte preimage of a full execution header and smaller than an SSZ proof for a Merkle tree with depth of 3 or greater (160+ bytes).

The tradeoff is that the contents of this ring buffer can not be used to verify other fields of the L1 execution header or beacon block.

Length of the ring buffers

Ring buffer lengths are currently set to the lengths prescribed in EIP-2935 and EIP-4788.

For the L2 history storage contract 8192 L2 headers corresponds to ~4.55 hours worth of L2 blocks for an L2 with block time of 2 seconds or ~34 minutes worth of blocks for an L2 with block time of 250 milliseconds. The former is enough time to span the current average L2 output interval of major L2s today (~1 hour). The latter is not and warrants discussing an extension of the ring buffer to accommodate L2s with faster block times or longer output frequencies. For this reason it may make sense to not prescribe a set length for this ring buffer as L2s can set the length that best fits their system.

Backwards Compatibility


This RIP introduces backwards incompatible changes to the block derivation and verification of an L2. These changes are purely additive and do not break anything related to current user activity and experience, but they do require changes to the L2's L1->L2 derivation process and the verification logic in their output settlement contract(s).

Test Cases


N/A

Reference Implementation


N/A

Security Considerations


N/A

Copyright


Copyright and related rights waived via CC0.