The sequencer is one of the core components in the Scroll protocol: it is responsible for maintaining the L2 blockchain. The sequencer also ensures that the user and developer experience on Scroll closely resemble those on Ethereum. It achieves this by maintaining EVM and RPC behaviour that is directly inherited from Ethereum, with very few modifications.
The primary functions of Scroll's sequencer are:
It is worth highlighting that bridging messages and tokens from L1 to L2 (deposits) is also the sequencer's responsibility. Messages from L2 to L1 (withdraws), on the other hand, can be executed by any user on L1.
The secondary functions of Scroll's sequencer are:
The sequencer, being a fork of go-ethereum, inherits most of its functionality from Ethereum.
This includes transaction and block data structures, EVM execution, RPC and p2p protocols.
As these were not reimplemented but were directly inherited from go-ethereum's original code, we can have a very high certainty of Scroll's compatibility with Ethereum.
The following sections will introduce the main components of the sequencer, the end-to-end sequencing workflow, and some design details.
In the remainder of this article, L1 will refer to Ethereum, while L2 will refer to Scroll.
The Scroll sequencer has two main components: l2geth
and rollup-relayer
.
l2geth
is Scroll's fork of go-ethereum.
It is responsible for building and executing blocks and maintaining the blockchain state.
l2geth
inherits most of its functionality from Ethereum, with some notable differences listed below.
l2geth
has the following submodules (not exhaustive list):
SyncService
: Sync and store L1 messages in l2geth
local database.rollup-relayer
is responsible for splitting up the Scroll blockchain into sections that are suitable for proving and commitment on L1.
The two main units of this are chunks and batches.
A chunk is simply a contiguous section of the L2 ledger, it is a collection of L2 blocks, and chunks are the unit of zkEVM proving.
Batches
, on the other hand, are collections of chunks that are submitted to L1, along with an aggregate proof.
rollup-relayer
has the following submodules:
chunk-proposer
: Collect L2 blocks, propose chunks while enforcing certain limits (see below).batch-proposer
: Collect chunks, propose batches while enforcing certain limits (see below).relayer
: Submit batches and batch proofs to L1.Furthermore, the following two Scroll contracts are closely related to the sequencer:
L1MessageQueue
maintains a queue of messages that the sequencer must relay to L2.L1ScrollMessenger
) as deposit transactions or through EnforcedTxGateway
as enforced transactions.ScrollChain
maintains batches and their finalization status.commitBatch
function, which stores its batchHash
in this contract.finalizeBatchWithProof
function.We will now present the end-to-end sequencing workflow from the perspective of a transaction.
Transactions can originate either from L2 or from L1, but during sequencing these two types are processed mostly the same way.
Most Scroll transactions originate on L2:
eth_sendRawTransaction
API.Some transactions (deposit and enforced transactions) originate on L1:
ScrollGatewayRouter
contract, one of the specific gateways like StandardERC20Gateway
, or they can also submit the deposit to the L1ScrollMessenger
contract directly.EnforcedTxGateway
contract.L1MessageQueue
, which then emits a QueueTransaction
event.SyncService
then collects all new QueueTransaction
events.QueueTransaction
event, the sequencer constructs a special transaction (L1MessageTx
) and stores it in its local database.The block creation and commit steps are shared for the two transaction types:
l2geth
worker periodically starts a new mining job.l2geth
broadcasts it into the Scroll peer-to-peer network.rollup-relayer
collects the new L2 block.rollup-relayer
commits the batch by calling commitBatch
on the ScrollChain
contract on L1.L1MessageQueue
contract on L1.)ScrollChain
and stored in its contract storage.The finalization steps are as follows:
rollup-relayer
, along with the updated state root and withdraw root.ScrollChain
verifies the submitted proof using an on-chain verifier contract.L1MessageQueue
.At this point, the transactions included in the finalized batch are considered finalized.
l2geth
: Summary of modificationsl2geth
is a fork of [go-ethereum](https://github.com/ethereum/go-ethereum)
.
As such, it inherits most of Ethereum's behaviours.
However, we needed to make some breaking changes to l2geth
to enable more efficient proving.
This section provides a non-exhaustive list of the modifications, along with their rationale.
zktrie
Opcodes:
BASEFEE
opcode are disabled and behaves the same way as INVALID
.SELFDESTRUCT
opcode is disabled and behaves the same way as INVALID
.DIFFICULTY
/PREVRANDAO
returns 0
since l2geth does not run PoW or PoS.BLOCKHASH
returns keccak(chainId || height)
.BLOCKHASH
will be unique for each block but it will not correspond to the actual L2 blockhash and it cannot be used as a source of randomness.Precompiles
sha256
, ripemd-160
, and blake2f
cannot be verified by the current zkEVM version and thus they are disabled.modexp
works but only supports inputs of 32 bytes (uint256
) or shorter.ecPairing
works but only allows up to 4 inputs.Fees
L1GasOracle
contract on L2.L1MessageTx
L1MessageTx
.SyncService
that monitors finalized blocks on L1 and collects L1 messages from these.StateAccount
PoseidonCodeHash
to StateAccount
.KeccakCodeHash
in StateAccount
.EXTCODEHASH
opcode returns the KeccakCodeHash
, maintaining compatibility with Ethereum.CodeSize
to StateAccount
.EXTCODESIZE
opcode returns CodeSize
.New validation rules
L1MessageQueue
, i.e.QueueIndex
.L1MessageQueue
.Other changes
scroll_traceBlockByNumberOrHash
to provers.l2geth
: Skipping & circuit capacity checkerWith Scroll's current zkEVM circuits, it is possible to construct transactions and blocks that cannot be proven because they "do not fit into the zkEVM".
We call this proof overflow.
To avoid this, we implemented a circuit capacity checker module as part of l2geth.
The circuit capacity checker is used both during block creation (worker.go
) and block validation (block_validation.go
).
During block creation, if the next transaction would lead to proof overflow, then we seal the block and leave the transaction for the next block.
If a single transaction leads to proof overflow, l2geth discards it.
We call this mechanism skipping.
Skipping L2 transactions means simply discarding them.
In this case, the user needs to submit another transaction with the same nonce that does not lead to proof overflow in order to proceed.
Skipping L1 messages is a more explicit process.
Both the ScrollChain
contract on L1 and the zkEVM verifies that each L1 transaction in L1MessageQueue
was either included and executed, or skipped.
While the batch encoding does not contain L1 transactions, it does include a skip bitmap that indicates to ScrollChain
which L1 messages were skipped.
If an L1 deposit message is skipped, the user can get their tokens refunded on L1.
Note: Proof overflow and thus skipping suggests malicious activity; for normal deposit transactions, this will never happen.
As the unit of zkEVM proving is the chunk, proof overflow must be avoided for chunks as well.
This is achieved by incorporating the circuit capacity checker into the rollup-relayer to make sure that it never proposes unprovable chunks.
rollup-relayer
: Chunk and batch constraintsCompared to l2geth
, the rollup-relayer
is a much more straightforward and simple component.
It collects L2 blocks, creates chunks and batches, and submits these to L1.
In this section we outline the main limits that rollup-relayer
must enforce during chunk and batch creation.
Constraints on chunks:
Constraints on batches:
rollup-relayer
includes a number of chunks that is optimal for the aggregation prover.As it stands, Scroll's current sequencer is centralized.
In this section, we consider 4 threats related to centralized sequencers: arbitrary state transitions, censorship risk, regulatory risk, liveness risk.
A centralized sequencer cannot execute arbitrary state transitions.
If, for example, the sequencer tried to move funds from a user's account to its own account, that would constitute an invalid state transition according to the EVM's rules.
While the sequencer could certainly create such a block, it could not be proven in the zkEVM and thus the funds could never be withdrawn to L1.
A centralized sequencer could choose to censor transactions from certain senders, rendering their funds on L2 frozen.
We added the enforced transaction mechanism to mitigate this risk.
Through this mechanism, users can submit transaction on L1 directly and the sequencer is forced to include such messages in its blocks.
Unfortunately, the problem of proof overflow forces us to allow the sequencer to skip such messages.
This kind of censorship is very explicit and easy to detect.
Scroll is working actively to eliminate the proof overflow problem through upgrades to our proving system.
Once proof overflow does not necessitate skipping anymore, the enforced transaction mechanism can fully solve censorship.
Since the centralized sequencer must be deployed in some specific jurisdiction, it is subject to regulatory risk.
Regulators can force Scroll to enforce KYC requirements, or potentially event take over the sequencer.
While this is certainly a possibility, it is possible in such scenarios to quickly migrate the sequencer to other jurisdictions.
The long-term solution to this risk is decentralizing the sequencer.
If the centralized sequencer is down for any reason, the L2 ledger cannot progress and users cannot withdraw their funds.
The long-term solution to this risk is decentralizing the sequencer.
Decentralizing the sequencer helps prevent both malicious acts from the sequencer, as well as Single-Point-of-Failure issues.
Scroll is actively working on upgrading the protocol to allow multiple sequencers.
We will share the progress with the community shortly.