Understanding Bedrock: The First Step for Optimism to Lay Out the Rollup Ecosystem
Original Title: 《Bedrock Explainer》
Written by: Optimism Official
Compiled by: Frank, Foresight News
Bedrock is the name of the first official version of the OP Stack, designed as a set of free and open-source modular components to power the development of Optimism.
Improvement Summary
Bedrock has improved upon its predecessor, mainly including:
- Utilizing optimized batch transaction compression and using Ethereum as a data availability layer, thereby reducing transaction costs;
- Shortening the delay in packaging L1 transactions into rollups by better handling L1 reorganization;
- Enabling modular proof systems through code reuse;
- Improving node performance by eliminating design debt.
Lower Fees
Bedrock employs optimized data compression strategies to minimize data costs. We are currently benchmarking the impact of this change, but we expect it to significantly reduce fees.
Bedrock also eliminates all Gas costs associated with EVM execution when submitting data to L1, which will further reduce fees by an additional 10% compared to previous versions of the protocol.
Shorter Deposit Confirmation Times
Bedrock introduces support for L1 reorganization in the node software, greatly reducing the time users wait for deposit confirmations.
The earlier versions of the protocol could take up to 10 minutes to confirm deposits, whereas with Bedrock, we expect deposits to be confirmed within 3 minutes.
Improved Modular Proofs
Bedrock abstracts the proof system from the OP Stack so that rollups can use fault proofs or validity proofs (e.g., zk-SNARKs) to prove correct execution after input on the rollup. This abstraction will also allow the use of Cannon to prove faults in the system.
Improved Node Performance
By executing multiple transactions in a single rollup "block," rather than the "one transaction per block" model of previous versions, the performance of the node software has significantly improved.
This allows the costs of Merkle Trie updates to be amortized across multiple transactions, reducing the growth of state data by approximately 15GB per year under current transaction volumes.
Node performance is further enhanced by eliminating technical debt from previous versions of the protocol, including no longer requiring a separate "data transfer layer" node to index L1 and updating node software to efficiently query transaction data from L1.
Improved Ethereum Equivalence
Bedrock has been designed from the ground up to be as "consistent" with Ethereum as possible, eliminating several deviations from Ethereum present in previous version protocols, including:
- The "one transaction per block" model;
- Custom opcodes for fetching L1 block information;
- Separation of L1/L2 fee fields in the JSON-RPC API;
- Custom ERC20 representations of ETH balances.
Bedrock also adds support for EIP-1559, blockchain reorganization, and other Ethereum features present on L1.
Design Principles
Bedrock is built with a modular and upgradeable design, while reusing existing Ethereum code as much as possible, aiming for 100% Ethereum equivalence.
Modularity
By using well-defined interfaces and version control schemes, Bedrock can easily swap out different components in the OP Stack and add new features.
This allows it to adapt to the future development of the Ethereum ecosystem with a flexible architecture, such as:
- Separation of rollup nodes from execution clients;
- Modular fault-tolerant design.
Code Reuse
Bedrock leverages existing Ethereum architecture and infrastructure as much as possible, which allows the OP Stack to inherit security and Lindy effect advantages from the battle-tested codebase used by the Ethereum mainnet.
You will find such examples throughout the design, including:
- Minimally modified execution clients;
- EVM contracts instead of precompiled client code.
Ethereum Equivalence
Bedrock aims to be as compatible as possible with the existing Ethereum developer experience, but there are some exceptions due to fundamental differences between L1 and rollups: differences in fee models, faster block times (2 seconds vs 12 seconds), and special transaction types that include L1 deposit transactions.
These exceptions include:
- Fault proofs designed to prove minimal modifications to the Ethereum execution client;
- Code reuse from the Ethereum execution client for use by nodes and sequencers in L2 networks.
Protocol
Rollups are built on data availability (typically a L1 blockchain like Ethereum), and in the most common configuration, the rollup protocol derives a "canonical L2 chain" from two main sources of information:
- Transaction data published to L1 by a sequencer;
- Deposit transactions submitted to a bridging contract on L1 by accounts and smart contracts.
Here are the basic components of the protocol:
- Deposits are written to the "canonical L2 chain" by directly interacting with smart contracts on L1;
- Withdrawals are written to the "canonical L2 chain" and implicitly trigger interactions with smart contracts and accounts on L1;
- Batches correspond to data written to the rollup's batches;
- Block derivation explains how to interpret data reads on L1 to understand the "canonical L2 chain";
- The proof system defines the finality of the output root published on L1 so that they can be executed (e.g., executing withdrawals).
Deposits
Deposits are a transaction on L1 and will be included in the rollup. By definition, deposits are guaranteed to be included in the "canonical L2 chain" as a means to prevent censorship or control of L2.
Arbitrary Messages Passed from L1
Deposit transactions are rollup transactions made as part of a deposit. With Bedrock, deposits are fully generic Ethereum transactions, meaning accounts or smart contracts on Ethereum can create "deposit" contracts.
Bedrock defines a deposit contract available on L1: it is a smart contract that L1 accounts and smart contracts can interact with to write to L2. The deposit transactions on L2 are derived from the transactions sent from this deposit contract, which include expected parameters such as from, to, and data.
For more details, see the Deposit Contract protocol specification section.
Purchasing Guaranteed L2 Gas on L1
Bedrock also clarifies the Gas burning mechanism and deposit fee market, where the Gas spent on deposit transactions on L2 is purchased through Gas burning on L1.
This Gas is specifically purchased on the fee market, and there is a hard cap on the total amount of Gas provided for all deposit transactions in a single L1 block, which is a mechanism to prevent denial-of-service (DoS) attacks that could occur when writing transactions from L1 to L2, as these transactions consume a lot of Gas on L2 but are cheap on L1.
The Gas provided for deposit transactions is sometimes referred to as "Guaranteed Gas." What makes Guaranteed Gas unique is that it is paid for by burning Gas on L1, making it non-refundable.
The total amount of L1 Gas that must be burned for each unit of guaranteed L2 Gas depends on the L2 Gas price reported by the EIP-1559-style fee mechanism. Additionally, users receive a dynamic Gas allowance based on the amount of L1 Gas spent according to the calculated fee mechanism.
For a deeper explanation, please read the Deposits section of the protocol specification.
Withdrawals
Withdrawals are cross-layer transactions initiated on L2 and completed by transactions executed on L1. Notably, L2 accounts can use withdrawals to call L1 contracts or transfer ETH from L2 accounts to L1 accounts.
Withdrawals are initiated by calling a pre-deployed contract called Message Passer on L2, which records important properties of the message in its storage, and then completes the withdrawal on L1 by calling the OptimismPortal contract to prove the inclusion of this withdrawal message.
In this way, withdrawals differ from deposits: withdrawal transactions must be completed using smart contracts on L1 rather than relying on information derived from blocks.
Two-Step Withdrawal Process
Withdrawal proof verification errors have been the root cause of many cross-chain bridge hacks in recent years. The Bedrock version introduces an additional step in the withdrawal process of previous versions, aimed at providing extra defensive design against these types of errors.
In the two-step withdrawal process, each withdrawal must submit a Merkle proof corresponding to the withdrawal at least 7 days before the final exit, giving monitoring tools a full 7 days to search for and detect invalid withdrawal proofs.
If an invalid withdrawal proof is detected during this period, a smart contract fix can be deployed before funds are lost, greatly reducing the risk of cross-chain bridge compromise.
For more details, see the Withdrawals protocol specification section.
Batch Transactions
In Bedrock, a wired format is defined for messaging between L1 and L2 (i.e., L2 derives blocks from L1, and L2 writes transactions to L1), designed to minimize the costs and software complexity of writing to L1.
Optimized Data Compression
To optimize data compression, L2 transaction lists (referred to as sequencer batches) are organized into object groups (called channels), with the maximum size of each channel defined in configurable parameters, initially set to 9.5 M. These channels are expected to be compressed using compression features and submitted to L1.
Batch Parallel Submission
To parallelize messages from the sequencer submitting compressed channel data to L1, channels are further broken down into "channel frames," which are compressed channel data blocks that can fit into a single L1 transaction.
Assuming "channel frames" are independent and the order is known, Ethereum transactions sent by the sequencer to L1 can be sent in parallel, minimizing the complexity of sequencer software and allowing for filling all available data space on L1.
Minimizing Ethereum Gas
Bedrock removes all execution Gas used to submit channel data to L1 in transactions called "batcher transactions." All validation logic that previously occurred on L1 smart contracts has been moved into the block derivation logic. Instead, "batcher transactions" are sent to a single EOA address on Ethereum, known as the "batch inbox address."
Batches still need to undergo validity checks (i.e., they must be correctly encoded), and the same applies to individual transactions within a batch (e.g., signatures must be valid). Invalid individual transactions in invalid batches and valid batches are treated as discarded and irrelevant to the system.
Note: Ethereum will soon upgrade to a new version that includes EIP-4844, which introduces a separate data writing fee market and increases the upper limit on the amount of data the Ethereum protocol is willing to store, which is expected to further reduce costs associated with publishing data to L1.
For a deeper explanation, please read the Wired Format Specification.
Block Derivation
In Bedrock, the design of the protocol ensures that the timing of deposits on L1 is related to the block derivation of the "canonical L2 chain." This is achieved through a pure function that writes data to L1 based on the sequencer, deposits, and L1 block attributes.
To achieve this, the protocol defines strategies to ensure deposit confirmation, handle L1 and L2 timestamps, and manage the sorting window within channels to ensure correct ordering.
Guaranteeing Deposit Confirmation
The goal of the block derivation protocol is defined as follows:
After each "L2 block interval," there must be an L2 block, and the timestamp of the L2 block must remain synchronized with the timestamp of the L1 block (i.e., ensuring deposits are included in logical time order).
In Bedrock, the concept of "sequencing epoch" is introduced: it is the range of L2 blocks derived from a series of L1 blocks, with each epoch identified by an "epoch number," which equals the block number of the first L1 block in the sorting window. The size of the epoch can vary under certain constraints.
The batch derivation channel will treat the timestamp of the L1 block associated with the "epoch number" as an anchor point for determining the order of transactions on L2. The protocol guarantees that the first L2 block of an epoch will never lag behind the timestamp of the matching L1 block of that epoch. The first block of an epoch must include a deposit on L1 to ensure that the deposit will be processed.
Please note, in the Bedrock version, the target configuration for block intervals on L2 is set to 2 seconds.
Handling L1 and L2 Timestamps
Bedrock attempts to address the issue of coordinating timestamps on L2 with timestamps present in deposit transactions on L1.
It achieves this by allowing a very short time window for sorting, enabling free application of timestamps on L2 transactions between epochs.
The sorting window is a sequence of L1 blocks from which epochs can be derived. In a sorting window, the number N of the first L1 block contains the "batcher transactions" of the epoch.
The sorting window includes blocks, depending on the size of the sorting window: a fixed rollup-level configuration parameter must be at least 2, increasing it will provide the sequencer with more opportunities to sort L2 transactions for deposits, while decreasing it will impose a stricter time window for the sequencer to submit "batcher transactions." This is a trade-off between creating MEV opportunities and increasing software complexity.
A protocol constant called "max sequencer drift" controls the maximum timestamp a block can have within its epoch, allowing the sequencer to remain active in the event of temporary issues connecting to L1.
Block Derivation Pipeline
The "canonical L2 chain" can be processed from scratch by starting from the L2 genesis state, setting the starting L2 chain to the first epoch, and then processing all sorting windows to determine the correct order pipeline for sequencer batches and deposits based on the following simplified order:
Fault Proofs
After the sequencer processes one or more L2 blocks, the outputs derived from executing transactions from these blocks will need to be written to L1 for trustless execution of messaging from L2 to L1, such as withdrawals.
In Bedrock, outputs are hashed in a tree structure to minimize the cost of capturing any data fragments for proof outputs. Proposers regularly submit the output root as the Merkle root of the entire "canonical L2 chain" to L1.
Future upgrades to the OP Stack should include a specification for a fault proof variant that includes bindings to incentivize proposers to submit the correct output root.
For complete details, please read the L2 Output Root Proposals section of the protocol specification.
Execution
In Bedrock, the OP Stack relies heavily on the separation between the Ethereum execution layer and consensus layer, necessitating reliance on the technical focus separation specified by Ethereum.
Thus, Bedrock introduces the separation of execution clients and rollup nodes in the same manner.
Execution Client
The execution client is the system that sequencers and other types of node operators run to determine the state of the "canonical L2 chain." It also performs other functions, such as handling inbound transactions and peer-to-peer communication, as well as managing system state to handle queries against it.
With Bedrock, the OP Stack aims to reuse Ethereum's own execution client specifications and many of its execution operations. In this version, Bedrock demonstrates extremely limited modifications to the Ethereum client go-ethereum, with differences of less than 2000 lines of code.
There are two fundamental reasons for the differences: handling deposit transactions and charging transaction fees.
Handling Deposit Transactions
To represent deposited transactions in the rollup, an additional transaction type has been introduced. The execution client implements this new transaction type according to the EIP-2718 transaction type standard.
Charging Transaction Fees
Rollups fundamentally have two types of transaction-related fees:
One is the sequencer fee. The cost of operating the sequencer is calculated using the same Gas table and the same EIP-1559 algorithm as Ethereum, and these fees fluctuate based on network congestion.
The other is the data availability fee. The data availability cost is associated with writing batch-processed transactions to L1, and these fees are intended to cover the costs the sequencer must pay to submit batch transactions to L1.
In Bedrock, the data availability portion of the fees is determined based on information in a rollup system smart contract called GasPriceOracle, which is updated during block derivation based on Gas price information retrieved from the L1 block attributes inserted at the start of each epoch.
Bedrock specifies that these two fees are added to a single field when using JSON-RPC.
Rollup Nodes
Unlike Ethereum, Bedrock does not have PoS consensus. Instead, the consensus of the "canonical L2 chain" is defined by block derivation. The OP Stack's execution client communicates with a new component that implements block derivation called the rollup node, which communicates with the execution client using the same engine API as Ethereum.
The rollup node is a stateless component responsible for deriving the state of the system by reading data and deposits from L1. In Bedrock, the rollup node can be used to sort incoming transactions from users or other rollup nodes, or to verify confirmed transactions published on L1 by relying solely on L1.
The various uses of the rollup node are outlined as follows:
Validating the "Canonical L2 Chain"
The simplest mode of running a rollup node is to strictly follow the "canonical L2 chain." In this mode, the rollup node has no peers and is solely used to read data from L1 and interpret it according to the rules of the block derivation protocol.
One purpose of this node is to verify whether any output root shared by other nodes or published on L1 is correct according to the protocol definition. Additionally, proposers intending to submit output roots to L1 can use optimism_outputAtBlock to generate the output roots they need and return the corresponding 32-byte hash value for the L2 output root.
For this, the node should only need to follow the "finalized" block header. The term "finalized" refers to Ethereum's PoS consensus (i.e., canonical and almost irreversible), while the "finalized" L2 block header is the block header of the "canonical L2 chain" derived solely from the "finalized" L1 block.
Participating in the L2 Network
The most common way to use a rollup node is to participate in the network of other rollup nodes, tracking the progress and state of L2. In this mode, the rollup node reads both data and deposits observed from L1, interpreting them as blocks and accepting inbound transactions from users and peers in the network of other rollup nodes.
Nodes participating in the network can use the secure and insecure block headers of the L2 they are synchronizing.
- Secure L2 block headers indicate rollups that can be constructed, where each block (including the header) can be fully derived from the reference L1 chain, before L1 must be "finalized" (i.e., reorganization may still occur on L1);
- Insecure L2 block headers include insecure blocks that have not yet been derived from L1. These blocks either come from operating the rollup node as a sequencer or from insecure synchronization with the sequencer. This is also referred to as the "latest" block header. In the event of a divergence, the secure L2 block header is always chosen over the insecure L2 block header. When divergences occur, the insecure portion of the chain will reorganize;
In most cases, for end-user applications, rollup nodes in the L2 network will reference insecure L2 block headers.
Transaction Sorting
The third way to use a rollup node is for transaction sorting. In this mode, the rollup node will create new blocks on top of the insecure L2 block headers. Currently, there is only one sequencer per OP Stack network.
The sequencer is also responsible for publishing batch transactions to L1 so that other nodes in the network can synchronize from them.
Batcher
The role of the sequencer is to produce batch transactions, for which the sorter can run the rollup node and have separate processes that execute batches by reading from the trusted rollup node they run on.
This guarantees an additional component of the OP Stack called the batch transaction processor, which reads transaction data from the rollup node and interprets it as batch transactions to be written to L1. The batcher component is responsible for reading the insecure L2 block headers of the rollup node run by the sequencer, creating batcher transactions, and writing them to L1.
Standard Bridging Contracts
Bedrock also includes a pair of bridging contracts for the most common types of deposits, known as standard bridging contracts. These contracts encapsulate deposit and withdrawal contracts, providing simple interfaces for deposits and withdrawals of ETH and ERC-20 tokens.
The design of these bridging contracts involves locking native tokens in a contract on one side and managing the minting and burning of a wrapped token on the other side, where bridging native tokens involves locking the native tokens in the contract and then minting an equivalent amount of wrapped tokens on the other side of the cross-chain bridge.
For more details, see the standard Cross-Chain Bridges protocol specification section.
Cannon
While fault-tolerant construction and verification are implemented in Cannon, the fault proof game specification and integrating output root challengers into the rollup node are part of subsequent specification milestones.
Further Reading
Protocol Specification
The Protocol Specification defines the technical details of the OP Stack and serves as the latest source of truth for the internal workings of the protocol.
Differences in Bedrock
To gain a deeper understanding of the differences between Bedrock and previous versions, please refer to "How is Bedrock Different".