kona_protocol/batch/
span.rs

1//! Span Batch implementation for efficient multi-block L2 transaction batching.
2//!
3//! Span batches are an advanced batching format that can contain transactions for multiple
4//! L2 blocks in a single compressed structure. This provides significant efficiency gains
5//! over single batches by amortizing overhead across multiple blocks and enabling
6//! sophisticated compression techniques.
7
8use alloc::vec::Vec;
9use alloy_eips::eip2718::Encodable2718;
10use alloy_primitives::FixedBytes;
11use kona_genesis::RollupConfig;
12use op_alloy_consensus::OpTxType;
13use tracing::{info, warn};
14
15use crate::{
16    BatchValidationProvider, BatchValidity, BlockInfo, L2BlockInfo, RawSpanBatch, SingleBatch,
17    SpanBatchBits, SpanBatchElement, SpanBatchError, SpanBatchPayload, SpanBatchPrefix,
18    SpanBatchTransactions,
19};
20
21/// Container for the inputs required to build a span of L2 blocks in derived form.
22///
23/// A [`SpanBatch`] represents a compressed format for multiple L2 blocks that enables
24/// significant space savings compared to individual single batches. The format uses
25/// differential encoding, bit packing, and shared data structures to minimize the
26/// L1 footprint while maintaining all necessary information for L2 block reconstruction.
27///
28/// # Compression Techniques
29///
30/// ## Temporal Compression
31/// - **Relative timestamps**: Store timestamps relative to genesis to reduce size
32/// - **Differential encoding**: Encode changes between consecutive blocks
33/// - **Epoch sharing**: Multiple blocks can share the same L1 origin
34///
35/// ## Spatial Compression
36/// - **Shared prefixes**: Common data shared across all blocks in span
37/// - **Transaction batching**: Transactions grouped and compressed together
38/// - **Bit packing**: Use minimal bits for frequently-used fields
39///
40/// # Format Structure
41///
42/// ```text
43/// SpanBatch {
44///   prefix: {
45///     rel_timestamp,     // Relative to genesis
46///     l1_origin_num,     // Final L1 block number  
47///     parent_check,      // First 20 bytes of parent hash
48///     l1_origin_check,   // First 20 bytes of L1 origin hash
49///   },
50///   payload: {
51///     block_count,       // Number of blocks in span
52///     origin_bits,       // Bit array indicating L1 origin changes
53///     block_tx_counts,   // Transaction count per block
54///     txs,              // Compressed transaction data
55///   }
56/// }
57/// ```
58///
59/// # Validation and Integrity
60///
61/// The span batch format includes several integrity checks:
62/// - **Parent check**: Validates continuity with previous span
63/// - **L1 origin check**: Ensures proper L1 origin binding
64/// - **Transaction count validation**: Verifies transaction distribution
65/// - **Bit field consistency**: Ensures origin bits match block count
66#[derive(Debug, Default, Clone, PartialEq, Eq)]
67pub struct SpanBatch {
68    /// First 20 bytes of the parent hash of the first block in the span.
69    ///
70    /// This field provides a collision-resistant check to ensure the span batch
71    /// builds properly on the expected parent block. Using only 20 bytes saves
72    /// space while maintaining strong integrity guarantees.
73    pub parent_check: FixedBytes<20>,
74    /// First 20 bytes of the L1 origin hash of the last block in the span.
75    ///
76    /// This field enables validation that the span batch references the correct
77    /// L1 origin block, ensuring proper derivation ordering and preventing
78    /// replay attacks across different L1 contexts.
79    pub l1_origin_check: FixedBytes<20>,
80    /// Genesis block timestamp for relative timestamp calculations.
81    ///
82    /// All timestamps in the span batch are stored relative to this genesis
83    /// timestamp to minimize storage requirements. This enables efficient
84    /// timestamp compression while maintaining full precision.
85    pub genesis_timestamp: u64,
86    /// Chain ID for transaction validation and network identification.
87    ///
88    /// Required for proper transaction signature validation and to prevent
89    /// cross-chain replay attacks. All transactions in the span must be
90    /// valid for this chain ID.
91    pub chain_id: u64,
92    /// Ordered list of block elements contained in this span.
93    ///
94    /// Each element represents the derived data for one L2 block, including
95    /// timestamp, epoch information, and transaction references. The order
96    /// must match the intended L2 block sequence.
97    pub batches: Vec<SpanBatchElement>,
98    /// Cached bit array indicating L1 origin changes between consecutive blocks.
99    ///
100    /// This compressed representation allows efficient encoding of which blocks
101    /// in the span advance to a new L1 origin. Bit `i` is set if block `i+1`
102    /// has a different L1 origin than block `i`.
103    pub origin_bits: SpanBatchBits,
104    /// Cached transaction count for each block in the span.
105    ///
106    /// Pre-computed transaction counts enable efficient random access to
107    /// transactions for specific blocks without scanning the entire transaction
108    /// list. Index `i` contains the transaction count for block `i`.
109    pub block_tx_counts: Vec<u64>,
110    /// Cached compressed transaction data for all blocks in the span.
111    ///
112    /// Contains all transactions from all blocks in a compressed format that
113    /// enables efficient encoding and decoding. Transactions are grouped and
114    /// compressed using span-specific techniques.
115    pub txs: SpanBatchTransactions,
116}
117
118impl SpanBatch {
119    /// Returns the starting timestamp for the first batch in the span.
120    ///
121    /// This is the absolute timestamp (not relative to genesis) of the first
122    /// block in the span batch. Used for validation and block sequencing.
123    ///
124    /// # Panics
125    /// Panics if the span batch contains no elements (`batches` is empty).
126    /// This should never happen in valid span batches as they must contain
127    /// at least one block.
128    ///
129    /// # Usage
130    /// Typically used during span batch validation to ensure proper temporal
131    /// ordering with respect to the parent block and L1 derivation window.
132    pub fn starting_timestamp(&self) -> u64 {
133        self.batches[0].timestamp
134    }
135
136    /// Returns the final timestamp for the last batch in the span.
137    ///
138    /// This is the absolute timestamp (not relative to genesis) of the last
139    /// block in the span batch. Used for validation and determining the
140    /// span's temporal range.
141    ///
142    /// # Panics
143    /// Panics if the span batch contains no elements (`batches` is empty).
144    /// This should never happen in valid span batches as they must contain
145    /// at least one block.
146    ///
147    /// # Usage
148    /// Used during validation to ensure the span doesn't exceed maximum
149    /// temporal ranges and fits within L1 derivation windows.
150    pub fn final_timestamp(&self) -> u64 {
151        self.batches[self.batches.len() - 1].timestamp
152    }
153
154    /// Returns the L1 epoch number for the first batch in the span.
155    ///
156    /// The epoch number corresponds to the L1 block number that serves as
157    /// the L1 origin for the first L2 block in this span. This establishes
158    /// the L1 derivation context for the span.
159    ///
160    /// # Panics
161    /// Panics if the span batch contains no elements (`batches` is empty).
162    /// This should never happen in valid span batches as they must contain
163    /// at least one block.
164    ///
165    /// # Usage
166    /// Used during validation to ensure proper L1 origin sequencing and
167    /// that the span begins with the expected L1 context.
168    pub fn starting_epoch_num(&self) -> u64 {
169        self.batches[0].epoch_num
170    }
171
172    /// Validates that the L1 origin hash matches the span's L1 origin check.
173    ///
174    /// Compares the first 20 bytes of the provided hash against the stored
175    /// `l1_origin_check` field. This provides a collision-resistant validation
176    /// that the span batch was derived from the expected L1 context.
177    ///
178    /// # Arguments
179    /// * `hash` - The full 32-byte L1 origin hash to validate
180    ///
181    /// # Returns
182    /// * `true` - If the first 20 bytes match the span's L1 origin check
183    /// * `false` - If there's a mismatch, indicating invalid L1 context
184    ///
185    /// # Algorithm
186    /// ```text
187    /// l1_origin_check[0..20] == hash[0..20]
188    /// ```
189    ///
190    /// Using only 20 bytes provides strong collision resistance (2^160 space)
191    /// while saving 12 bytes per span compared to storing full hashes.
192    pub fn check_origin_hash(&self, hash: FixedBytes<32>) -> bool {
193        self.l1_origin_check == hash[..20]
194    }
195
196    /// Validates that the parent hash matches the span's parent check.
197    ///
198    /// Compares the first 20 bytes of the provided hash against the stored
199    /// `parent_check` field. This ensures the span batch builds on the
200    /// expected parent block, maintaining chain continuity.
201    ///
202    /// # Arguments
203    /// * `hash` - The full 32-byte parent hash to validate
204    ///
205    /// # Returns
206    /// * `true` - If the first 20 bytes match the span's parent check
207    /// * `false` - If there's a mismatch, indicating discontinuity
208    ///
209    /// # Algorithm
210    /// ```text
211    /// parent_check[0..20] == hash[0..20]
212    /// ```
213    ///
214    /// This validation is critical for maintaining the integrity of the L2
215    /// chain and preventing insertion of span batches in wrong locations.
216    pub fn check_parent_hash(&self, hash: FixedBytes<32>) -> bool {
217        self.parent_check == hash[..20]
218    }
219
220    /// Accesses the nth element from the end of the batch list.
221    ///
222    /// This is a convenience method for accessing recent elements in the span,
223    /// typically used during validation or processing algorithms that need to
224    /// examine the latest elements in the sequence.
225    ///
226    /// # Arguments
227    /// * `n` - Offset from the end (0 = last element, 1 = second-to-last, etc.)
228    ///
229    /// # Returns
230    /// Reference to the nth element from the end of the batch list
231    ///
232    /// # Panics
233    /// Panics if `n >= batches.len()`, i.e., if trying to access beyond
234    /// the available elements.
235    ///
236    /// # Algorithm
237    /// ```text
238    /// index = batches.len() - 1 - n
239    /// return &batches[index]
240    /// ```
241    fn peek(&self, n: usize) -> &SpanBatchElement {
242        &self.batches[self.batches.len() - 1 - n]
243    }
244
245    /// Converts this span batch to its raw serializable format.
246    ///
247    /// Transforms the derived span batch into a [`RawSpanBatch`] that can be
248    /// serialized and transmitted over the network. This involves organizing
249    /// the cached data into the proper prefix and payload structure.
250    ///
251    /// # Returns
252    /// * `Ok(RawSpanBatch)` - Successfully converted raw span batch
253    /// * `Err(SpanBatchError)` - Conversion failed, typically due to empty batch
254    ///
255    /// # Errors
256    /// Returns [`SpanBatchError::EmptySpanBatch`] if the span contains no blocks,
257    /// which is invalid as span batches must contain at least one block.
258    ///
259    /// # Algorithm
260    /// The conversion process:
261    /// 1. **Validation**: Ensure the span is not empty
262    /// 2. **Prefix Construction**: Build prefix with temporal and origin data
263    /// 3. **Payload Assembly**: Package cached data into payload structure
264    /// 4. **Relative Timestamp Calculation**: Convert absolute to relative timestamp
265    ///
266    /// The relative timestamp is calculated as:
267    /// ```text
268    /// rel_timestamp = first_block_timestamp - genesis_timestamp
269    /// ```
270    ///
271    /// This enables efficient timestamp encoding in the serialized format.
272    pub fn to_raw_span_batch(&self) -> Result<RawSpanBatch, SpanBatchError> {
273        if self.batches.is_empty() {
274            return Err(SpanBatchError::EmptySpanBatch);
275        }
276
277        // These should never error since we check for an empty batch above.
278        let span_start = self.batches.first().ok_or(SpanBatchError::EmptySpanBatch)?;
279        let span_end = self.batches.last().ok_or(SpanBatchError::EmptySpanBatch)?;
280
281        Ok(RawSpanBatch {
282            prefix: SpanBatchPrefix {
283                rel_timestamp: span_start.timestamp - self.genesis_timestamp,
284                l1_origin_num: span_end.epoch_num,
285                parent_check: self.parent_check,
286                l1_origin_check: self.l1_origin_check,
287            },
288            payload: SpanBatchPayload {
289                block_count: self.batches.len() as u64,
290                origin_bits: self.origin_bits.clone(),
291                block_tx_counts: self.block_tx_counts.clone(),
292                txs: self.txs.clone(),
293            },
294        })
295    }
296
297    /// Converts all [`SpanBatchElement`]s after the L2 safe head to [`SingleBatch`]es. The
298    /// resulting [`SingleBatch`]es do not contain a parent hash, as it is populated by the
299    /// Batch Queue stage.
300    pub fn get_singular_batches(
301        &self,
302        l1_origins: &[BlockInfo],
303        l2_safe_head: L2BlockInfo,
304    ) -> Result<Vec<SingleBatch>, SpanBatchError> {
305        let mut single_batches = Vec::new();
306        let mut origin_index = 0;
307        for batch in &self.batches {
308            if batch.timestamp <= l2_safe_head.block_info.timestamp {
309                continue;
310            }
311            let origin_epoch_hash = l1_origins[origin_index..l1_origins.len()]
312                .iter()
313                .enumerate()
314                .find(|(_, origin)| origin.number == batch.epoch_num)
315                .map(|(i, origin)| {
316                    origin_index = i;
317                    origin.hash
318                })
319                .ok_or(SpanBatchError::MissingL1Origin)?;
320            let single_batch = SingleBatch {
321                epoch_num: batch.epoch_num,
322                epoch_hash: origin_epoch_hash,
323                timestamp: batch.timestamp,
324                transactions: batch.transactions.clone(),
325                ..Default::default()
326            };
327            single_batches.push(single_batch);
328        }
329        Ok(single_batches)
330    }
331
332    /// Append a [`SingleBatch`] to the [`SpanBatch`]. Updates the L1 origin check if need be.
333    pub fn append_singular_batch(
334        &mut self,
335        singular_batch: SingleBatch,
336        seq_num: u64,
337    ) -> Result<(), SpanBatchError> {
338        // If the new element is not ordered with respect to the last element, panic.
339        if !self.batches.is_empty() && self.peek(0).timestamp > singular_batch.timestamp {
340            panic!("Batch is not ordered");
341        }
342
343        let SingleBatch { epoch_hash, parent_hash, .. } = singular_batch;
344
345        // Always append the new batch and set the L1 origin check.
346        self.batches.push(singular_batch.into());
347        // Always update the L1 origin check.
348        self.l1_origin_check = epoch_hash[..20].try_into().expect("Sub-slice cannot fail");
349
350        let epoch_bit = if self.batches.len() == 1 {
351            // If there is only one batch, initialize the parent check and set the epoch bit based
352            // on the sequence number.
353            self.parent_check = parent_hash[..20].try_into().expect("Sub-slice cannot fail");
354            seq_num == 0
355        } else {
356            // If there is more than one batch, set the epoch bit based on the last two batches.
357            self.peek(1).epoch_num < self.peek(0).epoch_num
358        };
359
360        // Set the respective bit in the origin bits.
361        self.origin_bits.set_bit(self.batches.len() - 1, epoch_bit);
362
363        let new_txs = self.peek(0).transactions.clone();
364
365        // Update the block tx counts cache with the latest batch's transaction count.
366        self.block_tx_counts.push(new_txs.len() as u64);
367
368        // Add the new transactions to the transaction cache.
369        self.txs.add_txs(new_txs, self.chain_id)
370    }
371
372    /// Checks if the span batch is valid.
373    pub async fn check_batch<BV: BatchValidationProvider>(
374        &self,
375        cfg: &RollupConfig,
376        l1_blocks: &[BlockInfo],
377        l2_safe_head: L2BlockInfo,
378        inclusion_block: &BlockInfo,
379        fetcher: &mut BV,
380    ) -> BatchValidity {
381        let (prefix_validity, parent_block) =
382            self.check_batch_prefix(cfg, l1_blocks, l2_safe_head, inclusion_block, fetcher).await;
383        if !matches!(prefix_validity, BatchValidity::Accept) {
384            return prefix_validity;
385        }
386
387        let starting_epoch_num = self.starting_epoch_num();
388        let parent_block = parent_block.expect("parent_block must be Some");
389
390        let mut origin_index = 0;
391        let mut origin_advanced = starting_epoch_num == parent_block.l1_origin.number + 1;
392        for (i, batch) in self.batches.iter().enumerate() {
393            if batch.timestamp <= l2_safe_head.block_info.timestamp {
394                continue;
395            }
396            // Find the L1 origin for the batch.
397            for (j, j_block) in l1_blocks.iter().enumerate().skip(origin_index) {
398                if batch.epoch_num == j_block.number {
399                    origin_index = j;
400                    break;
401                }
402            }
403            let l1_origin = l1_blocks[origin_index];
404            if i > 0 {
405                origin_advanced = false;
406                if batch.epoch_num > self.batches[i - 1].epoch_num {
407                    origin_advanced = true;
408                }
409            }
410            let block_timestamp = batch.timestamp;
411            if block_timestamp < l1_origin.timestamp {
412                warn!(
413                    target: "batch_span",
414                    "block timestamp is less than L1 origin timestamp, l2_timestamp: {}, l1_timestamp: {}, origin: {:?}",
415                    block_timestamp,
416                    l1_origin.timestamp,
417                    l1_origin.id()
418                );
419                return BatchValidity::Drop;
420            }
421
422            // Check if we ran out of sequencer time drift
423            let max_drift = cfg.max_sequencer_drift(l1_origin.timestamp);
424            if block_timestamp > l1_origin.timestamp + max_drift {
425                if batch.transactions.is_empty() {
426                    // If the sequencer is co-operating by producing an empty batch,
427                    // then allow the batch if it was the right thing to do to maintain the L2 time
428                    // >= L1 time invariant. We only check batches that do not
429                    // advance the epoch, to ensure epoch advancement regardless of time drift is
430                    // allowed.
431                    if !origin_advanced {
432                        if origin_index + 1 >= l1_blocks.len() {
433                            info!(
434                                target: "batch_span",
435                                "without the next L1 origin we cannot determine yet if this empty batch that exceeds the time drift is still valid"
436                            );
437                            return BatchValidity::Undecided;
438                        }
439                        if block_timestamp >= l1_blocks[origin_index + 1].timestamp {
440                            // check if the next L1 origin could have been adopted
441                            info!(
442                                target: "batch_span",
443                                "batch exceeded sequencer time drift without adopting next origin, and next L1 origin would have been valid"
444                            );
445                            return BatchValidity::Drop;
446                        } else {
447                            info!(
448                                target: "batch_span",
449                                "continuing with empty batch before late L1 block to preserve L2 time invariant"
450                            );
451                        }
452                    }
453                } else {
454                    // If the sequencer is ignoring the time drift rule, then drop the batch and
455                    // force an empty batch instead, as the sequencer is not
456                    // allowed to include anything past this point without moving to the next epoch.
457                    warn!(
458                        target: "batch_span",
459                        "batch exceeded sequencer time drift, sequencer must adopt new L1 origin to include transactions again, max_time: {}",
460                        l1_origin.timestamp + max_drift
461                    );
462                    return BatchValidity::Drop;
463                }
464            }
465
466            // Check that the transactions are not empty and do not contain any deposits.
467            for (i, tx) in batch.transactions.iter().enumerate() {
468                if tx.is_empty() {
469                    warn!(
470                        target: "batch_span",
471                        "transaction data must not be empty, but found empty tx, tx_index: {}",
472                        i
473                    );
474                    return BatchValidity::Drop;
475                }
476                if tx.as_ref().first() == Some(&(OpTxType::Deposit as u8)) {
477                    warn!(
478                        target: "batch_span",
479                        "sequencers may not embed any deposits into batch data, but found tx that has one, tx_index: {}",
480                        i
481                    );
482                    return BatchValidity::Drop;
483                }
484
485                // If isthmus is not active yet and the transaction is a 7702, drop the batch.
486                if !cfg.is_isthmus_active(batch.timestamp) &&
487                    tx.as_ref().first() == Some(&(OpTxType::Eip7702 as u8))
488                {
489                    warn!(target: "batch_span", "EIP-7702 transactions are not supported pre-isthmus. tx_index: {}", i);
490                    return BatchValidity::Drop;
491                }
492            }
493        }
494
495        // Check overlapped blocks
496        let parent_num = parent_block.block_info.number;
497        let next_timestamp = l2_safe_head.block_info.timestamp + cfg.block_time;
498        if self.starting_timestamp() < next_timestamp {
499            for i in 0..(l2_safe_head.block_info.number - parent_num) {
500                let safe_block_num = parent_num + i + 1;
501                let safe_block_payload = match fetcher.block_by_number(safe_block_num).await {
502                    Ok(p) => p,
503                    Err(e) => {
504                        warn!(target: "batch_span", "failed to fetch block number {safe_block_num}: {e}");
505                        return BatchValidity::Undecided;
506                    }
507                };
508                let safe_block = &safe_block_payload.body;
509                let batch_txs = &self.batches[i as usize].transactions;
510                // Execution payload has deposit txs but batch does not.
511                let deposit_count: usize = safe_block
512                    .transactions
513                    .iter()
514                    .map(|tx| if tx.is_deposit() { 1 } else { 0 })
515                    .sum();
516                if safe_block.transactions.len() - deposit_count != batch_txs.len() {
517                    warn!(
518                        target: "batch_span",
519                        "overlapped block's tx count does not match, safe_block_txs: {}, batch_txs: {}",
520                        safe_block.transactions.len(),
521                        batch_txs.len()
522                    );
523                    return BatchValidity::Drop;
524                }
525                let batch_txs_len = batch_txs.len();
526                #[allow(clippy::needless_range_loop)]
527                for j in 0..batch_txs_len {
528                    let mut buf = Vec::new();
529                    safe_block.transactions[j + deposit_count].encode_2718(&mut buf);
530                    if buf != batch_txs[j].0 {
531                        warn!(target: "batch_span", "overlapped block's transaction does not match");
532                        return BatchValidity::Drop;
533                    }
534                }
535                let safe_block_ref = match L2BlockInfo::from_block_and_genesis(
536                    &safe_block_payload,
537                    &cfg.genesis,
538                ) {
539                    Ok(r) => r,
540                    Err(e) => {
541                        warn!(
542                            target: "batch_span",
543                            "failed to extract L2BlockInfo from execution payload, hash: {}, err: {e}",
544                            safe_block_payload.header.hash_slow()
545                        );
546                        return BatchValidity::Drop;
547                    }
548                };
549                if safe_block_ref.l1_origin.number != self.batches[i as usize].epoch_num {
550                    warn!(
551                        "overlapped block's L1 origin number does not match {}, {}",
552                        safe_block_ref.l1_origin.number, self.batches[i as usize].epoch_num
553                    );
554                    return BatchValidity::Drop;
555                }
556            }
557        }
558
559        BatchValidity::Accept
560    }
561
562    /// Checks the validity of the batch's prefix.
563    ///
564    /// This function is used for post-Holocene hardfork to perform batch validation
565    /// as each batch is being loaded in.
566    pub async fn check_batch_prefix<BF: BatchValidationProvider>(
567        &self,
568        cfg: &RollupConfig,
569        l1_origins: &[BlockInfo],
570        l2_safe_head: L2BlockInfo,
571        inclusion_block: &BlockInfo,
572        fetcher: &mut BF,
573    ) -> (BatchValidity, Option<L2BlockInfo>) {
574        if l1_origins.is_empty() {
575            warn!(target: "batch_span", "missing L1 block input, cannot proceed with batch checking");
576            return (BatchValidity::Undecided, None);
577        }
578        if self.batches.is_empty() {
579            warn!(target: "batch_span", "empty span batch, cannot proceed with batch checking");
580            return (BatchValidity::Undecided, None);
581        }
582
583        let epoch = l1_origins[0];
584        let next_timestamp = l2_safe_head.block_info.timestamp + cfg.block_time;
585
586        let starting_epoch_num = self.starting_epoch_num();
587        let mut batch_origin = epoch;
588        if starting_epoch_num == batch_origin.number + 1 {
589            if l1_origins.len() < 2 {
590                info!(
591                    target: "batch_span",
592                    "eager batch wants to advance current epoch {:?}, but could not without more L1 blocks",
593                    epoch.id()
594                );
595                return (BatchValidity::Undecided, None);
596            }
597            batch_origin = l1_origins[1];
598        }
599        if !cfg.is_delta_active(batch_origin.timestamp) {
600            warn!(
601                target: "batch_span",
602                "received SpanBatch (id {:?}) with L1 origin (timestamp {}) before Delta hard fork",
603                batch_origin.id(),
604                batch_origin.timestamp
605            );
606            return (BatchValidity::Drop, None);
607        }
608
609        if self.starting_timestamp() > next_timestamp {
610            warn!(
611                target: "batch_span",
612                "received out-of-order batch for future processing after next batch ({} > {})",
613                self.starting_timestamp(),
614                next_timestamp
615            );
616
617            // After holocene is activated, gaps are disallowed.
618            if cfg.is_holocene_active(inclusion_block.timestamp) {
619                return (BatchValidity::Drop, None);
620            }
621            return (BatchValidity::Future, None);
622        }
623
624        // Drop the batch if it has no new blocks after the safe head.
625        if self.final_timestamp() < next_timestamp {
626            warn!(target: "batch_span", "span batch has no new blocks after safe head");
627            return if cfg.is_holocene_active(inclusion_block.timestamp) {
628                (BatchValidity::Past, None)
629            } else {
630                (BatchValidity::Drop, None)
631            };
632        }
633
634        // Find the parent block of the span batch.
635        // If the span batch does not overlap the current safe chain, parent block should be the L2
636        // safe head.
637        let mut parent_num = l2_safe_head.block_info.number;
638        let mut parent_block = l2_safe_head;
639        if self.starting_timestamp() < next_timestamp {
640            if self.starting_timestamp() > l2_safe_head.block_info.timestamp {
641                // Batch timestamp cannot be between safe head and next timestamp.
642                warn!(target: "batch_span", "batch has misaligned timestamp, block time is too short");
643                return (BatchValidity::Drop, None);
644            }
645            if (l2_safe_head.block_info.timestamp - self.starting_timestamp()) % cfg.block_time != 0
646            {
647                warn!(target: "batch_span", "batch has misaligned timestamp, not overlapped exactly");
648                return (BatchValidity::Drop, None);
649            }
650            parent_num = l2_safe_head.block_info.number -
651                (l2_safe_head.block_info.timestamp - self.starting_timestamp()) / cfg.block_time -
652                1;
653            parent_block = match fetcher.l2_block_info_by_number(parent_num).await {
654                Ok(block) => block,
655                Err(e) => {
656                    warn!(target: "batch_span", "failed to fetch L2 block number {parent_num}: {e}");
657                    // Unable to validate the batch for now. Retry later.
658                    return (BatchValidity::Undecided, None);
659                }
660            };
661        }
662        if !self.check_parent_hash(parent_block.block_info.hash) {
663            warn!(
664                target: "batch_span",
665                "parent block mismatch, expected: {parent_num}, received: {}. parent hash: {}, parent hash check: {}",
666                parent_block.block_info.number, parent_block.block_info.hash, self.parent_check,
667            );
668            return (BatchValidity::Drop, None);
669        }
670
671        // Filter out batches that were included too late.
672        if starting_epoch_num + cfg.seq_window_size < inclusion_block.number {
673            warn!(target: "batch_span", "batch was included too late, sequence window expired");
674            return (BatchValidity::Drop, None);
675        }
676
677        // Check the L1 origin of the batch
678        if starting_epoch_num > parent_block.l1_origin.number + 1 {
679            warn!(
680                target: "batch_span",
681                "batch is for future epoch too far ahead, while it has the next timestamp, so it must be invalid. starting epoch: {} | next epoch: {}",
682                starting_epoch_num,
683                parent_block.l1_origin.number + 1
684            );
685            return (BatchValidity::Drop, None);
686        }
687
688        // Verify the l1 origin hash for each l1 block.
689        // SAFETY: `Self::batches` is not empty, so the last element is guaranteed to exist.
690        let end_epoch_num = self.batches.last().unwrap().epoch_num;
691        let mut origin_checked = false;
692        // l1Blocks is supplied from batch queue and its length is limited to SequencerWindowSize.
693        for l1_block in l1_origins {
694            if l1_block.number == end_epoch_num {
695                if !self.check_origin_hash(l1_block.hash) {
696                    warn!(
697                        target: "batch_span",
698                        "batch is for different L1 chain, epoch hash does not match, expected: {}",
699                        l1_block.hash
700                    );
701                    return (BatchValidity::Drop, None);
702                }
703                origin_checked = true;
704                break;
705            }
706        }
707        if !origin_checked {
708            info!(target: "batch_span", "need more l1 blocks to check entire origins of span batch");
709            return (BatchValidity::Undecided, None);
710        }
711
712        if starting_epoch_num < parent_block.l1_origin.number {
713            warn!(target: "batch_span", "dropped batch, epoch is too old, minimum: {:?}", parent_block.block_info.id());
714            return (BatchValidity::Drop, None);
715        }
716
717        (BatchValidity::Accept, Some(parent_block))
718    }
719}
720
721#[cfg(test)]
722mod tests {
723    use super::*;
724    use crate::test_utils::{CollectingLayer, TestBatchValidator, TraceStorage};
725    use alloc::vec;
726    use alloy_consensus::{Header, constants::EIP1559_TX_TYPE_ID};
727    use alloy_eips::BlockNumHash;
728    use alloy_primitives::{Bytes, b256};
729    use kona_genesis::{ChainGenesis, HardForkConfig};
730    use op_alloy_consensus::OpBlock;
731    use tracing::Level;
732    use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
733
734    #[test]
735    fn test_timestamp() {
736        let timestamp = 10;
737        let first_element = SpanBatchElement { timestamp, ..Default::default() };
738        let batch =
739            SpanBatch { batches: vec![first_element, Default::default()], ..Default::default() };
740        assert_eq!(batch.starting_timestamp(), timestamp);
741    }
742
743    #[test]
744    fn test_starting_timestamp() {
745        let timestamp = 10;
746        let first_element = SpanBatchElement { timestamp, ..Default::default() };
747        let batch =
748            SpanBatch { batches: vec![first_element, Default::default()], ..Default::default() };
749        assert_eq!(batch.starting_timestamp(), timestamp);
750    }
751
752    #[test]
753    fn test_final_timestamp() {
754        let timestamp = 10;
755        let last_element = SpanBatchElement { timestamp, ..Default::default() };
756        let batch =
757            SpanBatch { batches: vec![Default::default(), last_element], ..Default::default() };
758        assert_eq!(batch.final_timestamp(), timestamp);
759    }
760
761    #[test]
762    fn test_starting_epoch_num() {
763        let epoch_num = 10;
764        let first_element = SpanBatchElement { epoch_num, ..Default::default() };
765        let batch =
766            SpanBatch { batches: vec![first_element, Default::default()], ..Default::default() };
767        assert_eq!(batch.starting_epoch_num(), epoch_num);
768    }
769
770    #[test]
771    fn test_peek() {
772        let first_element = SpanBatchElement { epoch_num: 10, ..Default::default() };
773        let second_element = SpanBatchElement { epoch_num: 11, ..Default::default() };
774        let batch =
775            SpanBatch { batches: vec![first_element, second_element], ..Default::default() };
776        assert_eq!(batch.peek(0).epoch_num, 11);
777        assert_eq!(batch.peek(1).epoch_num, 10);
778    }
779
780    #[test]
781    fn test_append_empty_singular_batch() {
782        let mut batch = SpanBatch::default();
783        let singular_batch = SingleBatch {
784            epoch_num: 10,
785            epoch_hash: FixedBytes::from([17u8; 32]),
786            parent_hash: FixedBytes::from([17u8; 32]),
787            timestamp: 10,
788            transactions: vec![],
789        };
790        assert!(batch.append_singular_batch(singular_batch, 0).is_ok());
791        assert_eq!(batch.batches.len(), 1);
792        assert_eq!(batch.origin_bits.get_bit(0), Some(1));
793        assert_eq!(batch.block_tx_counts, vec![0]);
794        assert_eq!(batch.txs.tx_datas.len(), 0);
795
796        // Add another empty single batch.
797        let singular_batch = SingleBatch {
798            epoch_num: 11,
799            epoch_hash: FixedBytes::from([17u8; 32]),
800            parent_hash: FixedBytes::from([17u8; 32]),
801            timestamp: 20,
802            transactions: vec![],
803        };
804        assert!(batch.append_singular_batch(singular_batch, 1).is_ok());
805    }
806
807    #[test]
808    fn test_check_origin_hash() {
809        let l1_origin_check = FixedBytes::from([17u8; 20]);
810        let hash = b256!("1111111111111111111111111111111111111111000000000000000000000000");
811        let batch = SpanBatch { l1_origin_check, ..Default::default() };
812        assert!(batch.check_origin_hash(hash));
813        // This hash has 19 matching bytes, the other 13 are zeros.
814        let invalid = b256!("1111111111111111111111111111111111111100000000000000000000000000");
815        assert!(!batch.check_origin_hash(invalid));
816    }
817
818    #[test]
819    fn test_check_parent_hash() {
820        let parent_check = FixedBytes::from([17u8; 20]);
821        let hash = b256!("1111111111111111111111111111111111111111000000000000000000000000");
822        let batch = SpanBatch { parent_check, ..Default::default() };
823        assert!(batch.check_parent_hash(hash));
824        // This hash has 19 matching bytes, the other 13 are zeros.
825        let invalid = b256!("1111111111111111111111111111111111111100000000000000000000000000");
826        assert!(!batch.check_parent_hash(invalid));
827    }
828
829    #[tokio::test]
830    async fn test_check_batch_missing_l1_block_input() {
831        let trace_store: TraceStorage = Default::default();
832        let layer = CollectingLayer::new(trace_store.clone());
833        tracing_subscriber::Registry::default().with(layer).init();
834
835        let cfg = RollupConfig::default();
836        let l1_blocks = vec![];
837        let l2_safe_head = L2BlockInfo::default();
838        let inclusion_block = BlockInfo::default();
839        let mut fetcher: TestBatchValidator = TestBatchValidator::default();
840        let batch = SpanBatch::default();
841        assert_eq!(
842            batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await,
843            BatchValidity::Undecided
844        );
845        let logs = trace_store.get_by_level(Level::WARN);
846        assert_eq!(logs.len(), 1);
847        assert!(logs[0].contains("missing L1 block input, cannot proceed with batch checking"));
848    }
849
850    #[tokio::test]
851    async fn test_check_batches_is_empty() {
852        let trace_store: TraceStorage = Default::default();
853        let layer = CollectingLayer::new(trace_store.clone());
854        tracing_subscriber::Registry::default().with(layer).init();
855
856        let cfg = RollupConfig::default();
857        let l1_blocks = vec![BlockInfo::default()];
858        let l2_safe_head = L2BlockInfo::default();
859        let inclusion_block = BlockInfo::default();
860        let mut fetcher: TestBatchValidator = TestBatchValidator::default();
861        let batch = SpanBatch::default();
862        assert_eq!(
863            batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await,
864            BatchValidity::Undecided
865        );
866        let logs = trace_store.get_by_level(Level::WARN);
867        assert_eq!(logs.len(), 1);
868        assert!(logs[0].contains("empty span batch, cannot proceed with batch checking"));
869    }
870
871    #[tokio::test]
872    async fn test_singular_batches_missing_l1_origin() {
873        let l1_block = BlockInfo { number: 10, timestamp: 20, ..Default::default() };
874        let l1_blocks = vec![l1_block];
875        let l2_safe_head = L2BlockInfo {
876            block_info: BlockInfo { timestamp: 10, ..Default::default() },
877            l1_origin: BlockNumHash { number: 10, ..Default::default() },
878            ..Default::default()
879        };
880        let first = SpanBatchElement { epoch_num: 9, timestamp: 20, ..Default::default() };
881        let second = SpanBatchElement { epoch_num: 11, timestamp: 30, ..Default::default() };
882        let batch = SpanBatch { batches: vec![first, second], ..Default::default() };
883        assert_eq!(
884            batch.get_singular_batches(&l1_blocks, l2_safe_head),
885            Err(SpanBatchError::MissingL1Origin),
886        );
887    }
888
889    #[tokio::test]
890    async fn test_eager_block_missing_origins() {
891        let trace_store: TraceStorage = Default::default();
892        let layer = CollectingLayer::new(trace_store.clone());
893        tracing_subscriber::Registry::default().with(layer).init();
894
895        let cfg = RollupConfig::default();
896        let block = BlockInfo { number: 9, ..Default::default() };
897        let l1_blocks = vec![block];
898        let l2_safe_head = L2BlockInfo::default();
899        let inclusion_block = BlockInfo::default();
900        let mut fetcher: TestBatchValidator = TestBatchValidator::default();
901        let first = SpanBatchElement { epoch_num: 10, ..Default::default() };
902        let batch = SpanBatch { batches: vec![first], ..Default::default() };
903        assert_eq!(
904            batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await,
905            BatchValidity::Undecided
906        );
907        let logs = trace_store.get_by_level(Level::INFO);
908        assert_eq!(logs.len(), 1);
909        let str = alloc::format!(
910            "eager batch wants to advance current epoch {:?}, but could not without more L1 blocks",
911            block.id()
912        );
913        assert!(logs[0].contains(&str));
914    }
915
916    #[tokio::test]
917    async fn test_check_batch_delta_inactive() {
918        let trace_store: TraceStorage = Default::default();
919        let layer = CollectingLayer::new(trace_store.clone());
920        tracing_subscriber::Registry::default().with(layer).init();
921
922        let cfg = RollupConfig {
923            hardforks: HardForkConfig { delta_time: Some(10), ..Default::default() },
924            ..Default::default()
925        };
926        let block = BlockInfo { number: 10, timestamp: 9, ..Default::default() };
927        let l1_blocks = vec![block];
928        let l2_safe_head = L2BlockInfo::default();
929        let inclusion_block = BlockInfo::default();
930        let mut fetcher: TestBatchValidator = TestBatchValidator::default();
931        let first = SpanBatchElement { epoch_num: 10, timestamp: 10, ..Default::default() };
932        let batch = SpanBatch { batches: vec![first], ..Default::default() };
933        assert_eq!(
934            batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await,
935            BatchValidity::Drop
936        );
937        let logs = trace_store.get_by_level(Level::WARN);
938        assert_eq!(logs.len(), 1);
939        let str = alloc::format!(
940            "received SpanBatch (id {:?}) with L1 origin (timestamp {}) before Delta hard fork",
941            block.id(),
942            block.timestamp
943        );
944        assert!(logs[0].contains(&str));
945    }
946
947    #[tokio::test]
948    async fn test_check_batch_out_of_order() {
949        let trace_store: TraceStorage = Default::default();
950        let layer = CollectingLayer::new(trace_store.clone());
951        tracing_subscriber::Registry::default().with(layer).init();
952
953        let cfg = RollupConfig {
954            hardforks: HardForkConfig { delta_time: Some(0), ..Default::default() },
955            block_time: 10,
956            ..Default::default()
957        };
958        let block = BlockInfo { number: 10, timestamp: 10, ..Default::default() };
959        let l1_blocks = vec![block];
960        let l2_safe_head = L2BlockInfo {
961            block_info: BlockInfo { timestamp: 10, ..Default::default() },
962            ..Default::default()
963        };
964        let inclusion_block = BlockInfo::default();
965        let mut fetcher: TestBatchValidator = TestBatchValidator::default();
966        let first = SpanBatchElement { epoch_num: 10, timestamp: 21, ..Default::default() };
967        let batch = SpanBatch { batches: vec![first], ..Default::default() };
968        assert_eq!(
969            batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await,
970            BatchValidity::Future
971        );
972        let logs = trace_store.get_by_level(Level::WARN);
973        assert_eq!(logs.len(), 1);
974        assert!(logs[0].contains(
975            "received out-of-order batch for future processing after next batch (21 > 20)"
976        ));
977    }
978
979    #[tokio::test]
980    async fn test_check_batch_no_new_blocks() {
981        let trace_store: TraceStorage = Default::default();
982        let layer = CollectingLayer::new(trace_store.clone());
983        tracing_subscriber::Registry::default().with(layer).init();
984
985        let cfg = RollupConfig {
986            hardforks: HardForkConfig { delta_time: Some(0), ..Default::default() },
987            block_time: 10,
988            ..Default::default()
989        };
990        let block = BlockInfo { number: 10, timestamp: 10, ..Default::default() };
991        let l1_blocks = vec![block];
992        let l2_safe_head = L2BlockInfo {
993            block_info: BlockInfo { timestamp: 10, ..Default::default() },
994            ..Default::default()
995        };
996        let inclusion_block = BlockInfo::default();
997        let mut fetcher: TestBatchValidator = TestBatchValidator::default();
998        let first = SpanBatchElement { epoch_num: 10, timestamp: 10, ..Default::default() };
999        let batch = SpanBatch { batches: vec![first], ..Default::default() };
1000        assert_eq!(
1001            batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await,
1002            BatchValidity::Drop
1003        );
1004        let logs = trace_store.get_by_level(Level::WARN);
1005        assert_eq!(logs.len(), 1);
1006        assert!(logs[0].contains("span batch has no new blocks after safe head"));
1007    }
1008
1009    #[tokio::test]
1010    async fn test_check_batch_overlapping_blocks_tx_count_mismatch() {
1011        let trace_store: TraceStorage = Default::default();
1012        let layer = CollectingLayer::new(trace_store.clone());
1013        tracing_subscriber::Registry::default().with(layer).init();
1014
1015        let cfg = RollupConfig {
1016            hardforks: HardForkConfig { delta_time: Some(0), ..Default::default() },
1017            block_time: 10,
1018            max_sequencer_drift: 1000,
1019            ..Default::default()
1020        };
1021        let l1_blocks = vec![
1022            BlockInfo { number: 9, timestamp: 0, ..Default::default() },
1023            BlockInfo { number: 10, timestamp: 10, ..Default::default() },
1024            BlockInfo { number: 11, timestamp: 20, ..Default::default() },
1025        ];
1026        let l2_safe_head = L2BlockInfo {
1027            block_info: BlockInfo { number: 10, timestamp: 20, ..Default::default() },
1028            l1_origin: BlockNumHash { number: 11, ..Default::default() },
1029            ..Default::default()
1030        };
1031        let inclusion_block = BlockInfo::default();
1032        let mut fetcher: TestBatchValidator = TestBatchValidator {
1033            op_blocks: vec![OpBlock {
1034                header: Header { number: 9, ..Default::default() },
1035                body: alloy_consensus::BlockBody {
1036                    transactions: Vec::new(),
1037                    ommers: Vec::new(),
1038                    withdrawals: None,
1039                },
1040            }],
1041            blocks: vec![
1042                L2BlockInfo {
1043                    block_info: BlockInfo { number: 8, timestamp: 0, ..Default::default() },
1044                    l1_origin: BlockNumHash { number: 9, ..Default::default() },
1045                    ..Default::default()
1046                },
1047                L2BlockInfo {
1048                    block_info: BlockInfo { number: 9, timestamp: 10, ..Default::default() },
1049                    l1_origin: BlockNumHash { number: 10, ..Default::default() },
1050                    ..Default::default()
1051                },
1052                L2BlockInfo {
1053                    block_info: BlockInfo { number: 10, timestamp: 20, ..Default::default() },
1054                    l1_origin: BlockNumHash { number: 11, ..Default::default() },
1055                    ..Default::default()
1056                },
1057            ],
1058            ..Default::default()
1059        };
1060        let first = SpanBatchElement {
1061            epoch_num: 10,
1062            timestamp: 10,
1063            transactions: vec![Bytes(vec![EIP1559_TX_TYPE_ID].into())],
1064        };
1065        let second = SpanBatchElement { epoch_num: 10, timestamp: 60, ..Default::default() };
1066        let batch = SpanBatch { batches: vec![first, second], ..Default::default() };
1067        assert_eq!(
1068            batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await,
1069            BatchValidity::Drop
1070        );
1071        let logs = trace_store.get_by_level(Level::WARN);
1072        assert_eq!(logs.len(), 1);
1073        assert!(logs[0].contains(
1074            "overlapped block's tx count does not match, safe_block_txs: 0, batch_txs: 1"
1075        ));
1076    }
1077
1078    #[tokio::test]
1079    async fn test_check_batch_overlapping_blocks_tx_mismatch() {
1080        let trace_store: TraceStorage = Default::default();
1081        let layer = CollectingLayer::new(trace_store.clone());
1082        tracing_subscriber::Registry::default().with(layer).init();
1083
1084        let cfg = RollupConfig {
1085            hardforks: HardForkConfig { delta_time: Some(0), ..Default::default() },
1086            block_time: 10,
1087            max_sequencer_drift: 1000,
1088            ..Default::default()
1089        };
1090        let l1_blocks = vec![
1091            BlockInfo { number: 9, timestamp: 0, ..Default::default() },
1092            BlockInfo { number: 10, timestamp: 10, ..Default::default() },
1093            BlockInfo { number: 11, timestamp: 20, ..Default::default() },
1094        ];
1095        let l2_safe_head = L2BlockInfo {
1096            block_info: BlockInfo { number: 10, timestamp: 20, ..Default::default() },
1097            l1_origin: BlockNumHash { number: 11, ..Default::default() },
1098            ..Default::default()
1099        };
1100        let inclusion_block = BlockInfo::default();
1101        let mut fetcher: TestBatchValidator = TestBatchValidator {
1102            op_blocks: vec![OpBlock {
1103                header: Header { number: 9, ..Default::default() },
1104                body: alloy_consensus::BlockBody {
1105                    transactions: vec![op_alloy_consensus::OpTxEnvelope::Eip1559(
1106                        alloy_consensus::Signed::new_unchecked(
1107                            alloy_consensus::TxEip1559 {
1108                                chain_id: 0,
1109                                nonce: 0,
1110                                gas_limit: 2,
1111                                max_fee_per_gas: 1,
1112                                max_priority_fee_per_gas: 1,
1113                                to: alloy_primitives::TxKind::Create,
1114                                value: alloy_primitives::U256::from(3),
1115                                ..Default::default()
1116                            },
1117                            alloy_primitives::Signature::test_signature(),
1118                            alloy_primitives::B256::ZERO,
1119                        ),
1120                    )],
1121                    ommers: Vec::new(),
1122                    withdrawals: None,
1123                },
1124            }],
1125            blocks: vec![
1126                L2BlockInfo {
1127                    block_info: BlockInfo { number: 8, timestamp: 0, ..Default::default() },
1128                    l1_origin: BlockNumHash { number: 9, ..Default::default() },
1129                    ..Default::default()
1130                },
1131                L2BlockInfo {
1132                    block_info: BlockInfo { number: 9, timestamp: 10, ..Default::default() },
1133                    l1_origin: BlockNumHash { number: 10, ..Default::default() },
1134                    ..Default::default()
1135                },
1136                L2BlockInfo {
1137                    block_info: BlockInfo { number: 10, timestamp: 20, ..Default::default() },
1138                    l1_origin: BlockNumHash { number: 11, ..Default::default() },
1139                    ..Default::default()
1140                },
1141            ],
1142            ..Default::default()
1143        };
1144        let first = SpanBatchElement {
1145            epoch_num: 10,
1146            timestamp: 10,
1147            transactions: vec![Bytes(vec![EIP1559_TX_TYPE_ID].into())],
1148        };
1149        let second = SpanBatchElement { epoch_num: 10, timestamp: 60, ..Default::default() };
1150        let batch = SpanBatch { batches: vec![first, second], ..Default::default() };
1151        assert_eq!(
1152            batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await,
1153            BatchValidity::Drop
1154        );
1155        let logs = trace_store.get_by_level(Level::WARN);
1156        assert_eq!(logs.len(), 1);
1157        assert!(logs[0].contains("overlapped block's transaction does not match"));
1158    }
1159
1160    #[tokio::test]
1161    async fn test_check_batch_block_timestamp_lt_l1_origin() {
1162        let trace_store: TraceStorage = Default::default();
1163        let layer = CollectingLayer::new(trace_store.clone());
1164        tracing_subscriber::Registry::default().with(layer).init();
1165
1166        let cfg = RollupConfig {
1167            hardforks: HardForkConfig { delta_time: Some(0), ..Default::default() },
1168            block_time: 10,
1169            ..Default::default()
1170        };
1171        let l1_block = BlockInfo { number: 10, timestamp: 20, ..Default::default() };
1172        let l1_blocks = vec![l1_block];
1173        let l2_safe_head = L2BlockInfo {
1174            block_info: BlockInfo { timestamp: 10, ..Default::default() },
1175            l1_origin: BlockNumHash { number: 10, ..Default::default() },
1176            ..Default::default()
1177        };
1178        let inclusion_block = BlockInfo::default();
1179        let mut fetcher: TestBatchValidator = TestBatchValidator::default();
1180        let first = SpanBatchElement { epoch_num: 10, timestamp: 20, ..Default::default() };
1181        let second = SpanBatchElement { epoch_num: 10, timestamp: 19, ..Default::default() };
1182        let third = SpanBatchElement { epoch_num: 10, timestamp: 30, ..Default::default() };
1183        let batch = SpanBatch { batches: vec![first, second, third], ..Default::default() };
1184        assert_eq!(
1185            batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await,
1186            BatchValidity::Drop
1187        );
1188        let logs = trace_store.get_by_level(Level::WARN);
1189        assert_eq!(logs.len(), 1);
1190        let str = alloc::format!(
1191            "block timestamp is less than L1 origin timestamp, l2_timestamp: 19, l1_timestamp: 20, origin: {:?}",
1192            l1_block.id(),
1193        );
1194        assert!(logs[0].contains(&str));
1195    }
1196
1197    #[tokio::test]
1198    async fn test_check_batch_misaligned_timestamp() {
1199        let trace_store: TraceStorage = Default::default();
1200        let layer = CollectingLayer::new(trace_store.clone());
1201        tracing_subscriber::Registry::default().with(layer).init();
1202
1203        let cfg = RollupConfig {
1204            hardforks: HardForkConfig { delta_time: Some(0), ..Default::default() },
1205            block_time: 10,
1206            ..Default::default()
1207        };
1208        let block = BlockInfo { number: 10, timestamp: 10, ..Default::default() };
1209        let l1_blocks = vec![block];
1210        let l2_safe_head = L2BlockInfo {
1211            block_info: BlockInfo { timestamp: 10, ..Default::default() },
1212            ..Default::default()
1213        };
1214        let inclusion_block = BlockInfo::default();
1215        let mut fetcher: TestBatchValidator = TestBatchValidator::default();
1216        let first = SpanBatchElement { epoch_num: 10, timestamp: 11, ..Default::default() };
1217        let second = SpanBatchElement { epoch_num: 11, timestamp: 21, ..Default::default() };
1218        let batch = SpanBatch { batches: vec![first, second], ..Default::default() };
1219        assert_eq!(
1220            batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await,
1221            BatchValidity::Drop
1222        );
1223        let logs = trace_store.get_by_level(Level::WARN);
1224        assert_eq!(logs.len(), 1);
1225        assert!(logs[0].contains("batch has misaligned timestamp, block time is too short"));
1226    }
1227
1228    #[tokio::test]
1229    async fn test_check_batch_misaligned_without_overlap() {
1230        let trace_store: TraceStorage = Default::default();
1231        let layer = CollectingLayer::new(trace_store.clone());
1232        tracing_subscriber::Registry::default().with(layer).init();
1233
1234        let cfg = RollupConfig {
1235            hardforks: HardForkConfig { delta_time: Some(0), ..Default::default() },
1236            block_time: 10,
1237            ..Default::default()
1238        };
1239        let block = BlockInfo { number: 10, timestamp: 10, ..Default::default() };
1240        let l1_blocks = vec![block];
1241        let l2_safe_head = L2BlockInfo {
1242            block_info: BlockInfo { timestamp: 10, ..Default::default() },
1243            ..Default::default()
1244        };
1245        let inclusion_block = BlockInfo::default();
1246        let mut fetcher: TestBatchValidator = TestBatchValidator::default();
1247        let first = SpanBatchElement { epoch_num: 10, timestamp: 8, ..Default::default() };
1248        let second = SpanBatchElement { epoch_num: 11, timestamp: 20, ..Default::default() };
1249        let batch = SpanBatch { batches: vec![first, second], ..Default::default() };
1250        assert_eq!(
1251            batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await,
1252            BatchValidity::Drop
1253        );
1254        let logs = trace_store.get_by_level(Level::WARN);
1255        assert_eq!(logs.len(), 1);
1256        assert!(logs[0].contains("batch has misaligned timestamp, not overlapped exactly"));
1257    }
1258
1259    #[tokio::test]
1260    async fn test_check_batch_failed_to_fetch_l2_block() {
1261        let trace_store: TraceStorage = Default::default();
1262        let layer = CollectingLayer::new(trace_store.clone());
1263        tracing_subscriber::Registry::default().with(layer).init();
1264
1265        let cfg = RollupConfig {
1266            hardforks: HardForkConfig { delta_time: Some(0), ..Default::default() },
1267            block_time: 10,
1268            ..Default::default()
1269        };
1270        let block = BlockInfo { number: 10, timestamp: 10, ..Default::default() };
1271        let l1_blocks = vec![block];
1272        let l2_safe_head = L2BlockInfo {
1273            block_info: BlockInfo { number: 41, timestamp: 10, ..Default::default() },
1274            ..Default::default()
1275        };
1276        let inclusion_block = BlockInfo::default();
1277        let mut fetcher: TestBatchValidator = TestBatchValidator::default();
1278        let first = SpanBatchElement { epoch_num: 10, timestamp: 10, ..Default::default() };
1279        let second = SpanBatchElement { epoch_num: 11, timestamp: 20, ..Default::default() };
1280        let batch = SpanBatch { batches: vec![first, second], ..Default::default() };
1281        // parent number = 41 - (10 - 10) / 10 - 1 = 40
1282        assert_eq!(
1283            batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await,
1284            BatchValidity::Undecided
1285        );
1286        let logs = trace_store.get_by_level(Level::WARN);
1287        assert_eq!(logs.len(), 1);
1288        assert!(logs[0].contains("failed to fetch L2 block number 40: Block not found"));
1289    }
1290
1291    #[tokio::test]
1292    async fn test_check_batch_parent_hash_fail() {
1293        let trace_store: TraceStorage = Default::default();
1294        let layer = CollectingLayer::new(trace_store.clone());
1295        tracing_subscriber::Registry::default().with(layer).init();
1296
1297        let cfg = RollupConfig {
1298            hardforks: HardForkConfig { delta_time: Some(0), ..Default::default() },
1299            block_time: 10,
1300            ..Default::default()
1301        };
1302        let block = BlockInfo { number: 10, timestamp: 10, ..Default::default() };
1303        let l1_blocks = vec![block];
1304        let l2_safe_head = L2BlockInfo {
1305            block_info: BlockInfo { number: 41, timestamp: 10, ..Default::default() },
1306            ..Default::default()
1307        };
1308        let inclusion_block = BlockInfo::default();
1309        let l2_block = L2BlockInfo {
1310            block_info: BlockInfo { number: 41, timestamp: 10, ..Default::default() },
1311            l1_origin: BlockNumHash { number: 9, ..Default::default() },
1312            ..Default::default()
1313        };
1314        let mut fetcher: TestBatchValidator =
1315            TestBatchValidator { blocks: vec![l2_block], ..Default::default() };
1316        fetcher.short_circuit = true;
1317        let first = SpanBatchElement { epoch_num: 10, timestamp: 10, ..Default::default() };
1318        let second = SpanBatchElement { epoch_num: 11, timestamp: 20, ..Default::default() };
1319        let batch = SpanBatch {
1320            batches: vec![first, second],
1321            parent_check: FixedBytes::<20>::from_slice(
1322                &b256!("1111111111111111111111111111111111111111000000000000000000000000")[..20],
1323            ),
1324            ..Default::default()
1325        };
1326        // parent number = 41 - (10 - 10) / 10 - 1 = 40
1327        assert_eq!(
1328            batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await,
1329            BatchValidity::Drop
1330        );
1331        let logs = trace_store.get_by_level(Level::WARN);
1332        assert_eq!(logs.len(), 1);
1333        assert!(logs[0].contains("parent block mismatch, expected: 40, received: 41"));
1334    }
1335
1336    #[tokio::test]
1337    async fn test_check_sequence_window_expired() {
1338        let trace_store: TraceStorage = Default::default();
1339        let layer = CollectingLayer::new(trace_store.clone());
1340        tracing_subscriber::Registry::default().with(layer).init();
1341
1342        let cfg = RollupConfig {
1343            hardforks: HardForkConfig { delta_time: Some(0), ..Default::default() },
1344            block_time: 10,
1345            ..Default::default()
1346        };
1347        let block = BlockInfo { number: 10, timestamp: 10, ..Default::default() };
1348        let l1_blocks = vec![block];
1349        let parent_hash = b256!("1111111111111111111111111111111111111111000000000000000000000000");
1350        let l2_safe_head = L2BlockInfo {
1351            block_info: BlockInfo { number: 41, timestamp: 10, parent_hash, ..Default::default() },
1352            ..Default::default()
1353        };
1354        let inclusion_block = BlockInfo { number: 50, ..Default::default() };
1355        let l2_block = L2BlockInfo {
1356            block_info: BlockInfo {
1357                number: 40,
1358                hash: parent_hash,
1359                timestamp: 10,
1360                ..Default::default()
1361            },
1362            ..Default::default()
1363        };
1364        let mut fetcher: TestBatchValidator =
1365            TestBatchValidator { blocks: vec![l2_block], ..Default::default() };
1366        let first = SpanBatchElement { epoch_num: 10, timestamp: 10, ..Default::default() };
1367        let second = SpanBatchElement { epoch_num: 11, timestamp: 20, ..Default::default() };
1368        let batch = SpanBatch {
1369            batches: vec![first, second],
1370            parent_check: FixedBytes::<20>::from_slice(&parent_hash[..20]),
1371            ..Default::default()
1372        };
1373        // parent number = 41 - (10 - 10) / 10 - 1 = 40
1374        assert_eq!(
1375            batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await,
1376            BatchValidity::Drop
1377        );
1378        let logs = trace_store.get_by_level(Level::WARN);
1379        assert_eq!(logs.len(), 1);
1380        assert!(logs[0].contains("batch was included too late, sequence window expired"));
1381    }
1382
1383    #[tokio::test]
1384    async fn test_starting_epoch_too_far_ahead() {
1385        let trace_store: TraceStorage = Default::default();
1386        let layer = CollectingLayer::new(trace_store.clone());
1387        tracing_subscriber::Registry::default().with(layer).init();
1388
1389        let cfg = RollupConfig {
1390            seq_window_size: 100,
1391            hardforks: HardForkConfig { delta_time: Some(0), ..Default::default() },
1392            block_time: 10,
1393            ..Default::default()
1394        };
1395        let block = BlockInfo { number: 10, timestamp: 10, ..Default::default() };
1396        let l1_blocks = vec![block];
1397        let parent_hash = b256!("1111111111111111111111111111111111111111000000000000000000000000");
1398        let l2_safe_head = L2BlockInfo {
1399            block_info: BlockInfo { number: 41, timestamp: 10, parent_hash, ..Default::default() },
1400            l1_origin: BlockNumHash { number: 8, ..Default::default() },
1401            ..Default::default()
1402        };
1403        let inclusion_block = BlockInfo { number: 50, ..Default::default() };
1404        let l2_block = L2BlockInfo {
1405            block_info: BlockInfo {
1406                number: 40,
1407                hash: parent_hash,
1408                timestamp: 10,
1409                ..Default::default()
1410            },
1411            l1_origin: BlockNumHash { number: 8, ..Default::default() },
1412            ..Default::default()
1413        };
1414        let mut fetcher: TestBatchValidator =
1415            TestBatchValidator { blocks: vec![l2_block], ..Default::default() };
1416        let first = SpanBatchElement { epoch_num: 10, timestamp: 10, ..Default::default() };
1417        let second = SpanBatchElement { epoch_num: 11, timestamp: 20, ..Default::default() };
1418        let batch = SpanBatch {
1419            batches: vec![first, second],
1420            parent_check: FixedBytes::<20>::from_slice(&parent_hash[..20]),
1421            ..Default::default()
1422        };
1423        // parent number = 41 - (10 - 10) / 10 - 1 = 40
1424        assert_eq!(
1425            batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await,
1426            BatchValidity::Drop
1427        );
1428        let logs = trace_store.get_by_level(Level::WARN);
1429        assert_eq!(logs.len(), 1);
1430        let str = "batch is for future epoch too far ahead, while it has the next timestamp, so it must be invalid. starting epoch: 10 | next epoch: 9";
1431        assert!(logs[0].contains(str));
1432    }
1433
1434    #[tokio::test]
1435    async fn test_check_batch_epoch_hash_mismatch() {
1436        let trace_store: TraceStorage = Default::default();
1437        let layer = CollectingLayer::new(trace_store.clone());
1438        tracing_subscriber::Registry::default().with(layer).init();
1439
1440        let cfg = RollupConfig {
1441            seq_window_size: 100,
1442            hardforks: HardForkConfig { delta_time: Some(0), ..Default::default() },
1443            block_time: 10,
1444            ..Default::default()
1445        };
1446        let l1_block_hash =
1447            b256!("3333333333333333333333333333333333333333000000000000000000000000");
1448        let block =
1449            BlockInfo { number: 11, timestamp: 10, hash: l1_block_hash, ..Default::default() };
1450        let l1_blocks = vec![block];
1451        let parent_hash = b256!("1111111111111111111111111111111111111111000000000000000000000000");
1452        let l2_safe_head = L2BlockInfo {
1453            block_info: BlockInfo {
1454                number: 41,
1455                timestamp: 10,
1456                hash: parent_hash,
1457                ..Default::default()
1458            },
1459            l1_origin: BlockNumHash { number: 9, ..Default::default() },
1460            ..Default::default()
1461        };
1462        let inclusion_block = BlockInfo { number: 50, ..Default::default() };
1463        let l2_block = L2BlockInfo {
1464            block_info: BlockInfo {
1465                number: 40,
1466                timestamp: 10,
1467                hash: parent_hash,
1468                ..Default::default()
1469            },
1470            l1_origin: BlockNumHash { number: 9, ..Default::default() },
1471            ..Default::default()
1472        };
1473        let mut fetcher: TestBatchValidator =
1474            TestBatchValidator { blocks: vec![l2_block], ..Default::default() };
1475        let first = SpanBatchElement { epoch_num: 10, timestamp: 10, ..Default::default() };
1476        let second = SpanBatchElement { epoch_num: 11, timestamp: 20, ..Default::default() };
1477        let batch = SpanBatch {
1478            batches: vec![first, second],
1479            parent_check: FixedBytes::<20>::from_slice(&parent_hash[..20]),
1480            ..Default::default()
1481        };
1482        assert_eq!(
1483            batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await,
1484            BatchValidity::Drop
1485        );
1486        let logs = trace_store.get_by_level(Level::WARN);
1487        assert_eq!(logs.len(), 1);
1488        let str = alloc::format!(
1489            "batch is for different L1 chain, epoch hash does not match, expected: {}",
1490            l1_block_hash,
1491        );
1492        assert!(logs[0].contains(&str));
1493    }
1494
1495    #[tokio::test]
1496    async fn test_need_more_l1_blocks() {
1497        let trace_store: TraceStorage = Default::default();
1498        let layer = CollectingLayer::new(trace_store.clone());
1499        tracing_subscriber::Registry::default().with(layer).init();
1500
1501        let cfg = RollupConfig {
1502            seq_window_size: 100,
1503            hardforks: HardForkConfig { delta_time: Some(0), ..Default::default() },
1504            block_time: 10,
1505            ..Default::default()
1506        };
1507        let l1_block_hash =
1508            b256!("3333333333333333333333333333333333333333000000000000000000000000");
1509        let block =
1510            BlockInfo { number: 10, timestamp: 10, hash: l1_block_hash, ..Default::default() };
1511        let l1_blocks = vec![block];
1512        let parent_hash = b256!("1111111111111111111111111111111111111111000000000000000000000000");
1513        let l2_safe_head = L2BlockInfo {
1514            block_info: BlockInfo { number: 41, timestamp: 10, parent_hash, ..Default::default() },
1515            l1_origin: BlockNumHash { number: 9, ..Default::default() },
1516            ..Default::default()
1517        };
1518        let inclusion_block = BlockInfo { number: 50, ..Default::default() };
1519        let l2_block = L2BlockInfo {
1520            block_info: BlockInfo {
1521                number: 40,
1522                timestamp: 10,
1523                hash: parent_hash,
1524                ..Default::default()
1525            },
1526            l1_origin: BlockNumHash { number: 9, ..Default::default() },
1527            ..Default::default()
1528        };
1529        let mut fetcher: TestBatchValidator =
1530            TestBatchValidator { blocks: vec![l2_block], ..Default::default() };
1531        let first = SpanBatchElement { epoch_num: 10, timestamp: 10, ..Default::default() };
1532        let second = SpanBatchElement { epoch_num: 11, timestamp: 20, ..Default::default() };
1533        let batch = SpanBatch {
1534            batches: vec![first, second],
1535            parent_check: FixedBytes::<20>::from_slice(&parent_hash[..20]),
1536            l1_origin_check: FixedBytes::<20>::from_slice(&l1_block_hash[..20]),
1537            ..Default::default()
1538        };
1539        assert_eq!(
1540            batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await,
1541            BatchValidity::Undecided
1542        );
1543        let logs = trace_store.get_by_level(Level::INFO);
1544        assert_eq!(logs.len(), 1);
1545        assert!(logs[0].contains("need more l1 blocks to check entire origins of span batch"));
1546    }
1547
1548    #[tokio::test]
1549    async fn test_drop_batch_epoch_too_old() {
1550        let trace_store: TraceStorage = Default::default();
1551        let layer = CollectingLayer::new(trace_store.clone());
1552        tracing_subscriber::Registry::default().with(layer).init();
1553
1554        let cfg = RollupConfig {
1555            seq_window_size: 100,
1556            hardforks: HardForkConfig { delta_time: Some(0), ..Default::default() },
1557            block_time: 10,
1558            ..Default::default()
1559        };
1560        let l1_block_hash =
1561            b256!("3333333333333333333333333333333333333333000000000000000000000000");
1562        let block =
1563            BlockInfo { number: 11, timestamp: 10, hash: l1_block_hash, ..Default::default() };
1564        let l1_blocks = vec![block];
1565        let parent_hash = b256!("1111111111111111111111111111111111111111000000000000000000000000");
1566        let l2_safe_head = L2BlockInfo {
1567            block_info: BlockInfo { number: 41, timestamp: 10, parent_hash, ..Default::default() },
1568            l1_origin: BlockNumHash { number: 13, ..Default::default() },
1569            ..Default::default()
1570        };
1571        let inclusion_block = BlockInfo { number: 50, ..Default::default() };
1572        let l2_block = L2BlockInfo {
1573            block_info: BlockInfo {
1574                number: 40,
1575                timestamp: 10,
1576                hash: parent_hash,
1577                ..Default::default()
1578            },
1579            l1_origin: BlockNumHash { number: 14, ..Default::default() },
1580            ..Default::default()
1581        };
1582        let mut fetcher: TestBatchValidator =
1583            TestBatchValidator { blocks: vec![l2_block], ..Default::default() };
1584        let first = SpanBatchElement { epoch_num: 10, timestamp: 10, ..Default::default() };
1585        let second = SpanBatchElement { epoch_num: 11, timestamp: 20, ..Default::default() };
1586        let batch = SpanBatch {
1587            batches: vec![first, second],
1588            parent_check: FixedBytes::<20>::from_slice(&parent_hash[..20]),
1589            l1_origin_check: FixedBytes::<20>::from_slice(&l1_block_hash[..20]),
1590            ..Default::default()
1591        };
1592        assert_eq!(
1593            batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await,
1594            BatchValidity::Drop
1595        );
1596        let logs = trace_store.get_by_level(Level::WARN);
1597        assert_eq!(logs.len(), 1);
1598        let str = alloc::format!(
1599            "dropped batch, epoch is too old, minimum: {:?}",
1600            l2_block.block_info.id(),
1601        );
1602        assert!(logs[0].contains(&str));
1603    }
1604
1605    #[tokio::test]
1606    async fn test_check_batch_exceeds_max_seq_drif() {
1607        let trace_store: TraceStorage = Default::default();
1608        let layer = CollectingLayer::new(trace_store.clone());
1609        tracing_subscriber::Registry::default().with(layer).init();
1610
1611        let cfg = RollupConfig {
1612            seq_window_size: 100,
1613            max_sequencer_drift: 0,
1614            hardforks: HardForkConfig { delta_time: Some(0), ..Default::default() },
1615            block_time: 10,
1616            ..Default::default()
1617        };
1618        let l1_block_hash =
1619            b256!("3333333333333333333333333333333333333333000000000000000000000000");
1620        let block =
1621            BlockInfo { number: 11, timestamp: 10, hash: l1_block_hash, ..Default::default() };
1622        let second_block =
1623            BlockInfo { number: 12, timestamp: 10, hash: l1_block_hash, ..Default::default() };
1624        let l1_blocks = vec![block, second_block];
1625        let parent_hash = b256!("1111111111111111111111111111111111111111000000000000000000000000");
1626        let l2_safe_head = L2BlockInfo {
1627            block_info: BlockInfo {
1628                number: 41,
1629                timestamp: 10,
1630                hash: parent_hash,
1631                ..Default::default()
1632            },
1633            l1_origin: BlockNumHash { number: 9, ..Default::default() },
1634            ..Default::default()
1635        };
1636        let inclusion_block = BlockInfo { number: 50, ..Default::default() };
1637        let l2_block = L2BlockInfo {
1638            block_info: BlockInfo { number: 40, ..Default::default() },
1639            ..Default::default()
1640        };
1641        let mut fetcher: TestBatchValidator =
1642            TestBatchValidator { blocks: vec![l2_block], ..Default::default() };
1643        let first = SpanBatchElement { epoch_num: 10, timestamp: 20, ..Default::default() };
1644        let second = SpanBatchElement { epoch_num: 10, timestamp: 20, ..Default::default() };
1645        let third = SpanBatchElement { epoch_num: 11, timestamp: 20, ..Default::default() };
1646        let batch = SpanBatch {
1647            batches: vec![first, second, third],
1648            parent_check: FixedBytes::<20>::from_slice(&parent_hash[..20]),
1649            l1_origin_check: FixedBytes::<20>::from_slice(&l1_block_hash[..20]),
1650            ..Default::default()
1651        };
1652        assert_eq!(
1653            batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await,
1654            BatchValidity::Drop
1655        );
1656        let logs = trace_store.get_by_level(Level::INFO);
1657        assert_eq!(logs.len(), 1);
1658        assert!(logs[0].contains("batch exceeded sequencer time drift without adopting next origin, and next L1 origin would have been valid"));
1659    }
1660
1661    #[tokio::test]
1662    async fn test_continuing_with_empty_batch() {
1663        let trace_store: TraceStorage = Default::default();
1664        let layer = CollectingLayer::new(trace_store.clone());
1665        tracing_subscriber::Registry::default().with(layer).init();
1666
1667        let cfg = RollupConfig {
1668            seq_window_size: 100,
1669            max_sequencer_drift: 0,
1670            hardforks: HardForkConfig { delta_time: Some(0), ..Default::default() },
1671            block_time: 10,
1672            ..Default::default()
1673        };
1674        let l1_block_hash =
1675            b256!("3333333333333333333333333333333333333333000000000000000000000000");
1676        let block =
1677            BlockInfo { number: 11, timestamp: 10, hash: l1_block_hash, ..Default::default() };
1678        let second_block =
1679            BlockInfo { number: 12, timestamp: 21, hash: l1_block_hash, ..Default::default() };
1680        let l1_blocks = vec![block, second_block];
1681        let parent_hash = b256!("1111111111111111111111111111111111111111000000000000000000000000");
1682        let l2_safe_head = L2BlockInfo {
1683            block_info: BlockInfo {
1684                number: 41,
1685                timestamp: 10,
1686                hash: parent_hash,
1687                ..Default::default()
1688            },
1689            l1_origin: BlockNumHash { number: 9, ..Default::default() },
1690            ..Default::default()
1691        };
1692        let inclusion_block = BlockInfo { number: 50, ..Default::default() };
1693        let l2_block = L2BlockInfo {
1694            block_info: BlockInfo { number: 40, ..Default::default() },
1695            ..Default::default()
1696        };
1697        let mut fetcher: TestBatchValidator =
1698            TestBatchValidator { blocks: vec![l2_block], ..Default::default() };
1699        let first = SpanBatchElement { epoch_num: 10, timestamp: 20, transactions: vec![] };
1700        let second = SpanBatchElement { epoch_num: 10, timestamp: 20, transactions: vec![] };
1701        let third = SpanBatchElement { epoch_num: 11, timestamp: 20, transactions: vec![] };
1702        let batch = SpanBatch {
1703            batches: vec![first, second, third],
1704            parent_check: FixedBytes::<20>::from_slice(&parent_hash[..20]),
1705            l1_origin_check: FixedBytes::<20>::from_slice(&l1_block_hash[..20]),
1706            txs: SpanBatchTransactions::default(),
1707            ..Default::default()
1708        };
1709        assert_eq!(
1710            batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await,
1711            BatchValidity::Accept
1712        );
1713        let infos = trace_store.get_by_level(Level::INFO);
1714        assert_eq!(infos.len(), 1);
1715        assert!(infos[0].contains(
1716            "continuing with empty batch before late L1 block to preserve L2 time invariant"
1717        ));
1718    }
1719
1720    #[tokio::test]
1721    async fn test_check_batch_exceeds_sequencer_time_drift() {
1722        let trace_store: TraceStorage = Default::default();
1723        let layer = CollectingLayer::new(trace_store.clone());
1724        tracing_subscriber::Registry::default().with(layer).init();
1725
1726        let cfg = RollupConfig {
1727            seq_window_size: 100,
1728            max_sequencer_drift: 0,
1729            hardforks: HardForkConfig { delta_time: Some(0), ..Default::default() },
1730            block_time: 10,
1731            ..Default::default()
1732        };
1733        let l1_block_hash =
1734            b256!("3333333333333333333333333333333333333333000000000000000000000000");
1735        let block =
1736            BlockInfo { number: 11, timestamp: 10, hash: l1_block_hash, ..Default::default() };
1737        let second_block =
1738            BlockInfo { number: 12, timestamp: 10, hash: l1_block_hash, ..Default::default() };
1739        let l1_blocks = vec![block, second_block];
1740        let parent_hash = b256!("1111111111111111111111111111111111111111000000000000000000000000");
1741        let l2_safe_head = L2BlockInfo {
1742            block_info: BlockInfo {
1743                number: 41,
1744                timestamp: 10,
1745                hash: parent_hash,
1746                ..Default::default()
1747            },
1748            l1_origin: BlockNumHash { number: 9, ..Default::default() },
1749            ..Default::default()
1750        };
1751        let inclusion_block = BlockInfo { number: 50, ..Default::default() };
1752        let l2_block = L2BlockInfo {
1753            block_info: BlockInfo { number: 40, ..Default::default() },
1754            ..Default::default()
1755        };
1756        let mut fetcher: TestBatchValidator =
1757            TestBatchValidator { blocks: vec![l2_block], ..Default::default() };
1758        let first = SpanBatchElement {
1759            epoch_num: 10,
1760            timestamp: 20,
1761            transactions: vec![Default::default()],
1762        };
1763        let second = SpanBatchElement {
1764            epoch_num: 10,
1765            timestamp: 20,
1766            transactions: vec![Default::default()],
1767        };
1768        let third = SpanBatchElement {
1769            epoch_num: 11,
1770            timestamp: 20,
1771            transactions: vec![Default::default()],
1772        };
1773        let batch = SpanBatch {
1774            batches: vec![first, second, third],
1775            parent_check: FixedBytes::<20>::from_slice(&parent_hash[..20]),
1776            l1_origin_check: FixedBytes::<20>::from_slice(&l1_block_hash[..20]),
1777            txs: SpanBatchTransactions::default(),
1778            ..Default::default()
1779        };
1780        assert_eq!(
1781            batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await,
1782            BatchValidity::Drop
1783        );
1784        let logs = trace_store.get_by_level(Level::WARN);
1785        assert_eq!(logs.len(), 1);
1786        assert!(logs[0].contains("batch exceeded sequencer time drift, sequencer must adopt new L1 origin to include transactions again, max_time: 10"));
1787    }
1788
1789    #[tokio::test]
1790    async fn test_check_batch_empty_txs() {
1791        let trace_store: TraceStorage = Default::default();
1792        let layer = CollectingLayer::new(trace_store.clone());
1793        tracing_subscriber::Registry::default().with(layer).init();
1794
1795        let cfg = RollupConfig {
1796            seq_window_size: 100,
1797            max_sequencer_drift: 100,
1798            hardforks: HardForkConfig { delta_time: Some(0), ..Default::default() },
1799            block_time: 10,
1800            ..Default::default()
1801        };
1802        let l1_block_hash =
1803            b256!("3333333333333333333333333333333333333333000000000000000000000000");
1804        let block =
1805            BlockInfo { number: 11, timestamp: 10, hash: l1_block_hash, ..Default::default() };
1806        let second_block =
1807            BlockInfo { number: 12, timestamp: 21, hash: l1_block_hash, ..Default::default() };
1808        let l1_blocks = vec![block, second_block];
1809        let parent_hash = b256!("1111111111111111111111111111111111111111000000000000000000000000");
1810        let l2_safe_head = L2BlockInfo {
1811            block_info: BlockInfo {
1812                number: 41,
1813                timestamp: 10,
1814                hash: parent_hash,
1815                ..Default::default()
1816            },
1817            l1_origin: BlockNumHash { number: 9, ..Default::default() },
1818            ..Default::default()
1819        };
1820        let inclusion_block = BlockInfo { number: 50, ..Default::default() };
1821        let l2_block = L2BlockInfo {
1822            block_info: BlockInfo { number: 40, ..Default::default() },
1823            ..Default::default()
1824        };
1825        let mut fetcher: TestBatchValidator =
1826            TestBatchValidator { blocks: vec![l2_block], ..Default::default() };
1827        let first = SpanBatchElement {
1828            epoch_num: 10,
1829            timestamp: 20,
1830            transactions: vec![Default::default()],
1831        };
1832        let second = SpanBatchElement {
1833            epoch_num: 10,
1834            timestamp: 20,
1835            transactions: vec![Default::default()],
1836        };
1837        let third = SpanBatchElement { epoch_num: 11, timestamp: 20, transactions: vec![] };
1838        let batch = SpanBatch {
1839            batches: vec![first, second, third],
1840            parent_check: FixedBytes::<20>::from_slice(&parent_hash[..20]),
1841            l1_origin_check: FixedBytes::<20>::from_slice(&l1_block_hash[..20]),
1842            txs: SpanBatchTransactions::default(),
1843            ..Default::default()
1844        };
1845        assert_eq!(
1846            batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await,
1847            BatchValidity::Drop
1848        );
1849        let logs = trace_store.get_by_level(Level::WARN);
1850        assert_eq!(logs.len(), 1);
1851        assert!(logs[0].contains("transaction data must not be empty, but found empty tx"));
1852    }
1853
1854    #[tokio::test]
1855    async fn test_check_batch_with_deposit_tx() {
1856        let trace_store: TraceStorage = Default::default();
1857        let layer = CollectingLayer::new(trace_store.clone());
1858        tracing_subscriber::Registry::default().with(layer).init();
1859
1860        let cfg = RollupConfig {
1861            seq_window_size: 100,
1862            max_sequencer_drift: 100,
1863            hardforks: HardForkConfig { delta_time: Some(0), ..Default::default() },
1864            block_time: 10,
1865            ..Default::default()
1866        };
1867        let l1_block_hash =
1868            b256!("3333333333333333333333333333333333333333000000000000000000000000");
1869        let block =
1870            BlockInfo { number: 11, timestamp: 10, hash: l1_block_hash, ..Default::default() };
1871        let second_block =
1872            BlockInfo { number: 12, timestamp: 21, hash: l1_block_hash, ..Default::default() };
1873        let l1_blocks = vec![block, second_block];
1874        let parent_hash = b256!("1111111111111111111111111111111111111111000000000000000000000000");
1875        let l2_safe_head = L2BlockInfo {
1876            block_info: BlockInfo {
1877                number: 41,
1878                timestamp: 10,
1879                hash: parent_hash,
1880                ..Default::default()
1881            },
1882            l1_origin: BlockNumHash { number: 9, ..Default::default() },
1883            ..Default::default()
1884        };
1885        let inclusion_block = BlockInfo { number: 50, ..Default::default() };
1886        let l2_block = L2BlockInfo {
1887            block_info: BlockInfo { number: 40, ..Default::default() },
1888            ..Default::default()
1889        };
1890        let mut fetcher: TestBatchValidator =
1891            TestBatchValidator { blocks: vec![l2_block], ..Default::default() };
1892        let filler_bytes = Bytes::copy_from_slice(&[EIP1559_TX_TYPE_ID]);
1893        let first = SpanBatchElement {
1894            epoch_num: 10,
1895            timestamp: 20,
1896            transactions: vec![filler_bytes.clone()],
1897        };
1898        let second = SpanBatchElement {
1899            epoch_num: 10,
1900            timestamp: 20,
1901            transactions: vec![Bytes::copy_from_slice(&[OpTxType::Deposit as u8])],
1902        };
1903        let third =
1904            SpanBatchElement { epoch_num: 11, timestamp: 20, transactions: vec![filler_bytes] };
1905        let batch = SpanBatch {
1906            batches: vec![first, second, third],
1907            parent_check: FixedBytes::<20>::from_slice(&parent_hash[..20]),
1908            l1_origin_check: FixedBytes::<20>::from_slice(&l1_block_hash[..20]),
1909            txs: SpanBatchTransactions::default(),
1910            ..Default::default()
1911        };
1912        assert_eq!(
1913            batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await,
1914            BatchValidity::Drop
1915        );
1916        let logs = trace_store.get_by_level(Level::WARN);
1917        assert_eq!(logs.len(), 1);
1918        assert!(logs[0].contains("sequencers may not embed any deposits into batch data, but found tx that has one, tx_index: 0"));
1919    }
1920
1921    #[tokio::test]
1922    async fn test_check_batch_with_eip7702_tx() {
1923        let trace_store: TraceStorage = Default::default();
1924        let layer = CollectingLayer::new(trace_store.clone());
1925        tracing_subscriber::Registry::default().with(layer).init();
1926
1927        let cfg = RollupConfig {
1928            seq_window_size: 100,
1929            max_sequencer_drift: 100,
1930            hardforks: HardForkConfig { delta_time: Some(0), ..Default::default() },
1931            block_time: 10,
1932            ..Default::default()
1933        };
1934        let l1_block_hash =
1935            b256!("3333333333333333333333333333333333333333000000000000000000000000");
1936        let block =
1937            BlockInfo { number: 11, timestamp: 10, hash: l1_block_hash, ..Default::default() };
1938        let second_block =
1939            BlockInfo { number: 12, timestamp: 21, hash: l1_block_hash, ..Default::default() };
1940        let l1_blocks = vec![block, second_block];
1941        let parent_hash = b256!("1111111111111111111111111111111111111111000000000000000000000000");
1942        let l2_safe_head = L2BlockInfo {
1943            block_info: BlockInfo {
1944                number: 41,
1945                timestamp: 10,
1946                hash: parent_hash,
1947                ..Default::default()
1948            },
1949            l1_origin: BlockNumHash { number: 9, ..Default::default() },
1950            ..Default::default()
1951        };
1952        let inclusion_block = BlockInfo { number: 50, ..Default::default() };
1953        let l2_block = L2BlockInfo {
1954            block_info: BlockInfo { number: 40, ..Default::default() },
1955            ..Default::default()
1956        };
1957        let mut fetcher: TestBatchValidator =
1958            TestBatchValidator { blocks: vec![l2_block], ..Default::default() };
1959        let filler_bytes = Bytes::copy_from_slice(&[EIP1559_TX_TYPE_ID]);
1960        let first = SpanBatchElement {
1961            epoch_num: 10,
1962            timestamp: 20,
1963            transactions: vec![filler_bytes.clone()],
1964        };
1965        let second = SpanBatchElement {
1966            epoch_num: 10,
1967            timestamp: 20,
1968            transactions: vec![Bytes::copy_from_slice(&[alloy_consensus::TxType::Eip7702 as u8])],
1969        };
1970        let third =
1971            SpanBatchElement { epoch_num: 11, timestamp: 20, transactions: vec![filler_bytes] };
1972        let batch = SpanBatch {
1973            batches: vec![first, second, third],
1974            parent_check: FixedBytes::<20>::from_slice(&parent_hash[..20]),
1975            l1_origin_check: FixedBytes::<20>::from_slice(&l1_block_hash[..20]),
1976            txs: SpanBatchTransactions::default(),
1977            ..Default::default()
1978        };
1979        assert_eq!(
1980            batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await,
1981            BatchValidity::Drop
1982        );
1983        let logs = trace_store.get_by_level(Level::WARN);
1984        assert_eq!(logs.len(), 1);
1985        assert!(
1986            logs[0].contains("EIP-7702 transactions are not supported pre-isthmus. tx_index: 0")
1987        );
1988    }
1989
1990    #[tokio::test]
1991    async fn test_check_batch_failed_to_fetch_payload() {
1992        let trace_store: TraceStorage = Default::default();
1993        let layer = CollectingLayer::new(trace_store.clone());
1994        tracing_subscriber::Registry::default().with(layer).init();
1995
1996        let cfg = RollupConfig {
1997            seq_window_size: 100,
1998            hardforks: HardForkConfig { delta_time: Some(0), ..Default::default() },
1999            block_time: 10,
2000            ..Default::default()
2001        };
2002        let l1_block_hash =
2003            b256!("3333333333333333333333333333333333333333000000000000000000000000");
2004        let block =
2005            BlockInfo { number: 11, timestamp: 10, hash: l1_block_hash, ..Default::default() };
2006        let l1_blocks = vec![block];
2007        let parent_hash = b256!("1111111111111111111111111111111111111111000000000000000000000000");
2008        let l2_safe_head = L2BlockInfo {
2009            block_info: BlockInfo { number: 41, timestamp: 10, parent_hash, ..Default::default() },
2010            l1_origin: BlockNumHash { number: 9, ..Default::default() },
2011            ..Default::default()
2012        };
2013        let inclusion_block = BlockInfo { number: 50, ..Default::default() };
2014        let l2_block = L2BlockInfo {
2015            block_info: BlockInfo {
2016                number: 40,
2017                timestamp: 10,
2018                hash: parent_hash,
2019                ..Default::default()
2020            },
2021            l1_origin: BlockNumHash { number: 9, ..Default::default() },
2022            ..Default::default()
2023        };
2024        let mut fetcher: TestBatchValidator =
2025            TestBatchValidator { blocks: vec![l2_block], ..Default::default() };
2026        let first = SpanBatchElement { epoch_num: 10, timestamp: 10, ..Default::default() };
2027        let second = SpanBatchElement { epoch_num: 11, timestamp: 20, ..Default::default() };
2028        let batch = SpanBatch {
2029            batches: vec![first, second],
2030            parent_check: FixedBytes::<20>::from_slice(&parent_hash[..20]),
2031            l1_origin_check: FixedBytes::<20>::from_slice(&l1_block_hash[..20]),
2032            ..Default::default()
2033        };
2034        assert_eq!(
2035            batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await,
2036            BatchValidity::Undecided
2037        );
2038        let logs = trace_store.get_by_level(Level::WARN);
2039        assert_eq!(logs.len(), 1);
2040        assert!(logs[0].contains("failed to fetch block number 41: L2 Block not found"));
2041    }
2042
2043    #[tokio::test]
2044    async fn test_check_batch_failed_to_extract_l2_block_info() {
2045        let trace_store: TraceStorage = Default::default();
2046        let layer = CollectingLayer::new(trace_store.clone());
2047        tracing_subscriber::Registry::default().with(layer).init();
2048
2049        let cfg = RollupConfig {
2050            seq_window_size: 100,
2051            hardforks: HardForkConfig { delta_time: Some(0), ..Default::default() },
2052            block_time: 10,
2053            ..Default::default()
2054        };
2055        let l1_block_hash =
2056            b256!("3333333333333333333333333333333333333333000000000000000000000000");
2057        let block =
2058            BlockInfo { number: 11, timestamp: 10, hash: l1_block_hash, ..Default::default() };
2059        let l1_blocks = vec![block];
2060        let parent_hash = b256!("1111111111111111111111111111111111111111000000000000000000000000");
2061        let l2_safe_head = L2BlockInfo {
2062            block_info: BlockInfo { number: 41, timestamp: 10, parent_hash, ..Default::default() },
2063            l1_origin: BlockNumHash { number: 9, ..Default::default() },
2064            ..Default::default()
2065        };
2066        let inclusion_block = BlockInfo { number: 50, ..Default::default() };
2067        let l2_block = L2BlockInfo {
2068            block_info: BlockInfo {
2069                number: 40,
2070                timestamp: 10,
2071                hash: parent_hash,
2072                ..Default::default()
2073            },
2074            l1_origin: BlockNumHash { number: 9, ..Default::default() },
2075            ..Default::default()
2076        };
2077        let block = OpBlock {
2078            header: Header { number: 41, ..Default::default() },
2079            body: alloy_consensus::BlockBody {
2080                transactions: Vec::new(),
2081                ommers: Vec::new(),
2082                withdrawals: None,
2083            },
2084        };
2085        let mut fetcher: TestBatchValidator = TestBatchValidator {
2086            blocks: vec![l2_block],
2087            op_blocks: vec![block],
2088            ..Default::default()
2089        };
2090        let first = SpanBatchElement { epoch_num: 10, timestamp: 10, ..Default::default() };
2091        let second = SpanBatchElement { epoch_num: 11, timestamp: 20, ..Default::default() };
2092        let batch = SpanBatch {
2093            batches: vec![first, second],
2094            parent_check: FixedBytes::<20>::from_slice(&parent_hash[..20]),
2095            l1_origin_check: FixedBytes::<20>::from_slice(&l1_block_hash[..20]),
2096            ..Default::default()
2097        };
2098        assert_eq!(
2099            batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await,
2100            BatchValidity::Drop
2101        );
2102        let logs = trace_store.get_by_level(Level::WARN);
2103        assert_eq!(logs.len(), 1);
2104        let str = alloc::format!(
2105            "failed to extract L2BlockInfo from execution payload, hash: {:?}",
2106            b256!("0e2ee9abe94ee4514b170d7039d8151a7469d434a8575dbab5bd4187a27732dd"),
2107        );
2108        assert!(logs[0].contains(&str));
2109    }
2110
2111    #[tokio::test]
2112    async fn test_overlapped_blocks_origin_mismatch() {
2113        let trace_store: TraceStorage = Default::default();
2114        let layer = CollectingLayer::new(trace_store.clone());
2115        tracing_subscriber::Registry::default().with(layer).init();
2116
2117        let payload_block_hash =
2118            b256!("0e2ee9abe94ee4514b170d7039d8151a7469d434a8575dbab5bd4187a27732dd");
2119        let cfg = RollupConfig {
2120            seq_window_size: 100,
2121            hardforks: HardForkConfig { delta_time: Some(0), ..Default::default() },
2122            block_time: 10,
2123            genesis: ChainGenesis {
2124                l2: BlockNumHash { number: 41, hash: payload_block_hash },
2125                ..Default::default()
2126            },
2127            ..Default::default()
2128        };
2129        let l1_block_hash =
2130            b256!("3333333333333333333333333333333333333333000000000000000000000000");
2131        let block =
2132            BlockInfo { number: 11, timestamp: 10, hash: l1_block_hash, ..Default::default() };
2133        let l1_blocks = vec![block];
2134        let parent_hash = b256!("1111111111111111111111111111111111111111000000000000000000000000");
2135        let l2_safe_head = L2BlockInfo {
2136            block_info: BlockInfo { number: 41, timestamp: 10, parent_hash, ..Default::default() },
2137            l1_origin: BlockNumHash { number: 9, ..Default::default() },
2138            ..Default::default()
2139        };
2140        let inclusion_block = BlockInfo { number: 50, ..Default::default() };
2141        let l2_block = L2BlockInfo {
2142            block_info: BlockInfo {
2143                number: 40,
2144                hash: parent_hash,
2145                timestamp: 10,
2146                ..Default::default()
2147            },
2148            l1_origin: BlockNumHash { number: 9, ..Default::default() },
2149            ..Default::default()
2150        };
2151        let block = OpBlock {
2152            header: Header { number: 41, ..Default::default() },
2153            body: alloy_consensus::BlockBody {
2154                transactions: Vec::new(),
2155                ommers: Vec::new(),
2156                withdrawals: None,
2157            },
2158        };
2159        let mut fetcher: TestBatchValidator = TestBatchValidator {
2160            blocks: vec![l2_block],
2161            op_blocks: vec![block],
2162            ..Default::default()
2163        };
2164        let first = SpanBatchElement { epoch_num: 10, timestamp: 10, ..Default::default() };
2165        let second = SpanBatchElement { epoch_num: 11, timestamp: 20, ..Default::default() };
2166        let batch = SpanBatch {
2167            batches: vec![first, second],
2168            parent_check: FixedBytes::<20>::from_slice(&parent_hash[..20]),
2169            l1_origin_check: FixedBytes::<20>::from_slice(&l1_block_hash[..20]),
2170            ..Default::default()
2171        };
2172        assert_eq!(
2173            batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await,
2174            BatchValidity::Drop
2175        );
2176        let logs = trace_store.get_by_level(Level::WARN);
2177        assert_eq!(logs.len(), 1);
2178        assert!(logs[0].contains("overlapped block's L1 origin number does not match"));
2179    }
2180
2181    #[tokio::test]
2182    async fn test_check_batch_valid_with_genesis_epoch() {
2183        let trace_store: TraceStorage = Default::default();
2184        let layer = CollectingLayer::new(trace_store.clone());
2185        tracing_subscriber::Registry::default().with(layer).init();
2186
2187        let payload_block_hash =
2188            b256!("0e2ee9abe94ee4514b170d7039d8151a7469d434a8575dbab5bd4187a27732dd");
2189        let cfg = RollupConfig {
2190            seq_window_size: 100,
2191            hardforks: HardForkConfig { delta_time: Some(0), ..Default::default() },
2192            block_time: 10,
2193            genesis: ChainGenesis {
2194                l2: BlockNumHash { number: 41, hash: payload_block_hash },
2195                l1: BlockNumHash { number: 10, ..Default::default() },
2196                ..Default::default()
2197            },
2198            ..Default::default()
2199        };
2200        let l1_block_hash =
2201            b256!("3333333333333333333333333333333333333333000000000000000000000000");
2202        let block =
2203            BlockInfo { number: 11, timestamp: 10, hash: l1_block_hash, ..Default::default() };
2204        let l1_blocks = vec![block];
2205        let parent_hash = b256!("1111111111111111111111111111111111111111000000000000000000000000");
2206        let l2_safe_head = L2BlockInfo {
2207            block_info: BlockInfo {
2208                number: 41,
2209                timestamp: 10,
2210                hash: parent_hash,
2211                ..Default::default()
2212            },
2213            l1_origin: BlockNumHash { number: 9, ..Default::default() },
2214            ..Default::default()
2215        };
2216        let inclusion_block = BlockInfo { number: 50, ..Default::default() };
2217        let l2_block = L2BlockInfo {
2218            block_info: BlockInfo {
2219                number: 40,
2220                hash: parent_hash,
2221                timestamp: 10,
2222                ..Default::default()
2223            },
2224            l1_origin: BlockNumHash { number: 9, ..Default::default() },
2225            ..Default::default()
2226        };
2227        let block = OpBlock {
2228            header: Header { number: 41, ..Default::default() },
2229            body: alloy_consensus::BlockBody {
2230                transactions: Vec::new(),
2231                ommers: Vec::new(),
2232                withdrawals: None,
2233            },
2234        };
2235        let mut fetcher: TestBatchValidator = TestBatchValidator {
2236            blocks: vec![l2_block],
2237            op_blocks: vec![block],
2238            ..Default::default()
2239        };
2240        let first = SpanBatchElement { epoch_num: 10, timestamp: 10, ..Default::default() };
2241        let second = SpanBatchElement { epoch_num: 11, timestamp: 20, ..Default::default() };
2242        let batch = SpanBatch {
2243            batches: vec![first, second],
2244            parent_check: FixedBytes::<20>::from_slice(&parent_hash[..20]),
2245            l1_origin_check: FixedBytes::<20>::from_slice(&l1_block_hash[..20]),
2246            ..Default::default()
2247        };
2248        assert_eq!(
2249            batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await,
2250            BatchValidity::Accept
2251        );
2252        assert!(trace_store.is_empty());
2253    }
2254}