kona_protocol/batch/
span.rs

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