The L2 Beacon Roots protocol exposes the L1 beacon chain block root within any L2 OP chain, enabling beacon chain data validation directly on L2.
By making the beacon chain root accessible on L2, it provides trust-minimized access to specific L1 beacon chain data directly on L2. This allows L2 contracts to securely interact with and manage L1 beacon chain data.
The protocol is compatible with all OP chains.
The protocol leverages EIP-4788, which exposes beacon chain roots to the L1 EVM, along with the OP canonical bridge to facilitate data transmission between L1 and L2.
Contract | Optimism Sepolia (L2) | Sepolia (L1) |
---|---|---|
BeaconRootsVerifier | 0x1487541081FCf46D2dF0d2A4B2b8eCcC64951f67 | |
L2BeaconRoots | 0x716d304cA10a4e8f418FeFC9c4E008EDDdd752bE | |
L1BeaconRootsSender | 0xB5c70f0CD8Ca5738E555FB76E9f1B82BF254fc5b |
The L1 Beacon Roots contract, as defined in EIP-4788, is an L1 system contract. With each new block, before any transactions are executed, this contract records the parent beacon block root, indexed by the block timestamp. These recorded roots are then accessible to other L1 smart contracts.
This protocol contract on the L1 chain is responsible for relaying beacon block roots to the L2 chain through the OP canonical bridge. It provides public methods to send these roots by fetching them from the L1 Beacon Roots contract, preparing a cross-chain message for the OP bridge, and transmitting it to the L2 via the L1 CrossDomainMessenger.
The CrossDomainMessenger contracts on both L1 and L2 chains serve as the OP official bridge, facilitating data communication between L1 and L2. This protocol exclusively uses messaging from L1 to L2.
The contract on the L2 network responsible to store and provide access to the beacon block roots. It receives beacon roots from the L1BeaconRootsSender via the L2 CrossDomainMessenger, upon message relaying it stores the beacon block roots.
A contract on the L2 network that exposes functions to verify SSZ Merkle proofs for beacon blocks. In particular, it enables to verify withdrawals root. It reads the beacon roots from the L2BeaconRoots contract and perform SSZ Merkle proofs verifications against those roots.
The L1BeaconRootsSender's methods for sending data are entirely programatic, and enforce fetching block roots from the official beacon roots contract before transmitting it to the L2. This ensures that only valid beacon block roots from the official L1 Beacon Roots contract are transmitted to the L2BeaconRoots contract. This allows the send methods to be public without compromising the protocol's security.
To guarantee that the L2BeaconRoots contract only stores valid beacon roots, its set
method is restricted to the L2 CrossDomainMessenger, and only accepts messages from the designated L1BeaconRootsSender remote contract.
Before running the scripts, make sure to set the following environment variables:
ETH_RPC_URL_SEPOLIA
: RPC URL of a Sepolia networkPRIVATE_KEY_SEPOLIA
: The private key of an account with sufficient Sepolia ETH to cover transaction fees.
After setting the environment variables, execute the following command:
make send-current-beacon-root
Before running the scripts, make sure to set the following environment variables:
BEACON_URL
: Beacon node URL (Sepolia)
After setting the environment variables, execute the following command:
BLOCK_ID=<BLOCK_ID> make generate_proof
where BLOCK_ID
is a valid Sepolia beacon block ID.
Example
BLOCK_ID="0x6cd96952b3dacfc7188b9ad9b17a2012b67827741f60b3c4e51a14f9d07f0c91" make generate-proof
should ouput
{
"root": "0x6cd96952b3dacfc7188b9ad9b17a2012b67827741f60b3c4e51a14f9d07f0c91", // beacon block root
"leaf": "0x2e0130c856001a39482aa288f64f001a619dc46a5377b8c278776a41b3a62269", // here the leaf is the Withdrawals root
"index": "0x192e", // the generalized index of the Withdrawals Root in the SSZ Merkle tree
"proofs": [
"0x0000060000000000000000000000000000000000000000000000000000000000",
"0x1c81242091b62f06c68069f0574d78b7d5e2305ec0638d70aea9ddf4842f7cc2",
"0xa9a30aed71d87b062cdaf6a68476d1fb88ba17824d33073fc0a47c14ee2f0542",
"0xa55982c5098e75240dfa67589b6087b473360058919c1354f54fec07d0d54964",
"0x2b075536cd68d87c6a42236945f8946ec654d1603cea26fd63fcfb714bc4e678",
"0x13b46e5c802956f74b294cd43fee7f7fbe2786fa5234312142919ac6fba8122d",
"0x94e05d13e26c028d1e415400d68a54fe931d72a5fe482dabf79d1c52dd82573a",
"0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71",
"0xb8704d40a68b6bddae5e2d83c5be32959491185c4e41a5e355181845922d000c",
"0x0000000000000000000000000000000000000000000000000000000000000000",
"0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b",
"0xef91c6e7d3789ed96c6659a2641987fdeefee8c3fd6812f74ceeb258a3ce1561"
] // the proofs to prove the Merkle branch
}
After successfully sending a Beacon Block root on the L2 and generating the corresponding proof.
We can verify the withdral root on Etherscan]
Where
_timestamp
and_blockRoot
can be retrieved from the events emitted by L2BeaconRoots_proofs
and_withdrawlRoots
can be retrieved from the CLI proof generation for block_blockRoot