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