9. L2 Chain Derivation

Β 
πŸ’‘
One of the main responsibilities of the rollup node is to derive L2 blocks from L1 data.
Β 
We derive the L2 chain from the L1 chain. Each L1 block is mapped to an L2 sequencing epoch comprising multiple L2 blocks.
The epoch number is equal to the corresponding L1 block number.
The epoch number is equal to the corresponding L1 block number.
Β 
πŸ’‘
E = epoch number for a corresponding L2 sequencing epoch.
Required inputs to derive an epoch’s L2 blocks
  1. L1 sequencing window for epoch E .
  1. State of L2 chain after the last L2 block of epoch E-1.
Β 
L1 Sequencing Window (for epoch E)
πŸ’‘
Note: epochs are overlapping.
πŸ’‘
The sequencing window (SWS) refers to the L1 blocks in the range [E, E + SWS).
πŸ’‘
Each transaction batch maps to a single L2 block.
πŸ’‘
Because batcher transactions within a sequencing epoch require the hash of the L1 block E, the L1 block E (the first sequencer window block) cannot possibly contain any batcher transactions corresponding to epoch E.
πŸ’‘
We require: 1) Batcher transactions included in the sequencing window (we need these to reconstruct sequencer batches containing the transactions to include in L2 blocks. 2) Deposits made in L1 block E (in the form of events emitted by the deposit contract). 3) L1 block attributes from L1 block E (to derive the L1 attributes deposited transaction).
Β 
State of L2 chain (after final L2 block of epoch E-1)
πŸ’‘
If epoch E-1 does not exist, the L2 genesis state is used.
πŸ’‘
L2CI = L2 chain inception
πŸ’‘
An epoch E does not exist if E <= L2CI.
Β 
Β 
πŸ’‘
To derive the L2 chain from scratch:
  1. We start with the L2 genesis state and the L2 chain inception as the 1st epoch.
  1. We process all sequencing windows in order.
Β 
Β 
πŸ’‘
Each epoch may contain a variable number of L2 blocks (L2 blocks produced every 2s).
πŸ’‘
The L2 blocks cannot have a timestamp behind the L1 origin timestamp.
πŸ’‘
prev_l2_timestamp = timestamp of the final L2 block of the previous epoch
πŸ’‘
l2_block_time = configurable parameter of time between L2 blocks (currently 2s)
πŸ’‘
l1_timestamp = timestamp of the L1 block associated with the L2 block’s epoch.
πŸ’‘
max_sequencer_drift = the most that a sequencer is allowed to get ahead of L1.
Β 
We subject the following constraints on L2 blocks (min and max L2 timestamps):
πŸ’‘
min_l2_timestamp <= block.timestamp <= max_l2_timestamp
πŸ’‘
max_l2_timestamp
- L2 block timestamp must be within the timestamp of the epoch’s 1st L2 block and 2 seconds after. - L2 block timestamp cannot be beyond the upper sequencer drift threshold.
Β 
We thus have the following constraints in verbal form:
πŸ’‘
An L2 block must be produced every l2_block_time seconds.
πŸ’‘
The timestamp for the 1st L2 block of an epoch must never fall behind the timestamp of the L1 block matching the epoch.
Β 
Eager Block Derivation
πŸ’‘
You don’t have to wait for a full sequencing window of L1 blocks to start deriving L2 blocks in an epoch.
πŸ’‘
Eager Block Derivation describes immediately reconstructing sequencer batches as soon as they become available instead of waiting for the epoch to end.
πŸ’‘
Worst case scenario β†’ we are not able to derive the first L2 block of an epoch hence we cannot derive any further L2 blocks in the epoch. We can still queue batches, but we won’t be able to create blocks from them
Β 
Batch Submission
Β 
notion image
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Frame Format
A channel frame is encoded as:
frame = channel_id ++ frame_number ++ frame_data_length ++ frame_data ++ is_last

channel_id        = bytes16 (big-endian) // 16 bytes (part of fixed overhead)
frame_number      = uint16 (big-endian)  // 2 bytes (part of fixed overhead)
frame_data_length = uint32 (big-endian)  // 4 bytes (part of fixed overhead)
πŸ’‘
Fixed overhead size = 23 bytes (metadata)
πŸ’‘
frame_data has a maximum size limit of 1,000,000 bytes (1 MB). Hence the max value that frame_data_length expressed in bytes is 0x03D09000 which is the bytes big endian format for the integer 1 million.
Β 
Channel Format
πŸ’‘
Channels are encoded as channel_encoding defined below:
rlp_batches = []
for batch in batches:
    rlp_batches.append(batch)
channel_encoding = compress(rlp_batches)
πŸ’‘
batches = the input (a sequence of batches byte-encoded).
πŸ’‘
rlp_batches = concatenation of the RLP-encoded batches.
πŸ’‘
compress = function performing compression (using zlib algorithm).
πŸ’‘
channel_encoding = compressed version of rlp_batches.
Β 
L2 Chain Derivation
πŸ’‘
The derivation process is divided into a pipeline of the following stages: 1. L1 Traversal 2. L1 Retrieval 3. Frame Queue 4. Channel Bank 5. Channel Reader (Batch Decoding) 6. Batch Queue 7. Payload Attributes Derivation 8. Engine Queue
Β 

9. L2 Chain Derivation

Β 
πŸ’‘
One of the main responsibilities of the rollup node is to derive L2 blocks from L1 data.
Β 
We derive the L2 chain from the L1 chain. Each L1 block is mapped to an L2 sequencing epoch comprising multiple L2 blocks.
The epoch number is equal to the corresponding L1 block number.
The epoch number is equal to the corresponding L1 block number.
Β 
πŸ’‘
E = epoch number for a corresponding L2 sequencing epoch.
Required inputs to derive an epoch’s L2 blocks
  1. L1 sequencing window for epoch E .
  1. State of L2 chain after the last L2 block of epoch E-1.
Β 
L1 Sequencing Window (for epoch E)
πŸ’‘
Note: epochs are overlapping.
πŸ’‘
The sequencing window (SWS) refers to the L1 blocks in the range [E, E + SWS).
πŸ’‘
Each transaction batch maps to a single L2 block.
πŸ’‘
Because batcher transactions within a sequencing epoch require the hash of the L1 block E, the L1 block E (the first sequencer window block) cannot possibly contain any batcher transactions corresponding to epoch E.
πŸ’‘
We require: 1) Batcher transactions included in the sequencing window (we need these to reconstruct sequencer batches containing the transactions to include in L2 blocks. 2) Deposits made in L1 block E (in the form of events emitted by the deposit contract). 3) L1 block attributes from L1 block E (to derive the L1 attributes deposited transaction).
Β 
State of L2 chain (after final L2 block of epoch E-1)
πŸ’‘
If epoch E-1 does not exist, the L2 genesis state is used.
πŸ’‘
L2CI = L2 chain inception
πŸ’‘
An epoch E does not exist if E <= L2CI.
Β 
Β 
πŸ’‘
To derive the L2 chain from scratch:
  1. We start with the L2 genesis state and the L2 chain inception as the 1st epoch.
  1. We process all sequencing windows in order.
Β 
Β 
πŸ’‘
Each epoch may contain a variable number of L2 blocks (L2 blocks produced every 2s).
πŸ’‘
The L2 blocks cannot have a timestamp behind the L1 origin timestamp.
πŸ’‘
prev_l2_timestamp = timestamp of the final L2 block of the previous epoch
πŸ’‘
l2_block_time = configurable parameter of time between L2 blocks (currently 2s)
πŸ’‘
l1_timestamp = timestamp of the L1 block associated with the L2 block’s epoch.
πŸ’‘
max_sequencer_drift = the most that a sequencer is allowed to get ahead of L1.
Β 
We subject the following constraints on L2 blocks (min and max L2 timestamps):
πŸ’‘
min_l2_timestamp <= block.timestamp <= max_l2_timestamp
πŸ’‘
max_l2_timestamp
- L2 block timestamp must be within the timestamp of the epoch’s 1st L2 block and 2 seconds after. - L2 block timestamp cannot be beyond the upper sequencer drift threshold.
Β 
We thus have the following constraints in verbal form:
πŸ’‘
An L2 block must be produced every l2_block_time seconds.
πŸ’‘
The timestamp for the 1st L2 block of an epoch must never fall behind the timestamp of the L1 block matching the epoch.
Β 
Eager Block Derivation
πŸ’‘
You don’t have to wait for a full sequencing window of L1 blocks to start deriving L2 blocks in an epoch.
πŸ’‘
Eager Block Derivation describes immediately reconstructing sequencer batches as soon as they become available instead of waiting for the epoch to end.
πŸ’‘
Worst case scenario β†’ we are not able to derive the first L2 block of an epoch hence we cannot derive any further L2 blocks in the epoch. We can still queue batches, but we won’t be able to create blocks from them
Β 
Batch Submission
Β 
notion image
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Frame Format
A channel frame is encoded as:
frame = channel_id ++ frame_number ++ frame_data_length ++ frame_data ++ is_last

channel_id        = bytes16 (big-endian) // 16 bytes (part of fixed overhead)
frame_number      = uint16 (big-endian)  // 2 bytes (part of fixed overhead)
frame_data_length = uint32 (big-endian)  // 4 bytes (part of fixed overhead)
πŸ’‘
Fixed overhead size = 23 bytes (metadata)
πŸ’‘
frame_data has a maximum size limit of 1,000,000 bytes (1 MB). Hence the max value that frame_data_length expressed in bytes is 0x03D09000 which is the bytes big endian format for the integer 1 million.
Β 
Channel Format
πŸ’‘
Channels are encoded as channel_encoding defined below:
rlp_batches = []
for batch in batches:
    rlp_batches.append(batch)
channel_encoding = compress(rlp_batches)
πŸ’‘
batches = the input (a sequence of batches byte-encoded).
πŸ’‘
rlp_batches = concatenation of the RLP-encoded batches.
πŸ’‘
compress = function performing compression (using zlib algorithm).
πŸ’‘
channel_encoding = compressed version of rlp_batches.
Β 
L2 Chain Derivation
πŸ’‘
The derivation process is divided into a pipeline of the following stages: 1. L1 Traversal 2. L1 Retrieval 3. Frame Queue 4. Channel Bank 5. Channel Reader (Batch Decoding) 6. Batch Queue 7. Payload Attributes Derivation 8. Engine Queue
Β