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