Once we have batches of transactions, they are ready to be sequenced. The sequencer will call the layer 1 ZkEVM.sol contract’s sequencedBatches
function and provide it with multiple batches of transactions. It is basically a storage structure that holds the queue of sequences defining the virtual state.
// SequenceBatchNum --> SequencedBatchData
mapping(uint64 => SequencedBatchData) public sequencedBatches;
In order to get sequenced, batches need to be included in the array of batches. To do this, the trusted sequencer invokes ZkEVM.sol
contract’s sequencedBatches
and takes an array of batches as an argument. Please see the code snippet below:
function sequenceBatches (
BatchData[] memory batches
) public ifNotEmergencyState onlyTrustedSequencer
The figure below shows how a sequence of batches is organized:
With the validium mode, the contract has the following limits:
signaturesAndAddrs
byte array containing the signatures and all the addresses of the committee in ascending order * [signature 0, ..., signature requiredAmountOfSignatures -1, address 0, ... address N]transactionsHash
is the hash of transactions data, while the transactions
will be set nil.With the rollup mode, the contract has the following two public constants:
Only the trusted sequencer’s Ethereum account can access the sequencedBatches
mapping. It is also necessary that the contract not be in an emergency state.
The function call will be reverted if the above conditions are not met.
The sequencedBatches
function iterates over every batch of the sequence, checking its validity. A valid batch must meet the following criteria:
globalExitRoot
value that is present in the GlobalExitRootMap of the bridge’s layer 1 ZkEVMGlobalExitRoot.sol contract. A batch is valid only if it includes a valid globalExitRoot
.If one batch is not valid, the transaction reverts, meaning the whole sequence will be discarded. On the other hand, if all batches to be sequenced are valid, the sequencing process will continue.
A storage variable named lastBatchSequenced
is used to count and assign specific index numbers to each batch in the batch chain. This helps establish their positions in the batch chain.
To ensure the integrity of the batch chain, a similar hashing mechanism is employed as seen in blockchains, linking each batch to the one that follows. This includes the previous batch’s digest in the calculation of the next batch’s digest. Consequently, a batch’s digest is essentially an accumulated hash of all the previously sequenced batches, referred to as oldAccInputHash
for the previous and newAccInputHash
for the current one.
An accumulated hash of a specific batch has the following structure:
keccak256 (
abi.encodePacked (
bytes32 oldAccInputHash,
keccak256(bytes transactions),
bytes32 globalExitRoot ,
uint64 timestamp ,
address seqAddress
)
)
Where:
oldAccInputHash
is the accumulated hash of the previous sequenced batchkeccack256(transactions)
is the keccak digest of the transactions byte arrayglobalExitRoot
is the root of the bridge’s global exit Merkle treetimestamp
is the batch timestampseqAddress
is the address of the batch sequencerAs shown in the diagram above, each accumulated input hash ensures the integrity of the current batch’s data (i.e., transactions
, timestamp
, and globalExitRoot
, as well as the order in which they were sequenced.)
The batch sequence is added to the sequencedBatches
mapping using the following SequencedBatchData
struct only after the validity of all batches in a sequence has been verified and the accumulated hash of each batch has been computed.
struct SequencedBatchData {
bytes32 accInputHash;
uint64 sequencedTimestamp;
uint64 previousLastBatchSequenced;
}
Where:
accInputHash
is the batch’s unique cryptographic fingerprint of the last batch in the sequencesequencedTimestamp
is the timestamp of the block where the sequencing layer 1 transaction is executedpreviousLastBatchSequenced
is the index of the last sequenced batch before the first batch of the current sequence (i.e., the last batch of the previous sequence)The index number of the last batch in the sequence is used as key and the SequencedBatchData
struct is used as a value for when the sequence is entered into sequencedBatches
mapping.
To minimize costly storage operations on layer 1, storage slots are primarily employed for sequence commitments. Each slot records two batch indices:
SequencedBatchData
structBatch indices provide additional information about the number of batches in the sequence and their positions. The timestamp marks when the sequence occurred.
The Data Availability of layer 2 transactions is ensured because the data for each batch can be reconstructed from the calldata of the sequencing transaction, which is not part of the contract storage but is recorded in the layer 1 state.
Finally, a SequenceBatches
event will be emitted:
event SequenceBatches (uint64 indexed numBatch)
Once batches are successfully sequenced on layer 1, all ZKEVM nodes can sync their local layer 2 state directly from the layer 1 ZkEVM.sol
contract, reducing reliance on the trusted sequencer and reaching the layer 2 virtual state.