From 347c378b2e8be66be3e6a07c7166508dd10e0efa Mon Sep 17 00:00:00 2001 From: refcell Date: Tue, 29 Oct 2024 13:31:06 -0400 Subject: [PATCH] feat(derive): use upstream op-alloy batch types --- Cargo.lock | 1 - bin/client/src/l1/driver.rs | 2 +- bin/client/src/l2/chain_provider.rs | 7 +- crates/derive-alloy/src/alloy_providers.rs | 7 +- crates/derive/Cargo.toml | 1 - crates/derive/src/batch/batch_type.rs | 67 - crates/derive/src/batch/mod.rs | 130 -- crates/derive/src/batch/single_batch.rs | 428 ----- crates/derive/src/batch/span_batch/batch.rs | 1656 ----------------- crates/derive/src/batch/span_batch/bits.rs | 224 --- crates/derive/src/batch/span_batch/element.rs | 52 - crates/derive/src/batch/span_batch/errors.rs | 77 - crates/derive/src/batch/span_batch/mod.rs | 51 - crates/derive/src/batch/span_batch/payload.rs | 196 -- crates/derive/src/batch/span_batch/prefix.rs | 97 - crates/derive/src/batch/span_batch/raw.rs | 173 -- .../derive/src/batch/span_batch/signature.rs | 28 - .../src/batch/span_batch/transactions.rs | 463 ----- .../src/batch/span_batch/tx_data/eip1559.rs | 85 - .../src/batch/span_batch/tx_data/eip2930.rs | 78 - .../src/batch/span_batch/tx_data/legacy.rs | 106 -- .../src/batch/span_batch/tx_data/mod.rs | 13 - .../src/batch/span_batch/tx_data/wrapper.rs | 131 -- crates/derive/src/batch/span_batch/utils.rs | 141 -- crates/derive/src/batch/validity.rs | 38 - crates/derive/src/errors.rs | 3 +- crates/derive/src/lib.rs | 1 - crates/derive/src/stages/attributes_queue.rs | 3 +- .../derive/src/stages/batch/batch_provider.rs | 3 +- crates/derive/src/stages/batch/batch_queue.rs | 7 +- .../derive/src/stages/batch/batch_stream.rs | 7 +- .../src/stages/batch/batch_validator.rs | 6 +- crates/derive/src/stages/batch/mod.rs | 14 +- .../src/stages/channel/channel_reader.rs | 3 +- crates/derive/src/stages/channel/mod.rs | 2 +- .../derive/src/test_utils/attributes_queue.rs | 3 +- .../derive/src/test_utils/batch_provider.rs | 3 +- crates/derive/src/test_utils/batch_stream.rs | 3 +- .../derive/src/test_utils/chain_providers.rs | 7 +- .../src/test_utils/sys_config_fetcher.rs | 23 +- crates/derive/src/traits/attributes.rs | 4 +- crates/derive/src/traits/providers.rs | 16 +- crates/derive/testdata/raw_batch.hex | Bin 23886 -> 0 bytes 43 files changed, 58 insertions(+), 4302 deletions(-) delete mode 100644 crates/derive/src/batch/batch_type.rs delete mode 100644 crates/derive/src/batch/mod.rs delete mode 100644 crates/derive/src/batch/single_batch.rs delete mode 100644 crates/derive/src/batch/span_batch/batch.rs delete mode 100644 crates/derive/src/batch/span_batch/bits.rs delete mode 100644 crates/derive/src/batch/span_batch/element.rs delete mode 100644 crates/derive/src/batch/span_batch/errors.rs delete mode 100644 crates/derive/src/batch/span_batch/mod.rs delete mode 100644 crates/derive/src/batch/span_batch/payload.rs delete mode 100644 crates/derive/src/batch/span_batch/prefix.rs delete mode 100644 crates/derive/src/batch/span_batch/raw.rs delete mode 100644 crates/derive/src/batch/span_batch/signature.rs delete mode 100644 crates/derive/src/batch/span_batch/transactions.rs delete mode 100644 crates/derive/src/batch/span_batch/tx_data/eip1559.rs delete mode 100644 crates/derive/src/batch/span_batch/tx_data/eip2930.rs delete mode 100644 crates/derive/src/batch/span_batch/tx_data/legacy.rs delete mode 100644 crates/derive/src/batch/span_batch/tx_data/mod.rs delete mode 100644 crates/derive/src/batch/span_batch/tx_data/wrapper.rs delete mode 100644 crates/derive/src/batch/span_batch/utils.rs delete mode 100644 crates/derive/src/batch/validity.rs delete mode 100644 crates/derive/testdata/raw_batch.hex diff --git a/Cargo.lock b/Cargo.lock index c5bd2d516..ebdfa3de7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2257,7 +2257,6 @@ dependencies = [ "tokio", "tracing", "tracing-subscriber", - "unsigned-varint", ] [[package]] diff --git a/bin/client/src/l1/driver.rs b/bin/client/src/l1/driver.rs index 431e8d0b6..1bf177848 100644 --- a/bin/client/src/l1/driver.rs +++ b/bin/client/src/l1/driver.rs @@ -29,7 +29,7 @@ use kona_mpt::{TrieHinter, TrieProvider}; use kona_preimage::{CommsClient, PreimageKey, PreimageKeyType}; use op_alloy_consensus::OpTxType; use op_alloy_genesis::RollupConfig; -use op_alloy_protocol::{BlockInfo, L2BlockInfo}; +use op_alloy_protocol::{BatchValidationProvider, BlockInfo, L2BlockInfo}; use op_alloy_rpc_types_engine::OpAttributesWithParent; use tracing::{error, info, warn}; diff --git a/bin/client/src/l2/chain_provider.rs b/bin/client/src/l2/chain_provider.rs index 94d40b244..2a7a1e5c4 100644 --- a/bin/client/src/l2/chain_provider.rs +++ b/bin/client/src/l2/chain_provider.rs @@ -13,7 +13,7 @@ use kona_mpt::{OrderedListWalker, TrieHinter, TrieProvider}; use kona_preimage::{CommsClient, PreimageKey, PreimageKeyType}; use op_alloy_consensus::{OpBlock, OpTxEnvelope}; use op_alloy_genesis::{RollupConfig, SystemConfig}; -use op_alloy_protocol::{to_system_config, L2BlockInfo}; +use op_alloy_protocol::{to_system_config, BatchValidationProvider, L2BlockInfo}; /// The oracle-backed L2 chain provider for the client program. #[derive(Debug, Clone)] @@ -71,7 +71,7 @@ impl OracleL2ChainProvider { } #[async_trait] -impl L2ChainProvider for OracleL2ChainProvider { +impl BatchValidationProvider for OracleL2ChainProvider { type Error = anyhow::Error; async fn l2_block_info_by_number(&mut self, number: u64) -> Result { @@ -116,7 +116,10 @@ impl L2ChainProvider for OracleL2ChainProvider }; Ok(optimism_block) } +} +#[async_trait] +impl L2ChainProvider for OracleL2ChainProvider { async fn system_config_by_number( &mut self, number: u64, diff --git a/crates/derive-alloy/src/alloy_providers.rs b/crates/derive-alloy/src/alloy_providers.rs index 848d97635..da1601c16 100644 --- a/crates/derive-alloy/src/alloy_providers.rs +++ b/crates/derive-alloy/src/alloy_providers.rs @@ -10,7 +10,7 @@ use kona_derive::traits::{ChainProvider, L2ChainProvider}; use lru::LruCache; use op_alloy_consensus::OpBlock; use op_alloy_genesis::{RollupConfig, SystemConfig}; -use op_alloy_protocol::{to_system_config, BlockInfo, L2BlockInfo}; +use op_alloy_protocol::{to_system_config, BatchValidationProvider, BlockInfo, L2BlockInfo}; use std::{boxed::Box, num::NonZeroUsize, sync::Arc, vec::Vec}; const CACHE_SIZE: usize = 16; @@ -206,7 +206,7 @@ impl AlloyL2ChainProvider { } #[async_trait] -impl L2ChainProvider for AlloyL2ChainProvider { +impl BatchValidationProvider for AlloyL2ChainProvider { type Error = RpcError; async fn l2_block_info_by_number(&mut self, number: u64) -> Result { @@ -234,7 +234,10 @@ impl L2ChainProvider for AlloyL2ChainProvider { self.block_by_number_cache.put(number, block.clone()); Ok(block) } +} +#[async_trait] +impl L2ChainProvider for AlloyL2ChainProvider { async fn system_config_by_number( &mut self, number: u64, diff --git a/crates/derive/Cargo.toml b/crates/derive/Cargo.toml index e1554acaa..a3605edc4 100644 --- a/crates/derive/Cargo.toml +++ b/crates/derive/Cargo.toml @@ -30,7 +30,6 @@ brotli.workspace = true tracing.workspace = true miniz_oxide.workspace = true async-trait.workspace = true -unsigned-varint.workspace = true alloc-no-stdlib.workspace = true derive_more = { workspace = true, features = ["full"] } diff --git a/crates/derive/src/batch/batch_type.rs b/crates/derive/src/batch/batch_type.rs deleted file mode 100644 index b3797188d..000000000 --- a/crates/derive/src/batch/batch_type.rs +++ /dev/null @@ -1,67 +0,0 @@ -//! Contains the [BatchType] and its encodings. - -use alloy_rlp::{Decodable, Encodable}; - -/// The single batch type identifier. -pub(crate) const SINGLE_BATCH_TYPE: u8 = 0x00; - -/// The span batch type identifier. -pub(crate) const SPAN_BATCH_TYPE: u8 = 0x01; - -/// The Batch Type. -#[derive(Debug, Clone, PartialEq, Eq)] -#[repr(u8)] -pub enum BatchType { - /// Single Batch. - Single = SINGLE_BATCH_TYPE, - /// Span Batch. - Span = SPAN_BATCH_TYPE, -} - -impl From for BatchType { - fn from(val: u8) -> Self { - match val { - SINGLE_BATCH_TYPE => Self::Single, - SPAN_BATCH_TYPE => Self::Span, - _ => panic!("Invalid batch type: {val}"), - } - } -} - -impl From<&[u8]> for BatchType { - fn from(buf: &[u8]) -> Self { - Self::from(buf[0]) - } -} - -impl Encodable for BatchType { - fn encode(&self, out: &mut dyn alloy_rlp::BufMut) { - let val = match self { - Self::Single => SINGLE_BATCH_TYPE, - Self::Span => SPAN_BATCH_TYPE, - }; - val.encode(out); - } -} - -impl Decodable for BatchType { - fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { - let val = u8::decode(buf)?; - Ok(Self::from(val)) - } -} - -#[cfg(test)] -mod test { - use super::*; - use alloc::vec::Vec; - - #[test] - fn test_batch_type_rlp_roundtrip() { - let batch_type = BatchType::Single; - let mut buf = Vec::new(); - batch_type.encode(&mut buf); - let decoded = BatchType::decode(&mut buf.as_slice()).unwrap(); - assert_eq!(batch_type, decoded); - } -} diff --git a/crates/derive/src/batch/mod.rs b/crates/derive/src/batch/mod.rs deleted file mode 100644 index 49fbf213b..000000000 --- a/crates/derive/src/batch/mod.rs +++ /dev/null @@ -1,130 +0,0 @@ -//! This module contains the batch types for the OP Stack derivation pipeline: [SpanBatch] & -//! [SingleBatch]. - -use crate::traits::L2ChainProvider; -use alloy_rlp::{Buf, Decodable}; -use op_alloy_genesis::RollupConfig; -use op_alloy_protocol::{BlockInfo, L2BlockInfo}; - -use crate::errors::PipelineEncodingError; - -mod batch_type; -pub use batch_type::BatchType; - -mod validity; -pub use validity::BatchValidity; - -mod span_batch; -pub use span_batch::{ - RawSpanBatch, SpanBatch, SpanBatchBits, SpanBatchEip1559TransactionData, - SpanBatchEip2930TransactionData, SpanBatchElement, SpanBatchError, - SpanBatchLegacyTransactionData, SpanBatchPayload, SpanBatchPrefix, SpanBatchSignature, - SpanBatchTransactionData, SpanBatchTransactions, SpanDecodingError, MAX_SPAN_BATCH_ELEMENTS, -}; - -mod single_batch; -pub use single_batch::SingleBatch; - -/// A batch with its inclusion block. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct BatchWithInclusionBlock { - /// The inclusion block - pub inclusion_block: BlockInfo, - /// The batch - pub batch: Batch, -} - -impl BatchWithInclusionBlock { - /// Creates a new batch with inclusion block. - pub const fn new(inclusion_block: BlockInfo, batch: Batch) -> Self { - Self { inclusion_block, batch } - } - - /// Validates the batch can be applied on top of the specified L2 safe head. - /// The first entry of the l1_blocks should match the origin of the l2_safe_head. - /// One or more consecutive l1_blocks should be provided. - /// In case of only a single L1 block, the decision whether a batch is valid may have to stay - /// undecided. - pub async fn check_batch( - &self, - cfg: &RollupConfig, - l1_blocks: &[BlockInfo], - l2_safe_head: L2BlockInfo, - fetcher: &mut BF, - ) -> BatchValidity { - match &self.batch { - Batch::Single(single_batch) => { - single_batch.check_batch(cfg, l1_blocks, l2_safe_head, &self.inclusion_block) - } - Batch::Span(span_batch) => { - span_batch - .check_batch(cfg, l1_blocks, l2_safe_head, &self.inclusion_block, fetcher) - .await - } - } - } -} - -/// A Batch. -#[derive(Debug, Clone, PartialEq, Eq)] -#[allow(clippy::large_enum_variant)] -pub enum Batch { - /// A single batch - Single(SingleBatch), - /// Span Batches - Span(SpanBatch), -} - -impl Batch { - /// Returns the timestamp for the batch. - pub fn timestamp(&self) -> u64 { - match self { - Self::Single(sb) => sb.timestamp, - Self::Span(sb) => sb.starting_timestamp(), - } - } - - /// Attempts to decode a batch from a reader. - pub fn decode(r: &mut &[u8], cfg: &RollupConfig) -> Result { - if r.is_empty() { - return Err(PipelineEncodingError::EmptyBuffer); - } - - // Read the batch type - let batch_type = BatchType::from(r[0]); - r.advance(1); - - match batch_type { - BatchType::Single => { - let single_batch = - SingleBatch::decode(r).map_err(PipelineEncodingError::AlloyRlpError)?; - Ok(Self::Single(single_batch)) - } - BatchType::Span => { - let mut raw_span_batch = RawSpanBatch::decode(r)?; - let span_batch = raw_span_batch - .derive(cfg.block_time, cfg.genesis.l2_time, cfg.l2_chain_id) - .map_err(PipelineEncodingError::SpanBatchError)?; - Ok(Self::Span(span_batch)) - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use alloc::vec; - - #[test] - fn test_timestamp() { - let single_batch = SingleBatch { timestamp: 123, ..Default::default() }; - let span_batch = SpanBatch { - batches: vec![SpanBatchElement { timestamp: 456, ..Default::default() }], - ..Default::default() - }; - - assert_eq!(Batch::Single(single_batch).timestamp(), 123); - assert_eq!(Batch::Span(span_batch).timestamp(), 456); - } -} diff --git a/crates/derive/src/batch/single_batch.rs b/crates/derive/src/batch/single_batch.rs deleted file mode 100644 index fafa9cd7a..000000000 --- a/crates/derive/src/batch/single_batch.rs +++ /dev/null @@ -1,428 +0,0 @@ -//! This module contains the [SingleBatch] type. - -use super::validity::BatchValidity; -use alloc::vec::Vec; -use alloy_eips::BlockNumHash; -use alloy_primitives::{BlockHash, Bytes}; -use alloy_rlp::{RlpDecodable, RlpEncodable}; -use op_alloy_genesis::RollupConfig; -use op_alloy_protocol::{starts_with_2718_deposit, BlockInfo, L2BlockInfo}; -use tracing::{info, warn}; - -/// Represents a single batch: a single encoded L2 block -#[derive(Debug, Default, RlpDecodable, RlpEncodable, Clone, PartialEq, Eq)] -pub struct SingleBatch { - /// Block hash of the previous L2 block. `B256::ZERO` if it has not been set by the Batch - /// Queue. - pub parent_hash: BlockHash, - /// The batch epoch number. Same as the first L1 block number in the epoch. - pub epoch_num: u64, - /// The block hash of the first L1 block in the epoch - pub epoch_hash: BlockHash, - /// The L2 block timestamp of this batch - pub timestamp: u64, - /// The L2 block transactions in this batch - pub transactions: Vec, -} - -impl SingleBatch { - /// If any transactions are empty or deposited transaction types. - pub fn has_invalid_transactions(&self) -> bool { - self.transactions.iter().any(|tx| tx.0.is_empty() || tx.0[0] == 0x7E) - } - - /// Returns the [BlockNumHash] of the batch. - pub const fn epoch(&self) -> BlockNumHash { - BlockNumHash { number: self.epoch_num, hash: self.epoch_hash } - } - - /// Checks if the batch is valid. - pub fn check_batch( - &self, - cfg: &RollupConfig, - l1_blocks: &[BlockInfo], - l2_safe_head: L2BlockInfo, - inclusion_block: &BlockInfo, - ) -> BatchValidity { - // Sanity check input consistency - if l1_blocks.is_empty() { - warn!("missing L1 block input, cannot proceed with batch checking"); - return BatchValidity::Undecided; - } - - let epoch = l1_blocks[0]; - let next_timestamp = l2_safe_head.block_info.timestamp + cfg.block_time; - if self.timestamp > next_timestamp { - info!("received out-of-order batch for future processing after next batch"); - tracing::debug!( - "Next timestamp: {}, self timestamp {}", - next_timestamp, - self.timestamp - ); - - if cfg.is_holocene_active(inclusion_block.timestamp) { - return BatchValidity::Drop; - } - return BatchValidity::Future; - } - if self.timestamp < next_timestamp { - warn!("dropping batch with old timestamp, min_timestamp: {next_timestamp}"); - return if cfg.is_holocene_active(inclusion_block.timestamp) { - BatchValidity::Past - } else { - BatchValidity::Drop - }; - } - - // Dependent on the above timestamp check. - // If the timestamp is correct, then it must build on top of the safe head. - if self.parent_hash != l2_safe_head.block_info.hash { - let h = l2_safe_head.block_info.hash; - warn!("ignoring batch with mismatching parent hash, current_safe_head: {h}"); - return BatchValidity::Drop; - } - - // Filter out batches that were included too late. - if self.epoch_num + cfg.seq_window_size < inclusion_block.number { - warn!("batch was included too late, sequence window expired"); - return BatchValidity::Drop; - } - - // Check the L1 origin of the batch - let mut batch_origin = epoch; - if self.epoch_num < epoch.number { - warn!("dropped batch, epoch is too old, minimum: {:?}", epoch.id()); - return BatchValidity::Drop; - } else if self.epoch_num == epoch.number { - // Batch is sticking to the current epoch, continue. - } else if self.epoch_num == epoch.number + 1 { - // With only 1 l1Block we cannot look at the next L1 Origin. - // Note: This means that we are unable to determine validity of a batch - // without more information. In this case we should bail out until we have - // more information otherwise the eager algorithm may diverge from a non-eager - // algorithm. - if l1_blocks.len() < 2 { - info!("eager batch wants to advance epoch, but could not without more L1 blocks at epoch: {:?}", epoch.id()); - return BatchValidity::Undecided; - } - batch_origin = l1_blocks[1]; - } else { - warn!("dropped batch, epoch is too far ahead, maximum: {:?}", epoch.id()); - return BatchValidity::Drop; - } - - // Validate the batch epoch hash - if self.epoch_hash != batch_origin.hash { - warn!("dropped batch, epoch hash does not match, expected: {:?}", batch_origin.id()); - return BatchValidity::Drop; - } - - if self.timestamp < batch_origin.timestamp { - warn!("dropped batch, batch timestamp is less than L1 origin timestamp, l2_timestamp: {}, l1_timestamp: {}, origin: {:?}", self.timestamp, batch_origin.timestamp, batch_origin.id()); - return BatchValidity::Drop; - } - - // Check if we ran out of sequencer time drift - let max_drift = cfg.max_sequencer_drift(batch_origin.timestamp); - let max = if let Some(max) = batch_origin.timestamp.checked_add(max_drift) { - max - } else { - warn!("dropped batch, timestamp exceeds configured max sequencer drift, origin timestamp: {}, max drift: {}", batch_origin.timestamp, max_drift); - return BatchValidity::Drop; - }; - - let no_txs = self.transactions.is_empty(); - if self.timestamp > max && !no_txs { - // If the sequencer is ignoring the time drift rule, then drop the batch and force an - // empty batch instead, as the sequencer is not allowed to include anything - // past this point without moving to the next epoch. - warn!("batch exceeded sequencer time drift, sequencer must adopt new L1 origin to include transactions again, max time: {max}"); - return BatchValidity::Drop; - } - if self.timestamp > max && no_txs { - // If the sequencer is co-operating by producing an empty batch, - // allow the batch if it was the right thing to do to maintain the L2 time >= L1 time - // invariant. Only check batches that do not advance the epoch, to ensure - // epoch advancement regardless of time drift is allowed. - if epoch.number == batch_origin.number { - if l1_blocks.len() < 2 { - info!("without the next L1 origin we cannot determine yet if this empty batch that exceeds the time drift is still valid"); - return BatchValidity::Undecided; - } - let next_origin = l1_blocks[1]; - // Check if the next L1 Origin could have been adopted - if self.timestamp >= next_origin.timestamp { - info!("empty batch that exceeds the time drift without adopting next L1 origin, dropping"); - return BatchValidity::Drop; - } else { - info!("empty batch continuation, preserving L2 time invariant"); - } - } - } - - // We can do this check earlier, but it's intensive so we do it last for the sad-path. - for (i, tx) in self.transactions.iter().enumerate() { - if tx.is_empty() { - warn!("transaction data must not be empty, but found empty tx at index {i}"); - return BatchValidity::Drop; - } - if starts_with_2718_deposit(tx) { - warn!("sequencers may not embed any deposits into batch data, but found tx that has one at index: {i}"); - return BatchValidity::Drop; - } - } - - BatchValidity::Accept - } -} - -#[cfg(test)] -mod tests { - use super::*; - use alloc::vec; - use alloy_primitives::{hex, Bytes, B256}; - use alloy_rlp::{BytesMut, Decodable, Encodable}; - - #[test] - fn test_check_batch_empty() { - let single_batch = SingleBatch { - parent_hash: B256::ZERO, - epoch_num: 0xFF, - epoch_hash: B256::ZERO, - timestamp: 0xEE, - transactions: vec![Bytes::from(hex!("00"))], - }; - - let cfg = Default::default(); - let l1_blocks = vec![]; - let l2_safe_head = L2BlockInfo::default(); - let inclusion_block = BlockInfo::default(); - - let validity = single_batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block); - assert_eq!(validity, BatchValidity::Undecided); - } - - #[test] - fn test_drop_bad_parent_hash() { - let single_batch = SingleBatch { - parent_hash: B256::from([1u8; 32]), - epoch_num: 0xFF, - epoch_hash: B256::ZERO, - timestamp: 0x00, - transactions: vec![Bytes::from(hex!("00"))], - }; - - let cfg = Default::default(); - let l1_blocks = vec![BlockInfo::default()]; - let l2_safe_head = L2BlockInfo::default(); - let inclusion_block = BlockInfo::default(); - - let validity = single_batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block); - assert_eq!(validity, BatchValidity::Drop); - } - - #[test] - fn test_drop_prior_to_inclusion_block() { - let single_batch = SingleBatch { - parent_hash: B256::ZERO, - epoch_num: 0xFF, - epoch_hash: B256::ZERO, - timestamp: 0x00, - transactions: vec![Bytes::from(hex!("00"))], - }; - - // epoch_num (255) + seq_window_size (44) = 299 < 300 - let cfg = RollupConfig { seq_window_size: 44, ..Default::default() }; - let l1_blocks = vec![BlockInfo::default()]; - let l2_safe_head = L2BlockInfo::default(); - let inclusion_block = BlockInfo { number: 300, ..Default::default() }; - - let validity = single_batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block); - assert_eq!(validity, BatchValidity::Drop); - } - - #[test] - fn test_drop_old_batch() { - let single_batch = SingleBatch { - parent_hash: B256::ZERO, - epoch_num: 0xFF, - epoch_hash: B256::ZERO, - timestamp: 0x00, - transactions: vec![Bytes::from(hex!("00"))], - }; - - let cfg = RollupConfig { block_time: 2, ..Default::default() }; - let l1_blocks = vec![BlockInfo::default()]; - let l2_safe_head = L2BlockInfo::default(); - let inclusion_block = BlockInfo { number: 10, ..Default::default() }; - - let validity = single_batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block); - assert_eq!(validity, BatchValidity::Drop); - } - - #[test] - fn test_drop_old_batch_holocene() { - let single_batch = SingleBatch { - parent_hash: B256::ZERO, - epoch_num: 0xFF, - epoch_hash: B256::ZERO, - timestamp: 0x00, - transactions: vec![Bytes::from(hex!("00"))], - }; - - let cfg = RollupConfig { holocene_time: Some(0), block_time: 2, ..Default::default() }; - let l1_blocks = vec![BlockInfo::default()]; - let l2_safe_head = L2BlockInfo::default(); - let inclusion_block = BlockInfo { number: 10, ..Default::default() }; - - let validity = single_batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block); - assert_eq!(validity, BatchValidity::Past); - } - - #[test] - fn test_drop_prior_epoch_num() { - let single_batch = SingleBatch { - parent_hash: B256::ZERO, - epoch_num: 0xFF, - epoch_hash: B256::ZERO, - timestamp: 0x00, - transactions: vec![Bytes::from(hex!("00"))], - }; - - let cfg = RollupConfig { seq_window_size: 100, ..Default::default() }; - let l1_blocks = vec![BlockInfo { number: 300, ..Default::default() }]; - let l2_safe_head = L2BlockInfo::default(); - let inclusion_block = BlockInfo { number: 300, ..Default::default() }; - - let validity = single_batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block); - assert_eq!(validity, BatchValidity::Drop); - } - - #[test] - fn test_undecided_epoch_num_plus_one_too_few_l1_blocks() { - let single_batch = SingleBatch { - parent_hash: B256::ZERO, - epoch_num: 0xFF, - epoch_hash: B256::ZERO, - timestamp: 0x00, - transactions: vec![Bytes::from(hex!("00"))], - }; - - let cfg = RollupConfig { seq_window_size: 100, ..Default::default() }; - let l1_blocks = vec![BlockInfo { number: 254, ..Default::default() }]; - let l2_safe_head = L2BlockInfo::default(); - let inclusion_block = BlockInfo { number: 300, ..Default::default() }; - - let validity = single_batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block); - assert_eq!(validity, BatchValidity::Undecided); - } - - #[test] - fn test_drop_epoch_num_plus_one_bad_hash() { - let single_batch = SingleBatch { - parent_hash: B256::ZERO, - epoch_num: 0xFF, - epoch_hash: B256::ZERO, - timestamp: 0x00, - transactions: vec![Bytes::from(hex!("00"))], - }; - - let cfg = RollupConfig { seq_window_size: 100, ..Default::default() }; - let l1_blocks = vec![ - BlockInfo { number: 254, ..Default::default() }, - BlockInfo { number: 255, hash: B256::from([1u8; 32]), ..Default::default() }, - ]; - let l2_safe_head = L2BlockInfo::default(); - let inclusion_block = BlockInfo { number: 300, ..Default::default() }; - - let validity = single_batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block); - assert_eq!(validity, BatchValidity::Drop); - } - - #[test] - fn test_drop_later_epoch_number() { - let single_batch = SingleBatch { - parent_hash: B256::ZERO, - epoch_num: 0xFF, - epoch_hash: B256::ZERO, - timestamp: 0x00, - transactions: vec![Bytes::from(hex!("00"))], - }; - - let cfg = RollupConfig { seq_window_size: 200, ..Default::default() }; - let l1_blocks = vec![BlockInfo { number: 200, ..Default::default() }]; - let l2_safe_head = L2BlockInfo::default(); - let inclusion_block = BlockInfo { number: 300, ..Default::default() }; - - let validity = single_batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block); - assert_eq!(validity, BatchValidity::Drop); - } - - #[test] - fn test_drop_invalid_epoch_hash() { - let single_batch = SingleBatch { - parent_hash: B256::ZERO, - epoch_num: 0xFF, - epoch_hash: B256::from([1u8; 32]), - timestamp: 0x00, - transactions: vec![Bytes::from(hex!("00"))], - }; - - let cfg = RollupConfig { seq_window_size: 200, ..Default::default() }; - let l1_blocks = vec![BlockInfo { number: 0xFF, ..Default::default() }]; - let l2_safe_head = L2BlockInfo::default(); - let inclusion_block = BlockInfo { number: 300, ..Default::default() }; - - let validity = single_batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block); - assert_eq!(validity, BatchValidity::Drop); - } - - #[test] - fn test_drop_early_epoch_timestamp() { - let single_batch = SingleBatch { - parent_hash: B256::ZERO, - epoch_num: 0xFF, - epoch_hash: B256::ZERO, - timestamp: 0x00, - transactions: vec![Bytes::from(hex!("00"))], - }; - - let cfg = RollupConfig { seq_window_size: 200, ..Default::default() }; - let l1_blocks = vec![BlockInfo { number: 0xFF, timestamp: 1, ..Default::default() }]; - let l2_safe_head = L2BlockInfo::default(); - let inclusion_block = BlockInfo { number: 300, ..Default::default() }; - - let validity = single_batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block); - assert_eq!(validity, BatchValidity::Drop); - } - - #[test] - fn test_single_batch_rlp_roundtrip() { - let single_batch = SingleBatch { - parent_hash: B256::ZERO, - epoch_num: 0xFF, - epoch_hash: B256::ZERO, - timestamp: 0xEE, - transactions: vec![Bytes::from(hex!("00"))], - }; - - let mut out_buf = BytesMut::default(); - single_batch.encode(&mut out_buf); - let decoded = SingleBatch::decode(&mut out_buf.as_ref()).unwrap(); - assert_eq!(decoded, single_batch); - assert!(!single_batch.has_invalid_transactions()); - } - - #[test] - fn test_single_batch_invalid_transactions() { - let single_batch = SingleBatch { - parent_hash: B256::ZERO, - epoch_num: 0xFF, - epoch_hash: B256::ZERO, - timestamp: 0xEE, - transactions: vec![Bytes::from(hex!("7E"))], - }; - - assert!(single_batch.has_invalid_transactions()); - } -} diff --git a/crates/derive/src/batch/span_batch/batch.rs b/crates/derive/src/batch/span_batch/batch.rs deleted file mode 100644 index f8d5619bb..000000000 --- a/crates/derive/src/batch/span_batch/batch.rs +++ /dev/null @@ -1,1656 +0,0 @@ -//! The Span Batch Type - -use crate::traits::L2ChainProvider; -use alloc::vec::Vec; -use alloy_eips::eip2718::Encodable2718; -use alloy_primitives::FixedBytes; -use op_alloy_consensus::OpTxType; -use op_alloy_genesis::RollupConfig; -use op_alloy_protocol::{BlockInfo, L2BlockInfo}; -use tracing::{info, warn}; - -use super::{SpanBatchBits, SpanBatchElement, SpanBatchError, SpanBatchTransactions}; - -use crate::batch::{BatchValidity, SingleBatch}; - -/// The span batch contains the input to build a span of L2 blocks in derived form. -#[derive(Debug, Default, Clone, PartialEq, Eq)] -pub struct SpanBatch { - /// First 20 bytes of the first block's parent hash - pub parent_check: FixedBytes<20>, - /// First 20 bytes of the last block's L1 origin hash - pub l1_origin_check: FixedBytes<20>, - /// Genesis block timestamp - pub genesis_timestamp: u64, - /// Chain ID - pub chain_id: u64, - /// List of block input in derived form - pub batches: Vec, - /// Caching - origin bits - pub origin_bits: SpanBatchBits, - /// Caching - block tx counts - pub block_tx_counts: Vec, - /// Caching - span batch txs - pub txs: SpanBatchTransactions, -} - -impl SpanBatch { - /// Returns the starting timestamp for the first batch in the span. - /// - /// ## Safety - /// Panics if [Self::batches] is empty. - pub fn starting_timestamp(&self) -> u64 { - self.batches[0].timestamp - } - - /// Returns the final timestamp for the last batch in the span. - /// - /// ## Safety - /// Panics if [Self::batches] is empty. - pub fn final_timestamp(&self) -> u64 { - self.batches[self.batches.len() - 1].timestamp - } - - /// Returns the epoch number for the first batch in the span. - /// - /// ## Safety - /// Panics if [Self::batches] is empty. - pub fn starting_epoch_num(&self) -> u64 { - self.batches[0].epoch_num - } - - /// Checks if the first 20 bytes of the given hash match the L1 origin check. - pub fn check_origin_hash(&self, hash: FixedBytes<32>) -> bool { - self.l1_origin_check == hash[..20] - } - - /// Checks if the first 20 bytes of the given hash match the parent check. - pub fn check_parent_hash(&self, hash: FixedBytes<32>) -> bool { - self.parent_check == hash[..20] - } - - /// Checks if the span batch is valid. - pub async fn check_batch( - &self, - cfg: &RollupConfig, - l1_blocks: &[BlockInfo], - l2_safe_head: L2BlockInfo, - inclusion_block: &BlockInfo, - fetcher: &mut BF, - ) -> BatchValidity { - let (prefix_validity, parent_block) = - self.check_batch_prefix(cfg, l1_blocks, l2_safe_head, inclusion_block, fetcher).await; - if !matches!(prefix_validity, BatchValidity::Accept) { - return prefix_validity; - } - - let starting_epoch_num = self.starting_epoch_num(); - let parent_block = parent_block.expect("parent_block must be Some"); - - let mut origin_index = 0; - let mut origin_advanced = starting_epoch_num == parent_block.l1_origin.number + 1; - for (i, batch) in self.batches.iter().enumerate() { - if batch.timestamp <= l2_safe_head.block_info.timestamp { - continue; - } - // Find the L1 origin for the batch. - for (j, j_block) in l1_blocks.iter().enumerate().skip(origin_index) { - if batch.epoch_num == j_block.number { - origin_index = j; - break; - } - } - let l1_origin = l1_blocks[origin_index]; - if i > 0 { - origin_advanced = false; - if batch.epoch_num > self.batches[i - 1].epoch_num { - origin_advanced = true; - } - } - let block_timestamp = batch.timestamp; - if block_timestamp < l1_origin.timestamp { - warn!( - "block timestamp is less than L1 origin timestamp, l2_timestamp: {}, l1_timestamp: {}, origin: {:?}", - block_timestamp, - l1_origin.timestamp, - l1_origin.id() - ); - return BatchValidity::Drop; - } - - // Check if we ran out of sequencer time drift - let max_drift = cfg.max_sequencer_drift(l1_origin.timestamp); - if block_timestamp > l1_origin.timestamp + max_drift { - if batch.transactions.is_empty() { - // If the sequencer is co-operating by producing an empty batch, - // then allow the batch if it was the right thing to do to maintain the L2 time - // >= L1 time invariant. We only check batches that do not - // advance the epoch, to ensure epoch advancement regardless of time drift is - // allowed. - if !origin_advanced { - if origin_index + 1 >= l1_blocks.len() { - info!("without the next L1 origin we cannot determine yet if this empty batch that exceeds the time drift is still valid"); - return BatchValidity::Undecided; - } - if block_timestamp >= l1_blocks[origin_index + 1].timestamp { - // check if the next L1 origin could have been adopted - info!("batch exceeded sequencer time drift without adopting next origin, and next L1 origin would have been valid"); - return BatchValidity::Drop; - } else { - info!("continuing with empty batch before late L1 block to preserve L2 time invariant"); - } - } - } else { - // If the sequencer is ignoring the time drift rule, then drop the batch and - // force an empty batch instead, as the sequencer is not - // allowed to include anything past this point without moving to the next epoch. - warn!( - "batch exceeded sequencer time drift, sequencer must adopt new L1 origin to include transactions again, max_time: {}", - l1_origin.timestamp + max_drift - ); - return BatchValidity::Drop; - } - } - - // Check that the transactions are not empty and do not contain any deposits. - for (tx_index, tx_bytes) in batch.transactions.iter().enumerate() { - if tx_bytes.is_empty() { - warn!( - "transaction data must not be empty, but found empty tx, tx_index: {}", - tx_index - ); - return BatchValidity::Drop; - } - if tx_bytes.0[0] == OpTxType::Deposit as u8 { - warn!("sequencers may not embed any deposits into batch data, but found tx that has one, tx_index: {}", tx_index); - return BatchValidity::Drop; - } - } - } - - // Check overlapped blocks - let parent_num = parent_block.block_info.number; - let next_timestamp = l2_safe_head.block_info.timestamp + cfg.block_time; - if self.starting_timestamp() < next_timestamp { - for i in 0..(l2_safe_head.block_info.number - parent_num) { - let safe_block_num = parent_num + i + 1; - let safe_block_payload = match fetcher.block_by_number(safe_block_num).await { - Ok(p) => p, - Err(e) => { - warn!("failed to fetch block number {safe_block_num}: {e}"); - return BatchValidity::Undecided; - } - }; - let safe_block = &safe_block_payload.body; - let batch_txs = &self.batches[i as usize].transactions; - // Execution payload has deposit txs but batch does not. - let deposit_count: usize = safe_block - .transactions - .iter() - .map(|tx| if tx.is_deposit() { 1 } else { 0 }) - .sum(); - if safe_block.transactions.len() - deposit_count != batch_txs.len() { - warn!( - "overlapped block's tx count does not match, safe_block_txs: {}, batch_txs: {}", - safe_block.transactions.len(), - batch_txs.len() - ); - return BatchValidity::Drop; - } - let batch_txs_len = batch_txs.len(); - #[allow(clippy::needless_range_loop)] - for j in 0..batch_txs_len { - let mut buf = Vec::new(); - safe_block.transactions[j + deposit_count].encode_2718(&mut buf); - if buf != batch_txs[j].0 { - warn!("overlapped block's transaction does not match"); - return BatchValidity::Drop; - } - } - let safe_block_ref = match L2BlockInfo::from_block_and_genesis( - &safe_block_payload, - &cfg.genesis, - ) { - Ok(r) => r, - Err(e) => { - warn!("failed to extract L2BlockInfo from execution payload, hash: {}, err: {e}", safe_block_payload.header.hash_slow()); - return BatchValidity::Drop; - } - }; - if safe_block_ref.l1_origin.number != self.batches[i as usize].epoch_num { - warn!( - "overlapped block's L1 origin number does not match {}, {}", - safe_block_ref.l1_origin.number, self.batches[i as usize].epoch_num - ); - return BatchValidity::Drop; - } - } - } - - BatchValidity::Accept - } - - /// Checks the validity of the batch's prefix. This function is used in the [BatchStream] - /// post-Holocene as a batch is being loaded in. - /// - /// [BatchStream]: crate::stages::BatchStream - pub async fn check_batch_prefix( - &self, - cfg: &RollupConfig, - l1_origins: &[BlockInfo], - l2_safe_head: L2BlockInfo, - inclusion_block: &BlockInfo, - fetcher: &mut BF, - ) -> (BatchValidity, Option) { - if l1_origins.is_empty() { - warn!("missing L1 block input, cannot proceed with batch checking"); - return (BatchValidity::Undecided, None); - } - if self.batches.is_empty() { - warn!("empty span batch, cannot proceed with batch checking"); - return (BatchValidity::Undecided, None); - } - - let epoch = l1_origins[0]; - let next_timestamp = l2_safe_head.block_info.timestamp + cfg.block_time; - - let starting_epoch_num = self.starting_epoch_num(); - let mut batch_origin = epoch; - if starting_epoch_num == batch_origin.number + 1 { - if l1_origins.len() < 2 { - info!("eager batch wants to advance current epoch {:?}, but could not without more L1 blocks", epoch.id()); - return (BatchValidity::Undecided, None); - } - batch_origin = l1_origins[1]; - } - if !cfg.is_delta_active(batch_origin.timestamp) { - warn!( - "received SpanBatch (id {:?}) with L1 origin (timestamp {}) before Delta hard fork", - batch_origin.id(), - batch_origin.timestamp - ); - return (BatchValidity::Drop, None); - } - - if self.starting_timestamp() > next_timestamp { - warn!( - "received out-of-order batch for future processing after next batch ({} > {})", - self.starting_timestamp(), - next_timestamp - ); - - // After holocene is activated, gaps are disallowed. - if cfg.is_holocene_active(inclusion_block.timestamp) { - return (BatchValidity::Drop, None); - } - return (BatchValidity::Future, None); - } - - // Drop the batch if it has no new blocks after the safe head. - if self.final_timestamp() < next_timestamp { - warn!("span batch has no new blocks after safe head"); - return if cfg.is_holocene_active(inclusion_block.timestamp) { - (BatchValidity::Past, None) - } else { - (BatchValidity::Drop, None) - }; - } - - // Find the parent block of the span batch. - // If the span batch does not overlap the current safe chain, parent block should be the L2 - // safe head. - let mut parent_num = l2_safe_head.block_info.number; - let mut parent_block = l2_safe_head; - if self.starting_timestamp() < next_timestamp { - if self.starting_timestamp() > l2_safe_head.block_info.timestamp { - // Batch timestamp cannot be between safe head and next timestamp. - warn!("batch has misaligned timestamp, block time is too short"); - return (BatchValidity::Drop, None); - } - if (l2_safe_head.block_info.timestamp - self.starting_timestamp()) % cfg.block_time != 0 - { - warn!("batch has misaligned timestamp, not overlapped exactly"); - return (BatchValidity::Drop, None); - } - parent_num = l2_safe_head.block_info.number - - (l2_safe_head.block_info.timestamp - self.starting_timestamp()) / cfg.block_time - - 1; - parent_block = match fetcher.l2_block_info_by_number(parent_num).await { - Ok(block) => block, - Err(e) => { - warn!("failed to fetch L2 block number {parent_num}: {e}"); - // Unable to validate the batch for now. Retry later. - return (BatchValidity::Undecided, None); - } - }; - } - if !self.check_parent_hash(parent_block.block_info.hash) { - warn!( - "parent block mismatch, expected: {parent_num}, received: {}. parent hash: {}, parent hash check: {}", - parent_block.block_info.number, - parent_block.block_info.hash, - self.parent_check, - ); - return (BatchValidity::Drop, None); - } - - // Filter out batches that were included too late. - if starting_epoch_num + cfg.seq_window_size < inclusion_block.number { - warn!("batch was included too late, sequence window expired"); - return (BatchValidity::Drop, None); - } - - // Check the L1 origin of the batch - if starting_epoch_num > parent_block.l1_origin.number + 1 { - warn!( - "batch is for future epoch too far ahead, while it has the next timestamp, so it must be invalid. starting epoch: {} | next epoch: {}", - starting_epoch_num, - parent_block.l1_origin.number + 1 - ); - return (BatchValidity::Drop, None); - } - - // Verify the l1 origin hash for each l1 block. - // SAFETY: `Self::batches` is not empty, so the last element is guaranteed to exist. - let end_epoch_num = self.batches.last().unwrap().epoch_num; - let mut origin_checked = false; - // l1Blocks is supplied from batch queue and its length is limited to SequencerWindowSize. - for l1_block in l1_origins { - if l1_block.number == end_epoch_num { - if !self.check_origin_hash(l1_block.hash) { - warn!( - "batch is for different L1 chain, epoch hash does not match, expected: {}", - l1_block.hash - ); - return (BatchValidity::Drop, None); - } - origin_checked = true; - break; - } - } - if !origin_checked { - info!("need more l1 blocks to check entire origins of span batch"); - return (BatchValidity::Undecided, None); - } - - if starting_epoch_num < parent_block.l1_origin.number { - warn!("dropped batch, epoch is too old, minimum: {:?}", parent_block.block_info.id()); - return (BatchValidity::Drop, None); - } - - (BatchValidity::Accept, Some(parent_block)) - } - - /// Converts all [SpanBatchElement]s after the L2 safe head to [SingleBatch]es. The resulting - /// [SingleBatch]es do not contain a parent hash, as it is populated by the Batch Queue - /// stage. - pub fn get_singular_batches( - &self, - l1_origins: &[BlockInfo], - l2_safe_head: L2BlockInfo, - ) -> Result, SpanBatchError> { - let mut single_batches = Vec::new(); - let mut origin_index = 0; - for batch in &self.batches { - if batch.timestamp <= l2_safe_head.block_info.timestamp { - continue; - } - tracing::info!( - "checking {} l1 origins with first timestamp: {}, batch timestamp: {}, {}", - l1_origins.len(), - l1_origins[0].timestamp, - batch.timestamp, - batch.epoch_num - ); - let origin_epoch_hash = l1_origins[origin_index..l1_origins.len()] - .iter() - .enumerate() - .find(|(_, origin)| origin.number == batch.epoch_num) - .map(|(i, origin)| { - origin_index = i; - origin.hash - }) - .ok_or(SpanBatchError::MissingL1Origin)?; - let single_batch = SingleBatch { - epoch_num: batch.epoch_num, - epoch_hash: origin_epoch_hash, - timestamp: batch.timestamp, - transactions: batch.transactions.clone(), - ..Default::default() - }; - single_batches.push(single_batch); - } - Ok(single_batches) - } - - /// Append a [SingleBatch] to the [SpanBatch]. Updates the L1 origin check if need be. - pub fn append_singular_batch( - &mut self, - singular_batch: SingleBatch, - seq_num: u64, - ) -> Result<(), SpanBatchError> { - // If the new element is not ordered with respect to the last element, panic. - if !self.batches.is_empty() && self.peek(0).timestamp > singular_batch.timestamp { - panic!("Batch is not ordered"); - } - - let SingleBatch { epoch_hash, parent_hash, .. } = singular_batch; - - // Always append the new batch and set the L1 origin check. - self.batches.push(singular_batch.into()); - // Always update the L1 origin check. - self.l1_origin_check = epoch_hash[..20].try_into().expect("Sub-slice cannot fail"); - - let epoch_bit = if self.batches.len() == 1 { - // If there is only one batch, initialize the parent check and set the epoch bit based - // on the sequence number. - self.parent_check = parent_hash[..20].try_into().expect("Sub-slice cannot fail"); - seq_num == 0 - } else { - // If there is more than one batch, set the epoch bit based on the last two batches. - self.peek(1).epoch_num < self.peek(0).epoch_num - }; - - // Set the respective bit in the origin bits. - self.origin_bits.set_bit(self.batches.len() - 1, epoch_bit); - - let new_txs = self.peek(0).transactions.clone(); - - // Update the block tx counts cache with the latest batch's transaction count. - self.block_tx_counts.push(new_txs.len() as u64); - - // Add the new transactions to the transaction cache. - self.txs.add_txs(new_txs, self.chain_id) - } - - /// Peek at the `n`th-to-last last element in the batch. - fn peek(&self, n: usize) -> &SpanBatchElement { - &self.batches[self.batches.len() - 1 - n] - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::test_utils::{CollectingLayer, TestL2ChainProvider, TraceStorage}; - use alloc::vec; - use alloy_consensus::Header; - use alloy_eips::BlockNumHash; - use alloy_primitives::{b256, Bytes}; - use op_alloy_consensus::{OpBlock, OpTxType}; - use op_alloy_genesis::ChainGenesis; - use tracing::Level; - use tracing_subscriber::layer::SubscriberExt; - - #[test] - fn test_timestamp() { - let timestamp = 10; - let first_element = SpanBatchElement { timestamp, ..Default::default() }; - let batch = - SpanBatch { batches: vec![first_element, Default::default()], ..Default::default() }; - assert_eq!(batch.starting_timestamp(), timestamp); - } - - #[test] - fn test_starting_epoch_num() { - let epoch_num = 10; - let first_element = SpanBatchElement { epoch_num, ..Default::default() }; - let batch = - SpanBatch { batches: vec![first_element, Default::default()], ..Default::default() }; - assert_eq!(batch.starting_epoch_num(), epoch_num); - } - - #[test] - fn test_check_origin_hash() { - let l1_origin_check = FixedBytes::from([17u8; 20]); - let hash = b256!("1111111111111111111111111111111111111111000000000000000000000000"); - let batch = SpanBatch { l1_origin_check, ..Default::default() }; - assert!(batch.check_origin_hash(hash)); - // This hash has 19 matching bytes, the other 13 are zeros. - let invalid = b256!("1111111111111111111111111111111111111100000000000000000000000000"); - assert!(!batch.check_origin_hash(invalid)); - } - - #[test] - fn test_check_parent_hash() { - let parent_check = FixedBytes::from([17u8; 20]); - let hash = b256!("1111111111111111111111111111111111111111000000000000000000000000"); - let batch = SpanBatch { parent_check, ..Default::default() }; - assert!(batch.check_parent_hash(hash)); - // This hash has 19 matching bytes, the other 13 are zeros. - let invalid = b256!("1111111111111111111111111111111111111100000000000000000000000000"); - assert!(!batch.check_parent_hash(invalid)); - } - - #[tokio::test] - async fn test_check_batch_missing_l1_block_input() { - let trace_store: TraceStorage = Default::default(); - let layer = CollectingLayer::new(trace_store.clone()); - let subscriber = tracing_subscriber::Registry::default().with(layer); - let _guard = tracing::subscriber::set_default(subscriber); - - let cfg = RollupConfig::default(); - let l1_blocks = vec![]; - let l2_safe_head = L2BlockInfo::default(); - let inclusion_block = BlockInfo::default(); - let mut fetcher = TestL2ChainProvider::default(); - let batch = SpanBatch::default(); - assert_eq!( - batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await, - BatchValidity::Undecided - ); - let logs = trace_store.get_by_level(Level::WARN); - assert_eq!(logs.len(), 1); - assert!(logs[0].contains("missing L1 block input, cannot proceed with batch checking")); - } - - #[tokio::test] - async fn test_check_batches_is_empty() { - let trace_store: TraceStorage = Default::default(); - let layer = CollectingLayer::new(trace_store.clone()); - let subscriber = tracing_subscriber::Registry::default().with(layer); - let _guard = tracing::subscriber::set_default(subscriber); - - let cfg = RollupConfig::default(); - let l1_blocks = vec![BlockInfo::default()]; - let l2_safe_head = L2BlockInfo::default(); - let inclusion_block = BlockInfo::default(); - let mut fetcher = TestL2ChainProvider::default(); - let batch = SpanBatch::default(); - assert_eq!( - batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await, - BatchValidity::Undecided - ); - let logs = trace_store.get_by_level(Level::WARN); - assert_eq!(logs.len(), 1); - assert!(logs[0].contains("empty span batch, cannot proceed with batch checking")); - } - - #[tokio::test] - async fn test_eager_block_missing_origins() { - let trace_store: TraceStorage = Default::default(); - let layer = CollectingLayer::new(trace_store.clone()); - let subscriber = tracing_subscriber::Registry::default().with(layer); - let _guard = tracing::subscriber::set_default(subscriber); - - let cfg = RollupConfig::default(); - let block = BlockInfo { number: 9, ..Default::default() }; - let l1_blocks = vec![block]; - let l2_safe_head = L2BlockInfo::default(); - let inclusion_block = BlockInfo::default(); - let mut fetcher = TestL2ChainProvider::default(); - let first = SpanBatchElement { epoch_num: 10, ..Default::default() }; - let batch = SpanBatch { batches: vec![first], ..Default::default() }; - assert_eq!( - batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await, - BatchValidity::Undecided - ); - let logs = trace_store.get_by_level(Level::INFO); - assert_eq!(logs.len(), 1); - let str = alloc::format!( - "eager batch wants to advance current epoch {:?}, but could not without more L1 blocks", - block.id() - ); - assert!(logs[0].contains(&str)); - } - - #[tokio::test] - async fn test_check_batch_delta_inactive() { - let trace_store: TraceStorage = Default::default(); - let layer = CollectingLayer::new(trace_store.clone()); - let subscriber = tracing_subscriber::Registry::default().with(layer); - let _guard = tracing::subscriber::set_default(subscriber); - - let cfg = RollupConfig { delta_time: Some(10), ..Default::default() }; - let block = BlockInfo { number: 10, timestamp: 9, ..Default::default() }; - let l1_blocks = vec![block]; - let l2_safe_head = L2BlockInfo::default(); - let inclusion_block = BlockInfo::default(); - let mut fetcher = TestL2ChainProvider::default(); - let first = SpanBatchElement { epoch_num: 10, timestamp: 10, ..Default::default() }; - let batch = SpanBatch { batches: vec![first], ..Default::default() }; - assert_eq!( - batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await, - BatchValidity::Drop - ); - let logs = trace_store.get_by_level(Level::WARN); - assert_eq!(logs.len(), 1); - let str = alloc::format!( - "received SpanBatch (id {:?}) with L1 origin (timestamp {}) before Delta hard fork", - block.id(), - block.timestamp - ); - assert!(logs[0].contains(&str)); - } - - #[tokio::test] - async fn test_check_batch_out_of_order() { - let trace_store: TraceStorage = Default::default(); - let layer = CollectingLayer::new(trace_store.clone()); - let subscriber = tracing_subscriber::Registry::default().with(layer); - let _guard = tracing::subscriber::set_default(subscriber); - - let cfg = RollupConfig { delta_time: Some(0), block_time: 10, ..Default::default() }; - let block = BlockInfo { number: 10, timestamp: 10, ..Default::default() }; - let l1_blocks = vec![block]; - let l2_safe_head = L2BlockInfo { - block_info: BlockInfo { timestamp: 10, ..Default::default() }, - ..Default::default() - }; - let inclusion_block = BlockInfo::default(); - let mut fetcher = TestL2ChainProvider::default(); - let first = SpanBatchElement { epoch_num: 10, timestamp: 21, ..Default::default() }; - let batch = SpanBatch { batches: vec![first], ..Default::default() }; - assert_eq!( - batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await, - BatchValidity::Future - ); - let logs = trace_store.get_by_level(Level::WARN); - assert_eq!(logs.len(), 1); - assert!(logs[0].contains( - "received out-of-order batch for future processing after next batch (21 > 20)" - )); - } - - #[tokio::test] - async fn test_check_batch_no_new_blocks() { - let trace_store: TraceStorage = Default::default(); - let layer = CollectingLayer::new(trace_store.clone()); - let subscriber = tracing_subscriber::Registry::default().with(layer); - let _guard = tracing::subscriber::set_default(subscriber); - - let cfg = RollupConfig { delta_time: Some(0), block_time: 10, ..Default::default() }; - let block = BlockInfo { number: 10, timestamp: 10, ..Default::default() }; - let l1_blocks = vec![block]; - let l2_safe_head = L2BlockInfo { - block_info: BlockInfo { timestamp: 10, ..Default::default() }, - ..Default::default() - }; - let inclusion_block = BlockInfo::default(); - let mut fetcher = TestL2ChainProvider::default(); - let first = SpanBatchElement { epoch_num: 10, timestamp: 10, ..Default::default() }; - let batch = SpanBatch { batches: vec![first], ..Default::default() }; - assert_eq!( - batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await, - BatchValidity::Drop - ); - let logs = trace_store.get_by_level(Level::WARN); - assert_eq!(logs.len(), 1); - assert!(logs[0].contains("span batch has no new blocks after safe head")); - } - - #[tokio::test] - async fn test_check_batch_misaligned_timestamp() { - let trace_store: TraceStorage = Default::default(); - let layer = CollectingLayer::new(trace_store.clone()); - let subscriber = tracing_subscriber::Registry::default().with(layer); - let _guard = tracing::subscriber::set_default(subscriber); - - let cfg = RollupConfig { delta_time: Some(0), block_time: 10, ..Default::default() }; - let block = BlockInfo { number: 10, timestamp: 10, ..Default::default() }; - let l1_blocks = vec![block]; - let l2_safe_head = L2BlockInfo { - block_info: BlockInfo { timestamp: 10, ..Default::default() }, - ..Default::default() - }; - let inclusion_block = BlockInfo::default(); - let mut fetcher = TestL2ChainProvider::default(); - let first = SpanBatchElement { epoch_num: 10, timestamp: 11, ..Default::default() }; - let second = SpanBatchElement { epoch_num: 11, timestamp: 21, ..Default::default() }; - let batch = SpanBatch { batches: vec![first, second], ..Default::default() }; - assert_eq!( - batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await, - BatchValidity::Drop - ); - let logs = trace_store.get_by_level(Level::WARN); - assert_eq!(logs.len(), 1); - assert!(logs[0].contains("batch has misaligned timestamp, block time is too short")); - } - - #[tokio::test] - async fn test_check_batch_misaligned_without_overlap() { - let trace_store: TraceStorage = Default::default(); - let layer = CollectingLayer::new(trace_store.clone()); - let subscriber = tracing_subscriber::Registry::default().with(layer); - let _guard = tracing::subscriber::set_default(subscriber); - - let cfg = RollupConfig { delta_time: Some(0), block_time: 10, ..Default::default() }; - let block = BlockInfo { number: 10, timestamp: 10, ..Default::default() }; - let l1_blocks = vec![block]; - let l2_safe_head = L2BlockInfo { - block_info: BlockInfo { timestamp: 10, ..Default::default() }, - ..Default::default() - }; - let inclusion_block = BlockInfo::default(); - let mut fetcher = TestL2ChainProvider::default(); - let first = SpanBatchElement { epoch_num: 10, timestamp: 8, ..Default::default() }; - let second = SpanBatchElement { epoch_num: 11, timestamp: 20, ..Default::default() }; - let batch = SpanBatch { batches: vec![first, second], ..Default::default() }; - assert_eq!( - batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await, - BatchValidity::Drop - ); - let logs = trace_store.get_by_level(Level::WARN); - assert_eq!(logs.len(), 1); - assert!(logs[0].contains("batch has misaligned timestamp, not overlapped exactly")); - } - - #[tokio::test] - async fn test_check_batch_failed_to_fetch_l2_block() { - let trace_store: TraceStorage = Default::default(); - let layer = CollectingLayer::new(trace_store.clone()); - let subscriber = tracing_subscriber::Registry::default().with(layer); - let _guard = tracing::subscriber::set_default(subscriber); - - let cfg = RollupConfig { delta_time: Some(0), block_time: 10, ..Default::default() }; - let block = BlockInfo { number: 10, timestamp: 10, ..Default::default() }; - let l1_blocks = vec![block]; - let l2_safe_head = L2BlockInfo { - block_info: BlockInfo { number: 41, timestamp: 10, ..Default::default() }, - ..Default::default() - }; - let inclusion_block = BlockInfo::default(); - let mut fetcher = TestL2ChainProvider::default(); - let first = SpanBatchElement { epoch_num: 10, timestamp: 10, ..Default::default() }; - let second = SpanBatchElement { epoch_num: 11, timestamp: 20, ..Default::default() }; - let batch = SpanBatch { batches: vec![first, second], ..Default::default() }; - // parent number = 41 - (10 - 10) / 10 - 1 = 40 - assert_eq!( - batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await, - BatchValidity::Undecided - ); - let logs = trace_store.get_by_level(Level::WARN); - assert_eq!(logs.len(), 1); - assert!(logs[0].contains("failed to fetch L2 block number 40: Block not found")); - } - - #[tokio::test] - async fn test_check_batch_parent_hash_fail() { - let trace_store: TraceStorage = Default::default(); - let layer = CollectingLayer::new(trace_store.clone()); - let subscriber = tracing_subscriber::Registry::default().with(layer); - let _guard = tracing::subscriber::set_default(subscriber); - - let cfg = RollupConfig { delta_time: Some(0), block_time: 10, ..Default::default() }; - let block = BlockInfo { number: 10, timestamp: 10, ..Default::default() }; - let l1_blocks = vec![block]; - let l2_safe_head = L2BlockInfo { - block_info: BlockInfo { number: 41, timestamp: 10, ..Default::default() }, - ..Default::default() - }; - let inclusion_block = BlockInfo::default(); - let l2_block = L2BlockInfo { - block_info: BlockInfo { number: 41, timestamp: 10, ..Default::default() }, - l1_origin: BlockNumHash { number: 9, ..Default::default() }, - ..Default::default() - }; - let mut fetcher = TestL2ChainProvider { blocks: vec![l2_block], ..Default::default() }; - fetcher.short_circuit = true; - let first = SpanBatchElement { epoch_num: 10, timestamp: 10, ..Default::default() }; - let second = SpanBatchElement { epoch_num: 11, timestamp: 20, ..Default::default() }; - let batch = SpanBatch { - batches: vec![first, second], - parent_check: FixedBytes::<20>::from_slice( - &b256!("1111111111111111111111111111111111111111000000000000000000000000")[..20], - ), - ..Default::default() - }; - // parent number = 41 - (10 - 10) / 10 - 1 = 40 - assert_eq!( - batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await, - BatchValidity::Drop - ); - let logs = trace_store.get_by_level(Level::WARN); - assert_eq!(logs.len(), 1); - assert!(logs[0].contains("parent block mismatch, expected: 40, received: 41")); - } - - #[tokio::test] - async fn test_check_sequence_window_expired() { - let trace_store: TraceStorage = Default::default(); - let layer = CollectingLayer::new(trace_store.clone()); - let subscriber = tracing_subscriber::Registry::default().with(layer); - let _guard = tracing::subscriber::set_default(subscriber); - - let cfg = RollupConfig { delta_time: Some(0), block_time: 10, ..Default::default() }; - let block = BlockInfo { number: 10, timestamp: 10, ..Default::default() }; - let l1_blocks = vec![block]; - let parent_hash = b256!("1111111111111111111111111111111111111111000000000000000000000000"); - let l2_safe_head = L2BlockInfo { - block_info: BlockInfo { number: 41, timestamp: 10, parent_hash, ..Default::default() }, - ..Default::default() - }; - let inclusion_block = BlockInfo { number: 50, ..Default::default() }; - let l2_block = L2BlockInfo { - block_info: BlockInfo { - number: 40, - hash: parent_hash, - timestamp: 10, - ..Default::default() - }, - ..Default::default() - }; - let mut fetcher = TestL2ChainProvider { blocks: vec![l2_block], ..Default::default() }; - let first = SpanBatchElement { epoch_num: 10, timestamp: 10, ..Default::default() }; - let second = SpanBatchElement { epoch_num: 11, timestamp: 20, ..Default::default() }; - let batch = SpanBatch { - batches: vec![first, second], - parent_check: FixedBytes::<20>::from_slice(&parent_hash[..20]), - ..Default::default() - }; - // parent number = 41 - (10 - 10) / 10 - 1 = 40 - assert_eq!( - batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await, - BatchValidity::Drop - ); - let logs = trace_store.get_by_level(Level::WARN); - assert_eq!(logs.len(), 1); - assert!(logs[0].contains("batch was included too late, sequence window expired")); - } - - #[tokio::test] - async fn test_starting_epoch_too_far_ahead() { - let trace_store: TraceStorage = Default::default(); - let layer = CollectingLayer::new(trace_store.clone()); - let subscriber = tracing_subscriber::Registry::default().with(layer); - let _guard = tracing::subscriber::set_default(subscriber); - - let cfg = RollupConfig { - seq_window_size: 100, - delta_time: Some(0), - block_time: 10, - ..Default::default() - }; - let block = BlockInfo { number: 10, timestamp: 10, ..Default::default() }; - let l1_blocks = vec![block]; - let parent_hash = b256!("1111111111111111111111111111111111111111000000000000000000000000"); - let l2_safe_head = L2BlockInfo { - block_info: BlockInfo { number: 41, timestamp: 10, parent_hash, ..Default::default() }, - l1_origin: BlockNumHash { number: 8, ..Default::default() }, - ..Default::default() - }; - let inclusion_block = BlockInfo { number: 50, ..Default::default() }; - let l2_block = L2BlockInfo { - block_info: BlockInfo { - number: 40, - hash: parent_hash, - timestamp: 10, - ..Default::default() - }, - l1_origin: BlockNumHash { number: 8, ..Default::default() }, - ..Default::default() - }; - let mut fetcher = TestL2ChainProvider { blocks: vec![l2_block], ..Default::default() }; - let first = SpanBatchElement { epoch_num: 10, timestamp: 10, ..Default::default() }; - let second = SpanBatchElement { epoch_num: 11, timestamp: 20, ..Default::default() }; - let batch = SpanBatch { - batches: vec![first, second], - parent_check: FixedBytes::<20>::from_slice(&parent_hash[..20]), - ..Default::default() - }; - // parent number = 41 - (10 - 10) / 10 - 1 = 40 - assert_eq!( - batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await, - BatchValidity::Drop - ); - let logs = trace_store.get_by_level(Level::WARN); - assert_eq!(logs.len(), 1); - 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"; - assert!(logs[0].contains(str)); - } - - #[tokio::test] - async fn test_check_batch_epoch_hash_mismatch() { - let trace_store: TraceStorage = Default::default(); - let layer = CollectingLayer::new(trace_store.clone()); - let subscriber = tracing_subscriber::Registry::default().with(layer); - let _guard = tracing::subscriber::set_default(subscriber); - - let cfg = RollupConfig { - seq_window_size: 100, - delta_time: Some(0), - block_time: 10, - ..Default::default() - }; - let l1_block_hash = - b256!("3333333333333333333333333333333333333333000000000000000000000000"); - let block = - BlockInfo { number: 11, timestamp: 10, hash: l1_block_hash, ..Default::default() }; - let l1_blocks = vec![block]; - let parent_hash = b256!("1111111111111111111111111111111111111111000000000000000000000000"); - let l2_safe_head = L2BlockInfo { - block_info: BlockInfo { - number: 41, - timestamp: 10, - hash: parent_hash, - ..Default::default() - }, - l1_origin: BlockNumHash { number: 9, ..Default::default() }, - ..Default::default() - }; - let inclusion_block = BlockInfo { number: 50, ..Default::default() }; - let l2_block = L2BlockInfo { - block_info: BlockInfo { - number: 40, - timestamp: 10, - hash: parent_hash, - ..Default::default() - }, - l1_origin: BlockNumHash { number: 9, ..Default::default() }, - ..Default::default() - }; - let mut fetcher = TestL2ChainProvider { blocks: vec![l2_block], ..Default::default() }; - let first = SpanBatchElement { epoch_num: 10, timestamp: 10, ..Default::default() }; - let second = SpanBatchElement { epoch_num: 11, timestamp: 20, ..Default::default() }; - let batch = SpanBatch { - batches: vec![first, second], - parent_check: FixedBytes::<20>::from_slice(&parent_hash[..20]), - ..Default::default() - }; - assert_eq!( - batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await, - BatchValidity::Drop - ); - let logs = trace_store.get_by_level(Level::WARN); - assert_eq!(logs.len(), 1); - let str = alloc::format!( - "batch is for different L1 chain, epoch hash does not match, expected: {}", - l1_block_hash, - ); - assert!(logs[0].contains(&str)); - } - - #[tokio::test] - async fn test_need_more_l1_blocks() { - let trace_store: TraceStorage = Default::default(); - let layer = CollectingLayer::new(trace_store.clone()); - let subscriber = tracing_subscriber::Registry::default().with(layer); - let _guard = tracing::subscriber::set_default(subscriber); - - let cfg = RollupConfig { - seq_window_size: 100, - delta_time: Some(0), - block_time: 10, - ..Default::default() - }; - let l1_block_hash = - b256!("3333333333333333333333333333333333333333000000000000000000000000"); - let block = - BlockInfo { number: 10, timestamp: 10, hash: l1_block_hash, ..Default::default() }; - let l1_blocks = vec![block]; - let parent_hash = b256!("1111111111111111111111111111111111111111000000000000000000000000"); - let l2_safe_head = L2BlockInfo { - block_info: BlockInfo { number: 41, timestamp: 10, parent_hash, ..Default::default() }, - l1_origin: BlockNumHash { number: 9, ..Default::default() }, - ..Default::default() - }; - let inclusion_block = BlockInfo { number: 50, ..Default::default() }; - let l2_block = L2BlockInfo { - block_info: BlockInfo { - number: 40, - timestamp: 10, - hash: parent_hash, - ..Default::default() - }, - l1_origin: BlockNumHash { number: 9, ..Default::default() }, - ..Default::default() - }; - let mut fetcher = TestL2ChainProvider { blocks: vec![l2_block], ..Default::default() }; - let first = SpanBatchElement { epoch_num: 10, timestamp: 10, ..Default::default() }; - let second = SpanBatchElement { epoch_num: 11, timestamp: 20, ..Default::default() }; - let batch = SpanBatch { - batches: vec![first, second], - parent_check: FixedBytes::<20>::from_slice(&parent_hash[..20]), - l1_origin_check: FixedBytes::<20>::from_slice(&l1_block_hash[..20]), - ..Default::default() - }; - assert_eq!( - batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await, - BatchValidity::Undecided - ); - let logs = trace_store.get_by_level(Level::INFO); - assert_eq!(logs.len(), 1); - assert!(logs[0].contains("need more l1 blocks to check entire origins of span batch")); - } - - #[tokio::test] - async fn test_drop_batch_epoch_too_old() { - let trace_store: TraceStorage = Default::default(); - let layer = CollectingLayer::new(trace_store.clone()); - let subscriber = tracing_subscriber::Registry::default().with(layer); - let _guard = tracing::subscriber::set_default(subscriber); - - let cfg = RollupConfig { - seq_window_size: 100, - delta_time: Some(0), - block_time: 10, - ..Default::default() - }; - let l1_block_hash = - b256!("3333333333333333333333333333333333333333000000000000000000000000"); - let block = - BlockInfo { number: 11, timestamp: 10, hash: l1_block_hash, ..Default::default() }; - let l1_blocks = vec![block]; - let parent_hash = b256!("1111111111111111111111111111111111111111000000000000000000000000"); - let l2_safe_head = L2BlockInfo { - block_info: BlockInfo { number: 41, timestamp: 10, parent_hash, ..Default::default() }, - l1_origin: BlockNumHash { number: 13, ..Default::default() }, - ..Default::default() - }; - let inclusion_block = BlockInfo { number: 50, ..Default::default() }; - let l2_block = L2BlockInfo { - block_info: BlockInfo { - number: 40, - timestamp: 10, - hash: parent_hash, - ..Default::default() - }, - l1_origin: BlockNumHash { number: 14, ..Default::default() }, - ..Default::default() - }; - let mut fetcher = TestL2ChainProvider { blocks: vec![l2_block], ..Default::default() }; - let first = SpanBatchElement { epoch_num: 10, timestamp: 10, ..Default::default() }; - let second = SpanBatchElement { epoch_num: 11, timestamp: 20, ..Default::default() }; - let batch = SpanBatch { - batches: vec![first, second], - parent_check: FixedBytes::<20>::from_slice(&parent_hash[..20]), - l1_origin_check: FixedBytes::<20>::from_slice(&l1_block_hash[..20]), - ..Default::default() - }; - assert_eq!( - batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await, - BatchValidity::Drop - ); - let logs = trace_store.get_by_level(Level::WARN); - assert_eq!(logs.len(), 1); - let str = alloc::format!( - "dropped batch, epoch is too old, minimum: {:?}", - l2_block.block_info.id(), - ); - assert!(logs[0].contains(&str)); - } - - #[tokio::test] - async fn test_check_batch_exceeds_max_seq_drif() { - let trace_store: TraceStorage = Default::default(); - let layer = CollectingLayer::new(trace_store.clone()); - let subscriber = tracing_subscriber::Registry::default().with(layer); - let _guard = tracing::subscriber::set_default(subscriber); - - let cfg = RollupConfig { - seq_window_size: 100, - max_sequencer_drift: 0, - delta_time: Some(0), - block_time: 10, - ..Default::default() - }; - let l1_block_hash = - b256!("3333333333333333333333333333333333333333000000000000000000000000"); - let block = - BlockInfo { number: 11, timestamp: 10, hash: l1_block_hash, ..Default::default() }; - let second_block = - BlockInfo { number: 12, timestamp: 10, hash: l1_block_hash, ..Default::default() }; - let l1_blocks = vec![block, second_block]; - let parent_hash = b256!("1111111111111111111111111111111111111111000000000000000000000000"); - let l2_safe_head = L2BlockInfo { - block_info: BlockInfo { - number: 41, - timestamp: 10, - hash: parent_hash, - ..Default::default() - }, - l1_origin: BlockNumHash { number: 9, ..Default::default() }, - ..Default::default() - }; - let inclusion_block = BlockInfo { number: 50, ..Default::default() }; - let l2_block = L2BlockInfo { - block_info: BlockInfo { number: 40, ..Default::default() }, - ..Default::default() - }; - let mut fetcher = TestL2ChainProvider { blocks: vec![l2_block], ..Default::default() }; - let first = SpanBatchElement { epoch_num: 10, timestamp: 20, ..Default::default() }; - let second = SpanBatchElement { epoch_num: 10, timestamp: 20, ..Default::default() }; - let third = SpanBatchElement { epoch_num: 11, timestamp: 20, ..Default::default() }; - let batch = SpanBatch { - batches: vec![first, second, third], - parent_check: FixedBytes::<20>::from_slice(&parent_hash[..20]), - l1_origin_check: FixedBytes::<20>::from_slice(&l1_block_hash[..20]), - ..Default::default() - }; - assert_eq!( - batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await, - BatchValidity::Drop - ); - let logs = trace_store.get_by_level(Level::INFO); - assert_eq!(logs.len(), 1); - assert!(logs[0].contains("batch exceeded sequencer time drift without adopting next origin, and next L1 origin would have been valid")); - } - - #[tokio::test] - async fn test_continuing_with_empty_batch() { - let trace_store: TraceStorage = Default::default(); - let layer = CollectingLayer::new(trace_store.clone()); - let subscriber = tracing_subscriber::Registry::default().with(layer); - let _guard = tracing::subscriber::set_default(subscriber); - - let cfg = RollupConfig { - seq_window_size: 100, - max_sequencer_drift: 0, - delta_time: Some(0), - block_time: 10, - ..Default::default() - }; - let l1_block_hash = - b256!("3333333333333333333333333333333333333333000000000000000000000000"); - let block = - BlockInfo { number: 11, timestamp: 10, hash: l1_block_hash, ..Default::default() }; - let second_block = - BlockInfo { number: 12, timestamp: 21, hash: l1_block_hash, ..Default::default() }; - let l1_blocks = vec![block, second_block]; - let parent_hash = b256!("1111111111111111111111111111111111111111000000000000000000000000"); - let l2_safe_head = L2BlockInfo { - block_info: BlockInfo { - number: 41, - timestamp: 10, - hash: parent_hash, - ..Default::default() - }, - l1_origin: BlockNumHash { number: 9, ..Default::default() }, - ..Default::default() - }; - let inclusion_block = BlockInfo { number: 50, ..Default::default() }; - let l2_block = L2BlockInfo { - block_info: BlockInfo { number: 40, ..Default::default() }, - ..Default::default() - }; - let mut fetcher = TestL2ChainProvider { blocks: vec![l2_block], ..Default::default() }; - let first = SpanBatchElement { epoch_num: 10, timestamp: 20, transactions: vec![] }; - let second = SpanBatchElement { epoch_num: 10, timestamp: 20, transactions: vec![] }; - let third = SpanBatchElement { epoch_num: 11, timestamp: 20, transactions: vec![] }; - let batch = SpanBatch { - batches: vec![first, second, third], - parent_check: FixedBytes::<20>::from_slice(&parent_hash[..20]), - l1_origin_check: FixedBytes::<20>::from_slice(&l1_block_hash[..20]), - txs: SpanBatchTransactions::default(), - ..Default::default() - }; - assert_eq!( - batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await, - BatchValidity::Accept - ); - let infos = trace_store.get_by_level(Level::INFO); - assert_eq!(infos.len(), 1); - assert!(infos[0].contains( - "continuing with empty batch before late L1 block to preserve L2 time invariant" - )); - } - - #[tokio::test] - async fn test_check_batch_exceeds_sequencer_time_drift() { - let trace_store: TraceStorage = Default::default(); - let layer = CollectingLayer::new(trace_store.clone()); - let subscriber = tracing_subscriber::Registry::default().with(layer); - let _guard = tracing::subscriber::set_default(subscriber); - - let cfg = RollupConfig { - seq_window_size: 100, - max_sequencer_drift: 0, - delta_time: Some(0), - block_time: 10, - ..Default::default() - }; - let l1_block_hash = - b256!("3333333333333333333333333333333333333333000000000000000000000000"); - let block = - BlockInfo { number: 11, timestamp: 10, hash: l1_block_hash, ..Default::default() }; - let second_block = - BlockInfo { number: 12, timestamp: 10, hash: l1_block_hash, ..Default::default() }; - let l1_blocks = vec![block, second_block]; - let parent_hash = b256!("1111111111111111111111111111111111111111000000000000000000000000"); - let l2_safe_head = L2BlockInfo { - block_info: BlockInfo { - number: 41, - timestamp: 10, - hash: parent_hash, - ..Default::default() - }, - l1_origin: BlockNumHash { number: 9, ..Default::default() }, - ..Default::default() - }; - let inclusion_block = BlockInfo { number: 50, ..Default::default() }; - let l2_block = L2BlockInfo { - block_info: BlockInfo { number: 40, ..Default::default() }, - ..Default::default() - }; - let mut fetcher = TestL2ChainProvider { blocks: vec![l2_block], ..Default::default() }; - let first = SpanBatchElement { - epoch_num: 10, - timestamp: 20, - transactions: vec![Default::default()], - }; - let second = SpanBatchElement { - epoch_num: 10, - timestamp: 20, - transactions: vec![Default::default()], - }; - let third = SpanBatchElement { - epoch_num: 11, - timestamp: 20, - transactions: vec![Default::default()], - }; - let batch = SpanBatch { - batches: vec![first, second, third], - parent_check: FixedBytes::<20>::from_slice(&parent_hash[..20]), - l1_origin_check: FixedBytes::<20>::from_slice(&l1_block_hash[..20]), - txs: SpanBatchTransactions::default(), - ..Default::default() - }; - assert_eq!( - batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await, - BatchValidity::Drop - ); - let logs = trace_store.get_by_level(Level::WARN); - assert_eq!(logs.len(), 1); - assert!(logs[0].contains("batch exceeded sequencer time drift, sequencer must adopt new L1 origin to include transactions again, max_time: 10")); - } - - #[tokio::test] - async fn test_check_batch_empty_txs() { - let trace_store: TraceStorage = Default::default(); - let layer = CollectingLayer::new(trace_store.clone()); - let subscriber = tracing_subscriber::Registry::default().with(layer); - let _guard = tracing::subscriber::set_default(subscriber); - - let cfg = RollupConfig { - seq_window_size: 100, - max_sequencer_drift: 100, - delta_time: Some(0), - block_time: 10, - ..Default::default() - }; - let l1_block_hash = - b256!("3333333333333333333333333333333333333333000000000000000000000000"); - let block = - BlockInfo { number: 11, timestamp: 10, hash: l1_block_hash, ..Default::default() }; - let second_block = - BlockInfo { number: 12, timestamp: 21, hash: l1_block_hash, ..Default::default() }; - let l1_blocks = vec![block, second_block]; - let parent_hash = b256!("1111111111111111111111111111111111111111000000000000000000000000"); - let l2_safe_head = L2BlockInfo { - block_info: BlockInfo { - number: 41, - timestamp: 10, - hash: parent_hash, - ..Default::default() - }, - l1_origin: BlockNumHash { number: 9, ..Default::default() }, - ..Default::default() - }; - let inclusion_block = BlockInfo { number: 50, ..Default::default() }; - let l2_block = L2BlockInfo { - block_info: BlockInfo { number: 40, ..Default::default() }, - ..Default::default() - }; - let mut fetcher = TestL2ChainProvider { blocks: vec![l2_block], ..Default::default() }; - let first = SpanBatchElement { - epoch_num: 10, - timestamp: 20, - transactions: vec![Default::default()], - }; - let second = SpanBatchElement { - epoch_num: 10, - timestamp: 20, - transactions: vec![Default::default()], - }; - let third = SpanBatchElement { epoch_num: 11, timestamp: 20, transactions: vec![] }; - let batch = SpanBatch { - batches: vec![first, second, third], - parent_check: FixedBytes::<20>::from_slice(&parent_hash[..20]), - l1_origin_check: FixedBytes::<20>::from_slice(&l1_block_hash[..20]), - txs: SpanBatchTransactions::default(), - ..Default::default() - }; - assert_eq!( - batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await, - BatchValidity::Drop - ); - let logs = trace_store.get_by_level(Level::WARN); - assert_eq!(logs.len(), 1); - assert!(logs[0].contains("transaction data must not be empty, but found empty tx")); - } - - #[tokio::test] - async fn test_check_batch_with_deposit_tx() { - let trace_store: TraceStorage = Default::default(); - let layer = CollectingLayer::new(trace_store.clone()); - let subscriber = tracing_subscriber::Registry::default().with(layer); - let _guard = tracing::subscriber::set_default(subscriber); - - let cfg = RollupConfig { - seq_window_size: 100, - max_sequencer_drift: 100, - delta_time: Some(0), - block_time: 10, - ..Default::default() - }; - let l1_block_hash = - b256!("3333333333333333333333333333333333333333000000000000000000000000"); - let block = - BlockInfo { number: 11, timestamp: 10, hash: l1_block_hash, ..Default::default() }; - let second_block = - BlockInfo { number: 12, timestamp: 21, hash: l1_block_hash, ..Default::default() }; - let l1_blocks = vec![block, second_block]; - let parent_hash = b256!("1111111111111111111111111111111111111111000000000000000000000000"); - let l2_safe_head = L2BlockInfo { - block_info: BlockInfo { - number: 41, - timestamp: 10, - hash: parent_hash, - ..Default::default() - }, - l1_origin: BlockNumHash { number: 9, ..Default::default() }, - ..Default::default() - }; - let inclusion_block = BlockInfo { number: 50, ..Default::default() }; - let l2_block = L2BlockInfo { - block_info: BlockInfo { number: 40, ..Default::default() }, - ..Default::default() - }; - let mut fetcher = TestL2ChainProvider { blocks: vec![l2_block], ..Default::default() }; - let filler_bytes = Bytes::copy_from_slice(&[OpTxType::Eip1559 as u8]); - let first = SpanBatchElement { - epoch_num: 10, - timestamp: 20, - transactions: vec![filler_bytes.clone()], - }; - let second = SpanBatchElement { - epoch_num: 10, - timestamp: 20, - transactions: vec![Bytes::copy_from_slice(&[OpTxType::Deposit as u8])], - }; - let third = - SpanBatchElement { epoch_num: 11, timestamp: 20, transactions: vec![filler_bytes] }; - let batch = SpanBatch { - batches: vec![first, second, third], - parent_check: FixedBytes::<20>::from_slice(&parent_hash[..20]), - l1_origin_check: FixedBytes::<20>::from_slice(&l1_block_hash[..20]), - txs: SpanBatchTransactions::default(), - ..Default::default() - }; - assert_eq!( - batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await, - BatchValidity::Drop - ); - let logs = trace_store.get_by_level(Level::WARN); - assert_eq!(logs.len(), 1); - assert!(logs[0].contains("sequencers may not embed any deposits into batch data, but found tx that has one, tx_index: 0")); - } - - #[tokio::test] - async fn test_check_batch_failed_to_fetch_payload() { - let trace_store: TraceStorage = Default::default(); - let layer = CollectingLayer::new(trace_store.clone()); - let subscriber = tracing_subscriber::Registry::default().with(layer); - let _guard = tracing::subscriber::set_default(subscriber); - - let cfg = RollupConfig { - seq_window_size: 100, - delta_time: Some(0), - block_time: 10, - ..Default::default() - }; - let l1_block_hash = - b256!("3333333333333333333333333333333333333333000000000000000000000000"); - let block = - BlockInfo { number: 11, timestamp: 10, hash: l1_block_hash, ..Default::default() }; - let l1_blocks = vec![block]; - let parent_hash = b256!("1111111111111111111111111111111111111111000000000000000000000000"); - let l2_safe_head = L2BlockInfo { - block_info: BlockInfo { number: 41, timestamp: 10, parent_hash, ..Default::default() }, - l1_origin: BlockNumHash { number: 9, ..Default::default() }, - ..Default::default() - }; - let inclusion_block = BlockInfo { number: 50, ..Default::default() }; - let l2_block = L2BlockInfo { - block_info: BlockInfo { - number: 40, - timestamp: 10, - hash: parent_hash, - ..Default::default() - }, - l1_origin: BlockNumHash { number: 9, ..Default::default() }, - ..Default::default() - }; - let mut fetcher = TestL2ChainProvider { blocks: vec![l2_block], ..Default::default() }; - let first = SpanBatchElement { epoch_num: 10, timestamp: 10, ..Default::default() }; - let second = SpanBatchElement { epoch_num: 11, timestamp: 20, ..Default::default() }; - let batch = SpanBatch { - batches: vec![first, second], - parent_check: FixedBytes::<20>::from_slice(&parent_hash[..20]), - l1_origin_check: FixedBytes::<20>::from_slice(&l1_block_hash[..20]), - ..Default::default() - }; - assert_eq!( - batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await, - BatchValidity::Undecided - ); - let logs = trace_store.get_by_level(Level::WARN); - assert_eq!(logs.len(), 1); - assert!(logs[0].contains("failed to fetch block number 41: L2 Block not found")); - } - - #[tokio::test] - async fn test_check_batch_failed_to_extract_l2_block_info() { - let trace_store: TraceStorage = Default::default(); - let layer = CollectingLayer::new(trace_store.clone()); - let subscriber = tracing_subscriber::Registry::default().with(layer); - let _guard = tracing::subscriber::set_default(subscriber); - - let cfg = RollupConfig { - seq_window_size: 100, - delta_time: Some(0), - block_time: 10, - ..Default::default() - }; - let l1_block_hash = - b256!("3333333333333333333333333333333333333333000000000000000000000000"); - let block = - BlockInfo { number: 11, timestamp: 10, hash: l1_block_hash, ..Default::default() }; - let l1_blocks = vec![block]; - let parent_hash = b256!("1111111111111111111111111111111111111111000000000000000000000000"); - let l2_safe_head = L2BlockInfo { - block_info: BlockInfo { number: 41, timestamp: 10, parent_hash, ..Default::default() }, - l1_origin: BlockNumHash { number: 9, ..Default::default() }, - ..Default::default() - }; - let inclusion_block = BlockInfo { number: 50, ..Default::default() }; - let l2_block = L2BlockInfo { - block_info: BlockInfo { - number: 40, - timestamp: 10, - hash: parent_hash, - ..Default::default() - }, - l1_origin: BlockNumHash { number: 9, ..Default::default() }, - ..Default::default() - }; - let block = OpBlock { - header: Header { number: 41, ..Default::default() }, - body: alloy_consensus::BlockBody { - transactions: Vec::new(), - ommers: Vec::new(), - withdrawals: None, - }, - }; - let mut fetcher = TestL2ChainProvider { - blocks: vec![l2_block], - op_blocks: vec![block], - ..Default::default() - }; - let first = SpanBatchElement { epoch_num: 10, timestamp: 10, ..Default::default() }; - let second = SpanBatchElement { epoch_num: 11, timestamp: 20, ..Default::default() }; - let batch = SpanBatch { - batches: vec![first, second], - parent_check: FixedBytes::<20>::from_slice(&parent_hash[..20]), - l1_origin_check: FixedBytes::<20>::from_slice(&l1_block_hash[..20]), - ..Default::default() - }; - assert_eq!( - batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await, - BatchValidity::Drop - ); - let logs = trace_store.get_by_level(Level::WARN); - assert_eq!(logs.len(), 1); - let str = alloc::format!( - "failed to extract L2BlockInfo from execution payload, hash: {:?}", - b256!("0e2ee9abe94ee4514b170d7039d8151a7469d434a8575dbab5bd4187a27732dd"), - ); - assert!(logs[0].contains(&str)); - } - - #[tokio::test] - async fn test_overlapped_blocks_origin_mismatch() { - let trace_store: TraceStorage = Default::default(); - let layer = CollectingLayer::new(trace_store.clone()); - let subscriber = tracing_subscriber::Registry::default().with(layer); - let _guard = tracing::subscriber::set_default(subscriber); - - let payload_block_hash = - b256!("0e2ee9abe94ee4514b170d7039d8151a7469d434a8575dbab5bd4187a27732dd"); - let cfg = RollupConfig { - seq_window_size: 100, - delta_time: Some(0), - block_time: 10, - genesis: ChainGenesis { - l2: BlockNumHash { number: 41, hash: payload_block_hash }, - ..Default::default() - }, - ..Default::default() - }; - let l1_block_hash = - b256!("3333333333333333333333333333333333333333000000000000000000000000"); - let block = - BlockInfo { number: 11, timestamp: 10, hash: l1_block_hash, ..Default::default() }; - let l1_blocks = vec![block]; - let parent_hash = b256!("1111111111111111111111111111111111111111000000000000000000000000"); - let l2_safe_head = L2BlockInfo { - block_info: BlockInfo { number: 41, timestamp: 10, parent_hash, ..Default::default() }, - l1_origin: BlockNumHash { number: 9, ..Default::default() }, - ..Default::default() - }; - let inclusion_block = BlockInfo { number: 50, ..Default::default() }; - let l2_block = L2BlockInfo { - block_info: BlockInfo { - number: 40, - hash: parent_hash, - timestamp: 10, - ..Default::default() - }, - l1_origin: BlockNumHash { number: 9, ..Default::default() }, - ..Default::default() - }; - let block = OpBlock { - header: Header { number: 41, ..Default::default() }, - body: alloy_consensus::BlockBody { - transactions: Vec::new(), - ommers: Vec::new(), - withdrawals: None, - }, - }; - let mut fetcher = TestL2ChainProvider { - blocks: vec![l2_block], - op_blocks: vec![block], - ..Default::default() - }; - let first = SpanBatchElement { epoch_num: 10, timestamp: 10, ..Default::default() }; - let second = SpanBatchElement { epoch_num: 11, timestamp: 20, ..Default::default() }; - let batch = SpanBatch { - batches: vec![first, second], - parent_check: FixedBytes::<20>::from_slice(&parent_hash[..20]), - l1_origin_check: FixedBytes::<20>::from_slice(&l1_block_hash[..20]), - ..Default::default() - }; - assert_eq!( - batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await, - BatchValidity::Drop - ); - let logs = trace_store.get_by_level(Level::WARN); - assert_eq!(logs.len(), 1); - assert!(logs[0].contains("overlapped block's L1 origin number does not match")); - } - - #[tokio::test] - async fn test_check_batch_valid_with_genesis_epoch() { - let trace_store: TraceStorage = Default::default(); - let layer = CollectingLayer::new(trace_store.clone()); - let subscriber = tracing_subscriber::Registry::default().with(layer); - let _guard = tracing::subscriber::set_default(subscriber); - - let payload_block_hash = - b256!("0e2ee9abe94ee4514b170d7039d8151a7469d434a8575dbab5bd4187a27732dd"); - let cfg = RollupConfig { - seq_window_size: 100, - delta_time: Some(0), - block_time: 10, - genesis: ChainGenesis { - l2: BlockNumHash { number: 41, hash: payload_block_hash }, - l1: BlockNumHash { number: 10, ..Default::default() }, - ..Default::default() - }, - ..Default::default() - }; - let l1_block_hash = - b256!("3333333333333333333333333333333333333333000000000000000000000000"); - let block = - BlockInfo { number: 11, timestamp: 10, hash: l1_block_hash, ..Default::default() }; - let l1_blocks = vec![block]; - let parent_hash = b256!("1111111111111111111111111111111111111111000000000000000000000000"); - let l2_safe_head = L2BlockInfo { - block_info: BlockInfo { - number: 41, - timestamp: 10, - hash: parent_hash, - ..Default::default() - }, - l1_origin: BlockNumHash { number: 9, ..Default::default() }, - ..Default::default() - }; - let inclusion_block = BlockInfo { number: 50, ..Default::default() }; - let l2_block = L2BlockInfo { - block_info: BlockInfo { - number: 40, - hash: parent_hash, - timestamp: 10, - ..Default::default() - }, - l1_origin: BlockNumHash { number: 9, ..Default::default() }, - ..Default::default() - }; - let block = OpBlock { - header: Header { number: 41, ..Default::default() }, - body: alloy_consensus::BlockBody { - transactions: Vec::new(), - ommers: Vec::new(), - withdrawals: None, - }, - }; - let mut fetcher = TestL2ChainProvider { - blocks: vec![l2_block], - op_blocks: vec![block], - ..Default::default() - }; - let first = SpanBatchElement { epoch_num: 10, timestamp: 10, ..Default::default() }; - let second = SpanBatchElement { epoch_num: 11, timestamp: 20, ..Default::default() }; - let batch = SpanBatch { - batches: vec![first, second], - parent_check: FixedBytes::<20>::from_slice(&parent_hash[..20]), - l1_origin_check: FixedBytes::<20>::from_slice(&l1_block_hash[..20]), - ..Default::default() - }; - assert_eq!( - batch.check_batch(&cfg, &l1_blocks, l2_safe_head, &inclusion_block, &mut fetcher).await, - BatchValidity::Accept - ); - assert!(trace_store.is_empty()); - } -} diff --git a/crates/derive/src/batch/span_batch/bits.rs b/crates/derive/src/batch/span_batch/bits.rs deleted file mode 100644 index 7f8fd2653..000000000 --- a/crates/derive/src/batch/span_batch/bits.rs +++ /dev/null @@ -1,224 +0,0 @@ -//! Module for working with span batch bits. - -use super::errors::SpanBatchError; -use alloc::{vec, vec::Vec}; -use alloy_rlp::Buf; -use core::cmp::Ordering; - -/// Type for span batch bits. -#[derive(Debug, Default, Clone, PartialEq, Eq)] -pub struct SpanBatchBits(pub Vec); - -impl AsRef<[u8]> for SpanBatchBits { - fn as_ref(&self) -> &[u8] { - &self.0 - } -} - -impl SpanBatchBits { - /// Decodes a standard span-batch bitlist from a reader. - /// The bitlist is encoded as big-endian integer, left-padded with zeroes to a multiple of 8 - /// bits. The encoded bitlist cannot be longer than `bit_length`. - pub fn decode(b: &mut &[u8], bit_length: usize) -> Result { - let buffer_len = bit_length / 8 + if bit_length % 8 != 0 { 1 } else { 0 }; - let bits = if b.len() < buffer_len { - let mut bits = vec![0; buffer_len]; - bits[..b.len()].copy_from_slice(b); - b.advance(b.len()); - bits - } else { - let v = b[..buffer_len].to_vec(); - b.advance(buffer_len); - v - }; - let sb_bits = Self(bits); - - if sb_bits.bit_len() > bit_length { - return Err(SpanBatchError::BitfieldTooLong); - } - - Ok(sb_bits) - } - - /// Encodes a standard span-batch bitlist. - /// The bitlist is encoded as big-endian integer, left-padded with zeroes to a multiple of 8 - /// bits. The encoded bitlist cannot be longer than `bit_length` - pub fn encode(w: &mut Vec, bit_length: usize, bits: &Self) -> Result<(), SpanBatchError> { - if bits.bit_len() > bit_length { - return Err(SpanBatchError::BitfieldTooLong); - } - - // Round up, ensure enough bytes when number of bits is not a multiple of 8. - // Alternative of (L+7)/8 is not overflow-safe. - let buf_len = bit_length / 8 + if bit_length % 8 != 0 { 1 } else { 0 }; - let mut buf = vec![0; buf_len]; - buf[buf_len - bits.0.len()..].copy_from_slice(bits.as_ref()); - w.extend_from_slice(&buf); - Ok(()) - } - - /// Get a bit from the [SpanBatchBits] bitlist. - pub fn get_bit(&self, index: usize) -> Option { - let byte_index = index / 8; - let bit_index = index % 8; - - // Check if the byte index is within the bounds of the bitlist - if byte_index < self.0.len() { - // Retrieve the specific byte that contains the bit we're interested in - let byte = self.0[self.0.len() - byte_index - 1]; - - // Shift the bits of the byte to the right, based on the bit index, and - // mask it with 1 to isolate the bit we're interested in. - // If the result is not zero, the bit is set to 1, otherwise it's 0. - Some(if byte & (1 << bit_index) != 0 { 1 } else { 0 }) - } else { - // Return None if the index is out of bounds - None - } - } - - /// Sets a bit in the [SpanBatchBits] bitlist. - pub fn set_bit(&mut self, index: usize, value: bool) { - let byte_index = index / 8; - let bit_index = index % 8; - - // Ensure the vector is large enough to contain the bit at 'index'. - // If not, resize the vector, filling with 0s. - if byte_index >= self.0.len() { - Self::resize_from_right(&mut self.0, byte_index + 1); - } - - // Retrieve the specific byte to modify - let len = self.0.len(); - let byte = &mut self.0[len - byte_index - 1]; - - if value { - // Set the bit to 1 - *byte |= 1 << bit_index; - } else { - // Set the bit to 0 - *byte &= !(1 << bit_index); - } - } - - /// Calculates the bit length of the [SpanBatchBits] bitfield. - pub fn bit_len(&self) -> usize { - // Iterate over the bytes from left to right to find the first non-zero byte - for (i, &byte) in self.0.iter().enumerate() { - if byte != 0 { - // Calculate the index of the most significant bit in the byte - let msb_index = 7 - byte.leading_zeros() as usize; // 0-based index - - // Calculate the total bit length - let total_bit_length = msb_index + 1 + ((self.0.len() - i - 1) * 8); - return total_bit_length; - } - } - - // If all bytes are zero, the bitlist is considered to have a length of 0 - 0 - } - - /// Resizes an array from the right. Useful for big-endian zero extension. - fn resize_from_right(vec: &mut Vec, new_size: usize) { - let current_size = vec.len(); - match new_size.cmp(¤t_size) { - Ordering::Less => { - // Remove elements from the beginning. - let remove_count = current_size - new_size; - vec.drain(0..remove_count); - } - Ordering::Greater => { - // Calculate how many new elements to add. - let additional = new_size - current_size; - // Prepend new elements with default values. - let mut prepend_elements = vec![T::default(); additional]; - prepend_elements.append(vec); - *vec = prepend_elements; - } - Ordering::Equal => { /* If new_size == current_size, do nothing. */ } - } - } -} - -#[cfg(test)] -mod test { - use super::*; - use proptest::{collection::vec, prelude::any, proptest}; - - proptest! { - #[test] - fn test_encode_decode_roundtrip_span_bitlist(vec in vec(any::(), 0..5096)) { - let bits = SpanBatchBits(vec); - assert_eq!(SpanBatchBits::decode(&mut bits.as_ref(), bits.0.len() * 8).unwrap(), bits); - let mut encoded = Vec::new(); - SpanBatchBits::encode(&mut encoded, bits.0.len() * 8, &bits).unwrap(); - assert_eq!(encoded, bits.0); - } - - #[test] - fn test_span_bitlist_bitlen(index in 0usize..65536) { - let mut bits = SpanBatchBits::default(); - bits.set_bit(index, true); - assert_eq!(bits.0.len(), (index / 8) + 1); - assert_eq!(bits.bit_len(), index + 1); - } - - #[test] - fn test_span_bitlist_bitlen_shrink(first_index in 8usize..65536) { - let second_index = first_index.clamp(0, first_index - 8); - let mut bits = SpanBatchBits::default(); - - // Set and clear first index. - bits.set_bit(first_index, true); - assert_eq!(bits.0.len(), (first_index / 8) + 1); - assert_eq!(bits.bit_len(), first_index + 1); - bits.set_bit(first_index, false); - assert_eq!(bits.0.len(), (first_index / 8) + 1); - assert_eq!(bits.bit_len(), 0); - - // Set second bit. Even though the array is larger, as it was originally allocated with more words, - // the bitlength should still be lowered as the higher-order words are 0'd out. - bits.set_bit(second_index, true); - assert_eq!(bits.0.len(), (first_index / 8) + 1); - assert_eq!(bits.bit_len(), second_index + 1); - } - } - - #[test] - fn bitlist_big_endian_zero_extended() { - let mut bits = SpanBatchBits::default(); - - bits.set_bit(1, true); - bits.set_bit(6, true); - bits.set_bit(8, true); - bits.set_bit(15, true); - assert_eq!(bits.0[0], 0b1000_0001); - assert_eq!(bits.0[1], 0b0100_0010); - assert_eq!(bits.0.len(), 2); - assert_eq!(bits.bit_len(), 16); - } - - #[test] - fn test_static_set_get_bits_span_bitlist() { - let mut bits = SpanBatchBits::default(); - assert!(bits.0.is_empty()); - - bits.set_bit(0, true); - bits.set_bit(1, true); - bits.set_bit(2, true); - bits.set_bit(4, true); - bits.set_bit(7, true); - assert_eq!(bits.0.len(), 1); - assert_eq!(bits.get_bit(0), Some(1)); - assert_eq!(bits.get_bit(1), Some(1)); - assert_eq!(bits.get_bit(2), Some(1)); - assert_eq!(bits.get_bit(3), Some(0)); - assert_eq!(bits.get_bit(4), Some(1)); - - bits.set_bit(17, true); - assert_eq!(bits.get_bit(17), Some(1)); - assert_eq!(bits.get_bit(32), None); - assert_eq!(bits.0.len(), 3); - } -} diff --git a/crates/derive/src/batch/span_batch/element.rs b/crates/derive/src/batch/span_batch/element.rs deleted file mode 100644 index a07065473..000000000 --- a/crates/derive/src/batch/span_batch/element.rs +++ /dev/null @@ -1,52 +0,0 @@ -//! Span Batch Element - -use crate::batch::SingleBatch; -use alloc::vec::Vec; -use alloy_primitives::Bytes; - -/// A single batch element is similar to the [SingleBatch] type -/// but does not contain the parent hash and epoch hash since spans -/// do not contain this data for every block in the span. -#[derive(Debug, Default, Clone, PartialEq, Eq)] -pub struct SpanBatchElement { - /// The epoch number of the L1 block - pub epoch_num: u64, - /// The timestamp of the L2 block - pub timestamp: u64, - /// The transactions in the L2 block - pub transactions: Vec, -} - -impl From for SpanBatchElement { - fn from(batch: SingleBatch) -> Self { - Self { - epoch_num: batch.epoch_num, - timestamp: batch.timestamp, - transactions: batch.transactions, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use proptest::{collection::vec, prelude::any, proptest}; - - proptest! { - #[test] - fn test_span_batch_element_from_single_batch(epoch_num in 0u64..u64::MAX, timestamp in 0u64..u64::MAX, transactions in vec(any::(), 0..100)) { - let single_batch = SingleBatch { - epoch_num, - timestamp, - transactions: transactions.clone(), - ..Default::default() - }; - - let span_batch_element: SpanBatchElement = single_batch.into(); - - assert_eq!(span_batch_element.epoch_num, epoch_num); - assert_eq!(span_batch_element.timestamp, timestamp); - assert_eq!(span_batch_element.transactions, transactions); - } - } -} diff --git a/crates/derive/src/batch/span_batch/errors.rs b/crates/derive/src/batch/span_batch/errors.rs deleted file mode 100644 index 1e70bdfda..000000000 --- a/crates/derive/src/batch/span_batch/errors.rs +++ /dev/null @@ -1,77 +0,0 @@ -//! Span Batch Errors - -/// Span Batch Errors -#[derive(derive_more::Display, Debug, Clone, PartialEq, Eq)] -pub enum SpanBatchError { - /// The span batch is too big - #[display("The span batch is too big.")] - TooBigSpanBatchSize, - /// The bit field is too long - #[display("The bit field is too long")] - BitfieldTooLong, - /// Empty Span Batch - #[display("Empty span batch")] - EmptySpanBatch, - /// Missing L1 origin - #[display("Missing L1 origin")] - MissingL1Origin, - /// Decoding errors - #[display("Span batch decoding error: {_0}")] - Decoding(SpanDecodingError), -} - -impl From for SpanBatchError { - fn from(e: SpanDecodingError) -> Self { - Self::Decoding(e) - } -} - -impl core::error::Error for SpanBatchError { - fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { - match self { - Self::Decoding(e) => Some(e), - _ => None, - } - } -} - -/// Decoding Error -#[derive(derive_more::Display, Debug, Clone, PartialEq, Eq)] -pub enum SpanDecodingError { - /// Failed to decode relative timestamp - #[display("Failed to decode relative timestamp")] - RelativeTimestamp, - /// Failed to decode L1 origin number - #[display("Failed to decode L1 origin number")] - L1OriginNumber, - /// Failed to decode parent check - #[display("Failed to decode parent check")] - ParentCheck, - /// Failed to decode L1 origin check - #[display("Failed to decode L1 origin check")] - L1OriginCheck, - /// Failed to decode block count - #[display("Failed to decode block count")] - BlockCount, - /// Failed to decode block tx counts - #[display("Failed to decode block tx counts")] - BlockTxCounts, - /// Failed to decode transaction nonces - #[display("Failed to decode transaction nonces")] - TxNonces, - /// Mismatch in length between the transaction type and signature arrays in a span batch - /// transaction payload. - #[display("Mismatch in length between the transaction type and signature arrays")] - TypeSignatureLenMismatch, - /// Invalid transaction type - #[display("Invalid transaction type")] - InvalidTransactionType, - /// Invalid transaction data - #[display("Invalid transaction data")] - InvalidTransactionData, - /// Invalid transaction signature - #[display("Invalid transaction signature")] - InvalidTransactionSignature, -} - -impl core::error::Error for SpanDecodingError {} diff --git a/crates/derive/src/batch/span_batch/mod.rs b/crates/derive/src/batch/span_batch/mod.rs deleted file mode 100644 index b32afb194..000000000 --- a/crates/derive/src/batch/span_batch/mod.rs +++ /dev/null @@ -1,51 +0,0 @@ -//! Contains all Span Batch types and logic. -//! -//! ## Batch format -//! -//! ```text -//! [SPAN_BATCH_TYPE] = 1 -//! span_batch = [SPAN_BATCH_TYPE] ++ prefix ++ payload -//! prefix = rel_timestamp ++ l1_origin_num ++ parent_check ++ l1_origin_check -//! payload = block_count ++ origin_bits ++ block_tx_counts ++ txs -//! txs = contract_creation_bits ++ y_parity_bits ++ tx_sigs ++ tx_tos ++ tx_datas ++ tx_nonces ++ tx_gases ++ protected_bits -//! ``` - -/// MAX_SPAN_BATCH_ELEMENTS is the maximum number of blocks, transactions in total, -/// or transaction per block allowed in a span batch. -pub const MAX_SPAN_BATCH_ELEMENTS: u64 = 10_000_000; - -mod batch; -pub use batch::SpanBatch; - -mod bits; -pub use bits::SpanBatchBits; - -mod payload; -pub use payload::SpanBatchPayload; - -mod prefix; -pub use prefix::SpanBatchPrefix; - -mod errors; -pub use errors::*; // Re-export all error types - -mod raw; -pub use raw::RawSpanBatch; - -mod element; -pub use element::SpanBatchElement; - -mod signature; -pub use signature::SpanBatchSignature; - -mod tx_data; -pub use tx_data::{ - SpanBatchEip1559TransactionData, SpanBatchEip2930TransactionData, - SpanBatchLegacyTransactionData, SpanBatchTransactionData, -}; - -mod transactions; -pub use transactions::SpanBatchTransactions; - -mod utils; -pub(crate) use utils::{convert_v_to_y_parity, read_tx_data}; diff --git a/crates/derive/src/batch/span_batch/payload.rs b/crates/derive/src/batch/span_batch/payload.rs deleted file mode 100644 index b97fa938e..000000000 --- a/crates/derive/src/batch/span_batch/payload.rs +++ /dev/null @@ -1,196 +0,0 @@ -//! Raw Span Batch Payload - -use super::MAX_SPAN_BATCH_ELEMENTS; -use crate::batch::{SpanBatchBits, SpanBatchError, SpanBatchTransactions, SpanDecodingError}; -use alloc::vec::Vec; - -/// Span Batch Payload -#[derive(Debug, Clone, Default, PartialEq, Eq)] -pub struct SpanBatchPayload { - /// Number of L2 block in the span - pub block_count: u64, - /// Standard span-batch bitlist of blockCount bits. Each bit indicates if the L1 origin is - /// changed at the L2 block. - pub origin_bits: SpanBatchBits, - /// List of transaction counts for each L2 block - pub block_tx_counts: Vec, - /// Transactions encoded in SpanBatch specs - pub txs: SpanBatchTransactions, -} - -impl SpanBatchPayload { - /// Decodes a [SpanBatchPayload] from a reader. - pub fn decode_payload(r: &mut &[u8]) -> Result { - let mut payload = Self::default(); - payload.decode_block_count(r)?; - payload.decode_origin_bits(r)?; - payload.decode_block_tx_counts(r)?; - payload.decode_txs(r)?; - Ok(payload) - } - - /// Encodes a [SpanBatchPayload] into a writer. - pub fn encode_payload(&self, w: &mut Vec) -> Result<(), SpanBatchError> { - self.encode_block_count(w); - self.encode_origin_bits(w)?; - self.encode_block_tx_counts(w); - self.encode_txs(w) - } - - /// Decodes the origin bits from a reader. - pub fn decode_origin_bits(&mut self, r: &mut &[u8]) -> Result<(), SpanBatchError> { - if self.block_count > MAX_SPAN_BATCH_ELEMENTS { - return Err(SpanBatchError::TooBigSpanBatchSize); - } - - self.origin_bits = SpanBatchBits::decode(r, self.block_count as usize)?; - Ok(()) - } - - /// Decode a block count from a reader. - pub fn decode_block_count(&mut self, r: &mut &[u8]) -> Result<(), SpanBatchError> { - let (block_count, remaining) = unsigned_varint::decode::u64(r) - .map_err(|_| SpanBatchError::Decoding(SpanDecodingError::BlockCount))?; - // The number of transactions in a single L2 block cannot be greater than - // [MAX_SPAN_BATCH_ELEMENTS]. - if block_count > MAX_SPAN_BATCH_ELEMENTS { - return Err(SpanBatchError::TooBigSpanBatchSize); - } - if block_count == 0 { - return Err(SpanBatchError::EmptySpanBatch); - } - self.block_count = block_count; - *r = remaining; - Ok(()) - } - - /// Decode block transaction counts from a reader. - pub fn decode_block_tx_counts(&mut self, r: &mut &[u8]) -> Result<(), SpanBatchError> { - // Initially allocate the vec with the block count, to reduce re-allocations in the first - // few blocks. - let mut block_tx_counts = Vec::with_capacity(self.block_count as usize); - - for _ in 0..self.block_count { - let (block_tx_count, remaining) = unsigned_varint::decode::u64(r) - .map_err(|_| SpanBatchError::Decoding(SpanDecodingError::BlockTxCounts))?; - - // The number of transactions in a single L2 block cannot be greater than - // [MAX_SPAN_BATCH_ELEMENTS]. - if block_tx_count > MAX_SPAN_BATCH_ELEMENTS { - return Err(SpanBatchError::TooBigSpanBatchSize); - } - block_tx_counts.push(block_tx_count); - *r = remaining; - } - self.block_tx_counts = block_tx_counts; - Ok(()) - } - - /// Decode transactions from a reader. - pub fn decode_txs(&mut self, r: &mut &[u8]) -> Result<(), SpanBatchError> { - if self.block_tx_counts.is_empty() { - return Err(SpanBatchError::EmptySpanBatch); - } - - let total_block_tx_count = - self.block_tx_counts.iter().try_fold(0u64, |acc, block_tx_count| { - acc.checked_add(*block_tx_count).ok_or(SpanBatchError::TooBigSpanBatchSize) - })?; - - // The total number of transactions in a span batch cannot be greater than - // [MAX_SPAN_BATCH_ELEMENTS]. - if total_block_tx_count > MAX_SPAN_BATCH_ELEMENTS { - return Err(SpanBatchError::TooBigSpanBatchSize); - } - self.txs.total_block_tx_count = total_block_tx_count; - self.txs.decode(r)?; - Ok(()) - } - - /// Encode the origin bits into a writer. - pub fn encode_origin_bits(&self, w: &mut Vec) -> Result<(), SpanBatchError> { - SpanBatchBits::encode(w, self.block_count as usize, &self.origin_bits) - } - - /// Encode the block count into a writer. - pub fn encode_block_count(&self, w: &mut Vec) { - let mut u64_varint_buf = [0u8; 10]; - w.extend_from_slice(unsigned_varint::encode::u64(self.block_count, &mut u64_varint_buf)); - } - - /// Encode the block transaction counts into a writer. - pub fn encode_block_tx_counts(&self, w: &mut Vec) { - let mut u64_varint_buf = [0u8; 10]; - for block_tx_count in &self.block_tx_counts { - u64_varint_buf.fill(0); - w.extend_from_slice(unsigned_varint::encode::u64(*block_tx_count, &mut u64_varint_buf)); - } - } - - /// Encode the transactions into a writer. - pub fn encode_txs(&self, w: &mut Vec) -> Result<(), SpanBatchError> { - self.txs.encode(w) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use alloc::vec; - - #[test] - fn test_decode_origin_bits() { - let block_count = 10; - let encoded = vec![2; block_count / 8 + 1]; - let mut payload = - SpanBatchPayload { block_count: block_count as u64, ..Default::default() }; - payload.decode_origin_bits(&mut encoded.as_slice()).unwrap(); - assert_eq!(payload.origin_bits, SpanBatchBits(vec![2; block_count / 8 + 1])); - } - - #[test] - fn test_zero_block_count() { - let mut u64_varint_buf = [0; 10]; - let mut encoded = unsigned_varint::encode::u64(0, &mut u64_varint_buf); - let mut payload = SpanBatchPayload::default(); - let err = payload.decode_block_count(&mut encoded).unwrap_err(); - assert_eq!(err, SpanBatchError::EmptySpanBatch); - } - - #[test] - fn test_decode_block_count() { - let block_count = MAX_SPAN_BATCH_ELEMENTS; - let mut u64_varint_buf = [0; 10]; - let mut encoded = unsigned_varint::encode::u64(block_count, &mut u64_varint_buf); - let mut payload = SpanBatchPayload::default(); - payload.decode_block_count(&mut encoded).unwrap(); - assert_eq!(payload.block_count, block_count); - } - - #[test] - fn test_decode_block_count_errors() { - let block_count = MAX_SPAN_BATCH_ELEMENTS + 1; - let mut u64_varint_buf = [0; 10]; - let mut encoded = unsigned_varint::encode::u64(block_count, &mut u64_varint_buf); - let mut payload = SpanBatchPayload::default(); - let err = payload.decode_block_count(&mut encoded).unwrap_err(); - assert_eq!(err, SpanBatchError::TooBigSpanBatchSize); - } - - #[test] - fn test_decode_block_tx_counts() { - let block_count = 2; - let mut u64_varint_buf = [0; 10]; - let mut encoded = unsigned_varint::encode::u64(block_count, &mut u64_varint_buf); - let mut payload = SpanBatchPayload::default(); - payload.decode_block_count(&mut encoded).unwrap(); - let mut r: Vec = Vec::new(); - for _ in 0..2 { - let mut buf = [0u8; 10]; - let encoded = unsigned_varint::encode::u64(2, &mut buf); - r.append(&mut encoded.to_vec()); - } - payload.decode_block_tx_counts(&mut r.as_slice()).unwrap(); - assert_eq!(payload.block_tx_counts, vec![2, 2]); - } -} diff --git a/crates/derive/src/batch/span_batch/prefix.rs b/crates/derive/src/batch/span_batch/prefix.rs deleted file mode 100644 index 51376f5c3..000000000 --- a/crates/derive/src/batch/span_batch/prefix.rs +++ /dev/null @@ -1,97 +0,0 @@ -//! Raw Span Batch Prefix - -use crate::batch::{SpanBatchError, SpanDecodingError}; -use alloc::vec::Vec; -use alloy_primitives::FixedBytes; - -/// Span Batch Prefix -#[derive(Debug, Clone, Default, PartialEq, Eq)] -pub struct SpanBatchPrefix { - /// Relative timestamp of the first block - pub rel_timestamp: u64, - /// L1 origin number - pub l1_origin_num: u64, - /// First 20 bytes of the first block's parent hash - pub parent_check: FixedBytes<20>, - /// First 20 bytes of the last block's L1 origin hash - pub l1_origin_check: FixedBytes<20>, -} - -impl SpanBatchPrefix { - /// Decodes a [SpanBatchPrefix] from a reader. - pub fn decode_prefix(r: &mut &[u8]) -> Result { - let mut prefix = Self::default(); - prefix.decode_rel_timestamp(r)?; - prefix.decode_l1_origin_num(r)?; - prefix.decode_parent_check(r)?; - prefix.decode_l1_origin_check(r)?; - Ok(prefix) - } - - /// Decodes the relative timestamp from a reader. - pub fn decode_rel_timestamp(&mut self, r: &mut &[u8]) -> Result<(), SpanBatchError> { - let (rel_timestamp, remaining) = unsigned_varint::decode::u64(r) - .map_err(|_| SpanBatchError::Decoding(SpanDecodingError::RelativeTimestamp))?; - *r = remaining; - self.rel_timestamp = rel_timestamp; - Ok(()) - } - - /// Decodes the L1 origin number from a reader. - pub fn decode_l1_origin_num(&mut self, r: &mut &[u8]) -> Result<(), SpanBatchError> { - let (l1_origin_num, remaining) = unsigned_varint::decode::u64(r) - .map_err(|_| SpanBatchError::Decoding(SpanDecodingError::L1OriginNumber))?; - *r = remaining; - self.l1_origin_num = l1_origin_num; - Ok(()) - } - - /// Decodes the parent check from a reader. - pub fn decode_parent_check(&mut self, r: &mut &[u8]) -> Result<(), SpanBatchError> { - let (parent_check, remaining) = r.split_at(20); - let parent_check = FixedBytes::<20>::from_slice(parent_check); - *r = remaining; - self.parent_check = parent_check; - Ok(()) - } - - /// Decodes the L1 origin check from a reader. - pub fn decode_l1_origin_check(&mut self, r: &mut &[u8]) -> Result<(), SpanBatchError> { - let (l1_origin_check, remaining) = r.split_at(20); - let l1_origin_check = FixedBytes::<20>::from_slice(l1_origin_check); - *r = remaining; - self.l1_origin_check = l1_origin_check; - Ok(()) - } - - /// Encodes the [SpanBatchPrefix] into a writer. - pub fn encode_prefix(&self, w: &mut Vec) { - let mut u64_buf = [0u8; 10]; - w.extend_from_slice(unsigned_varint::encode::u64(self.rel_timestamp, &mut u64_buf)); - w.extend_from_slice(unsigned_varint::encode::u64(self.l1_origin_num, &mut u64_buf)); - w.extend_from_slice(self.parent_check.as_slice()); - w.extend_from_slice(self.l1_origin_check.as_slice()); - } -} - -#[cfg(test)] -mod test { - use super::*; - use alloc::vec::Vec; - use alloy_primitives::address; - - #[test] - fn test_span_batch_prefix_encoding_roundtrip() { - let expected = SpanBatchPrefix { - rel_timestamp: 0xFF, - l1_origin_num: 0xEE, - parent_check: address!("beef00000000000000000000000000000000beef").into(), - l1_origin_check: address!("babe00000000000000000000000000000000babe").into(), - }; - - let mut buf = Vec::new(); - expected.encode_prefix(&mut buf); - - assert_eq!(SpanBatchPrefix::decode_prefix(&mut buf.as_slice()).unwrap(), expected); - } -} diff --git a/crates/derive/src/batch/span_batch/raw.rs b/crates/derive/src/batch/span_batch/raw.rs deleted file mode 100644 index 9e5ef6148..000000000 --- a/crates/derive/src/batch/span_batch/raw.rs +++ /dev/null @@ -1,173 +0,0 @@ -//! Raw Span Batch - -use super::{SpanBatch, SpanBatchElement, SpanBatchError, SpanBatchPayload, SpanBatchPrefix}; -use crate::batch::{BatchType, SpanDecodingError}; -use alloc::{vec, vec::Vec}; - -/// Raw Span Batch -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct RawSpanBatch { - /// The span batch prefix - pub prefix: SpanBatchPrefix, - /// The span batch payload - pub payload: SpanBatchPayload, -} - -impl TryFrom for RawSpanBatch { - type Error = SpanBatchError; - - fn try_from(value: SpanBatch) -> Result { - if value.batches.is_empty() { - return Err(SpanBatchError::EmptySpanBatch); - } - - // These should never error since we check for an empty batch above. - let span_start = value.batches.first().ok_or(SpanBatchError::EmptySpanBatch)?; - let span_end = value.batches.last().ok_or(SpanBatchError::EmptySpanBatch)?; - - Ok(Self { - prefix: SpanBatchPrefix { - rel_timestamp: span_start.timestamp - value.genesis_timestamp, - l1_origin_num: span_end.epoch_num, - parent_check: value.parent_check, - l1_origin_check: value.l1_origin_check, - }, - payload: SpanBatchPayload { - block_count: value.batches.len() as u64, - origin_bits: value.origin_bits.clone(), - block_tx_counts: value.block_tx_counts.clone(), - txs: value.txs.clone(), - }, - }) - } -} - -impl RawSpanBatch { - /// Returns the batch type - pub const fn get_batch_type(&self) -> BatchType { - BatchType::Span - } - - /// Encodes the [RawSpanBatch] into a writer. - pub fn encode(&self, w: &mut Vec) -> Result<(), SpanBatchError> { - self.prefix.encode_prefix(w); - self.payload.encode_payload(w) - } - - /// Decodes the [RawSpanBatch] from a reader.] - pub fn decode(r: &mut &[u8]) -> Result { - let prefix = SpanBatchPrefix::decode_prefix(r)?; - let payload = SpanBatchPayload::decode_payload(r)?; - Ok(Self { prefix, payload }) - } - - /// Converts a [RawSpanBatch] into a [SpanBatch], which has a list of [SpanBatchElement]s. Thos - /// function does not populate the [SpanBatch] with chain configuration data, which is - /// required for making payload attributes. - pub fn derive( - &mut self, - block_time: u64, - genesis_time: u64, - chain_id: u64, - ) -> Result { - if self.payload.block_count == 0 { - return Err(SpanBatchError::EmptySpanBatch); - } - - let mut block_origin_nums = vec![0u64; self.payload.block_count as usize]; - let mut l1_origin_number = self.prefix.l1_origin_num; - for i in (0..self.payload.block_count).rev() { - block_origin_nums[i as usize] = l1_origin_number; - if self - .payload - .origin_bits - .get_bit(i as usize) - .ok_or(SpanBatchError::Decoding(SpanDecodingError::L1OriginCheck))? == - 1 && - i > 0 - { - l1_origin_number -= 1; - } - } - - // Recover `v` values in transaction signatures within the batch. - self.payload.txs.recover_v(chain_id)?; - - // Get all transactions in the batch. - let enveloped_txs = self.payload.txs.full_txs(chain_id)?; - - let mut tx_idx = 0; - let batches = (0..self.payload.block_count).fold(Vec::new(), |mut acc, i| { - let transactions = - (0..self.payload.block_tx_counts[i as usize]).fold(Vec::new(), |mut acc, _| { - acc.push(enveloped_txs[tx_idx].clone()); - tx_idx += 1; - acc - }); - acc.push(SpanBatchElement { - epoch_num: block_origin_nums[i as usize], - timestamp: genesis_time + self.prefix.rel_timestamp + block_time * i, - transactions: transactions.into_iter().map(|v| v.into()).collect(), - }); - acc - }); - - Ok(SpanBatch { - parent_check: self.prefix.parent_check, - l1_origin_check: self.prefix.l1_origin_check, - batches, - ..Default::default() - }) - } -} - -#[cfg(test)] -mod test { - use super::{RawSpanBatch, SpanBatch, SpanBatchElement}; - use alloc::{vec, vec::Vec}; - use alloy_primitives::FixedBytes; - - #[test] - fn test_try_from_span_batch_empty_batches_errors() { - let span_batch = SpanBatch::default(); - let raw_span_batch = RawSpanBatch::try_from(span_batch).unwrap_err(); - assert_eq!(raw_span_batch, super::SpanBatchError::EmptySpanBatch); - } - - #[test] - fn test_try_from_span_batch_succeeds() { - let parent_check = FixedBytes::from([2u8; 20]); - let l1_origin_check = FixedBytes::from([3u8; 20]); - let first = SpanBatchElement { epoch_num: 100, timestamp: 400, transactions: Vec::new() }; - let last = SpanBatchElement { epoch_num: 200, timestamp: 500, transactions: Vec::new() }; - let span_batch = SpanBatch { - batches: vec![first, last], - genesis_timestamp: 300, - parent_check, - l1_origin_check, - ..Default::default() - }; - let expected_prefix = super::SpanBatchPrefix { - rel_timestamp: 100, - l1_origin_num: 200, - parent_check, - l1_origin_check, - }; - let expected_payload = super::SpanBatchPayload { block_count: 2, ..Default::default() }; - let raw_span_batch = RawSpanBatch::try_from(span_batch).unwrap(); - assert_eq!(raw_span_batch.prefix, expected_prefix); - assert_eq!(raw_span_batch.payload, expected_payload); - } - - #[test] - fn test_decode_encode_raw_span_batch() { - // Load in the raw span batch from the `op-node` derivation pipeline implementation. - let raw_span_batch_hex = include_bytes!("../../../testdata/raw_batch.hex"); - let mut raw_span_batch = RawSpanBatch::decode(&mut raw_span_batch_hex.as_slice()).unwrap(); - raw_span_batch.payload.txs.recover_v(981).unwrap(); - - let mut encoding_buf = Vec::new(); - raw_span_batch.encode(&mut encoding_buf).unwrap(); - assert_eq!(encoding_buf, raw_span_batch_hex); - } -} diff --git a/crates/derive/src/batch/span_batch/signature.rs b/crates/derive/src/batch/span_batch/signature.rs deleted file mode 100644 index baf1bc194..000000000 --- a/crates/derive/src/batch/span_batch/signature.rs +++ /dev/null @@ -1,28 +0,0 @@ -//! This module contains the [SpanBatchSignature] type, which represents the ECDSA signature of a -//! transaction within a span batch. - -use super::{SpanBatchError, SpanDecodingError}; -use alloy_primitives::{Signature, U256}; - -/// The ECDSA signature of a transaction within a span batch. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct SpanBatchSignature { - pub(crate) v: u64, - pub(crate) r: U256, - pub(crate) s: U256, -} - -impl From for SpanBatchSignature { - fn from(value: Signature) -> Self { - Self { v: value.v().to_u64(), r: value.r(), s: value.s() } - } -} - -impl TryFrom for Signature { - type Error = SpanBatchError; - - fn try_from(value: SpanBatchSignature) -> Result { - Self::from_rs_and_parity(value.r, value.s, value.v) - .map_err(|_| SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionSignature)) - } -} diff --git a/crates/derive/src/batch/span_batch/transactions.rs b/crates/derive/src/batch/span_batch/transactions.rs deleted file mode 100644 index 2802e1b25..000000000 --- a/crates/derive/src/batch/span_batch/transactions.rs +++ /dev/null @@ -1,463 +0,0 @@ -//! This module contains the [SpanBatchTransactions] type and logic for encoding and decoding -//! transactions in a span batch. - -use super::{ - convert_v_to_y_parity, read_tx_data, utils::is_protected_v, SpanBatchBits, SpanBatchError, - SpanBatchSignature, SpanBatchTransactionData, SpanDecodingError, MAX_SPAN_BATCH_ELEMENTS, -}; -use alloc::vec::Vec; -use alloy_consensus::{Transaction, TxEnvelope, TxType}; -use alloy_eips::eip2718::Encodable2718; -use alloy_primitives::{Address, Bytes, U256}; -use alloy_rlp::{Buf, Decodable, Encodable}; - -/// This struct contains the decoded information for transactions in a span batch. -#[derive(Debug, Default, Clone, PartialEq, Eq)] -pub struct SpanBatchTransactions { - /// The total number of transactions in a span batch. Must be manually set. - pub total_block_tx_count: u64, - /// The contract creation bits, standard span-batch bitlist. - pub contract_creation_bits: SpanBatchBits, - /// The y parity bits, standard span-batch bitlist. - pub y_parity_bits: SpanBatchBits, - /// The transaction signatures. - pub tx_sigs: Vec, - /// The transaction nonces - pub tx_nonces: Vec, - /// The transaction gas limits. - pub tx_gases: Vec, - /// The `to` addresses of the transactions. - pub tx_tos: Vec
, - /// The transaction data. - pub tx_datas: Vec>, - /// The protected bits, standard span-batch bitlist. - pub protected_bits: SpanBatchBits, - /// The types of the transactions. - pub tx_types: Vec, - /// Total legacy transaction count in the span batch. - pub legacy_tx_count: u64, -} - -impl SpanBatchTransactions { - /// Encodes the [SpanBatchTransactions] into a writer. - pub fn encode(&self, w: &mut Vec) -> Result<(), SpanBatchError> { - self.encode_contract_creation_bits(w)?; - self.encode_y_parity_bits(w)?; - self.encode_tx_sigs_rs(w)?; - self.encode_tx_tos(w)?; - self.encode_tx_datas(w)?; - self.encode_tx_nonces(w)?; - self.encode_tx_gases(w)?; - self.encode_protected_bits(w)?; - Ok(()) - } - - /// Decodes the [SpanBatchTransactions] from a reader. - pub fn decode(&mut self, r: &mut &[u8]) -> Result<(), SpanBatchError> { - self.decode_contract_creation_bits(r)?; - self.decode_y_parity_bits(r)?; - self.decode_tx_sigs_rs(r)?; - self.decode_tx_tos(r)?; - self.decode_tx_datas(r)?; - self.decode_tx_nonces(r)?; - self.decode_tx_gases(r)?; - self.decode_protected_bits(r)?; - Ok(()) - } - - /// Encode the contract creation bits into a writer. - pub fn encode_contract_creation_bits(&self, w: &mut Vec) -> Result<(), SpanBatchError> { - SpanBatchBits::encode(w, self.total_block_tx_count as usize, &self.contract_creation_bits)?; - Ok(()) - } - - /// Encode the protected bits into a writer. - pub fn encode_protected_bits(&self, w: &mut Vec) -> Result<(), SpanBatchError> { - SpanBatchBits::encode(w, self.legacy_tx_count as usize, &self.protected_bits)?; - Ok(()) - } - - /// Encode the y parity bits into a writer. - pub fn encode_y_parity_bits(&self, w: &mut Vec) -> Result<(), SpanBatchError> { - SpanBatchBits::encode(w, self.total_block_tx_count as usize, &self.y_parity_bits)?; - Ok(()) - } - - /// Encode the transaction signatures into a writer (excluding `v` field). - pub fn encode_tx_sigs_rs(&self, w: &mut Vec) -> Result<(), SpanBatchError> { - for sig in &self.tx_sigs { - w.extend_from_slice(&sig.r.to_be_bytes::<32>()); - w.extend_from_slice(&sig.s.to_be_bytes::<32>()); - } - Ok(()) - } - - /// Encode the transaction nonces into a writer. - pub fn encode_tx_nonces(&self, w: &mut Vec) -> Result<(), SpanBatchError> { - let mut buf = [0u8; 10]; - for nonce in &self.tx_nonces { - let slice = unsigned_varint::encode::u64(*nonce, &mut buf); - w.extend_from_slice(slice); - } - Ok(()) - } - - /// Encode the transaction gas limits into a writer. - pub fn encode_tx_gases(&self, w: &mut Vec) -> Result<(), SpanBatchError> { - let mut buf = [0u8; 10]; - for gas in &self.tx_gases { - let slice = unsigned_varint::encode::u64(*gas, &mut buf); - w.extend_from_slice(slice); - } - Ok(()) - } - - /// Encode the `to` addresses of the transactions into a writer. - pub fn encode_tx_tos(&self, w: &mut Vec) -> Result<(), SpanBatchError> { - for to in &self.tx_tos { - w.extend_from_slice(to.as_ref()); - } - Ok(()) - } - - /// Encode the transaction data into a writer. - pub fn encode_tx_datas(&self, w: &mut Vec) -> Result<(), SpanBatchError> { - for data in &self.tx_datas { - w.extend_from_slice(data); - } - Ok(()) - } - - /// Decode the contract creation bits from a reader. - pub fn decode_contract_creation_bits(&mut self, r: &mut &[u8]) -> Result<(), SpanBatchError> { - if self.total_block_tx_count > MAX_SPAN_BATCH_ELEMENTS { - return Err(SpanBatchError::TooBigSpanBatchSize); - } - - self.contract_creation_bits = SpanBatchBits::decode(r, self.total_block_tx_count as usize)?; - Ok(()) - } - - /// Decode the protected bits from a reader. - pub fn decode_protected_bits(&mut self, r: &mut &[u8]) -> Result<(), SpanBatchError> { - if self.legacy_tx_count > MAX_SPAN_BATCH_ELEMENTS { - return Err(SpanBatchError::TooBigSpanBatchSize); - } - - self.protected_bits = SpanBatchBits::decode(r, self.legacy_tx_count as usize)?; - Ok(()) - } - - /// Decode the y parity bits from a reader. - pub fn decode_y_parity_bits(&mut self, r: &mut &[u8]) -> Result<(), SpanBatchError> { - self.y_parity_bits = SpanBatchBits::decode(r, self.total_block_tx_count as usize)?; - Ok(()) - } - - /// Decode the transaction signatures from a reader (excluding `v` field). - pub fn decode_tx_sigs_rs(&mut self, r: &mut &[u8]) -> Result<(), SpanBatchError> { - let mut sigs = Vec::with_capacity(self.total_block_tx_count as usize); - for _ in 0..self.total_block_tx_count { - let r_val = U256::from_be_slice(&r[..32]); - let s_val = U256::from_be_slice(&r[32..64]); - sigs.push(SpanBatchSignature { v: 0, r: r_val, s: s_val }); - r.advance(64); - } - self.tx_sigs = sigs; - Ok(()) - } - - /// Decode the transaction nonces from a reader. - pub fn decode_tx_nonces(&mut self, r: &mut &[u8]) -> Result<(), SpanBatchError> { - let mut nonces = Vec::with_capacity(self.total_block_tx_count as usize); - for _ in 0..self.total_block_tx_count { - let (nonce, remaining) = unsigned_varint::decode::u64(r) - .map_err(|_| SpanBatchError::Decoding(SpanDecodingError::TxNonces))?; - nonces.push(nonce); - *r = remaining; - } - self.tx_nonces = nonces; - Ok(()) - } - - /// Decode the transaction gas limits from a reader. - pub fn decode_tx_gases(&mut self, r: &mut &[u8]) -> Result<(), SpanBatchError> { - let mut gases = Vec::with_capacity(self.total_block_tx_count as usize); - for _ in 0..self.total_block_tx_count { - let (gas, remaining) = unsigned_varint::decode::u64(r) - .map_err(|_| SpanBatchError::Decoding(SpanDecodingError::TxNonces))?; - gases.push(gas); - *r = remaining; - } - self.tx_gases = gases; - Ok(()) - } - - /// Decode the `to` addresses of the transactions from a reader. - pub fn decode_tx_tos(&mut self, r: &mut &[u8]) -> Result<(), SpanBatchError> { - let mut tos = Vec::with_capacity(self.total_block_tx_count as usize); - let contract_creation_count = self.contract_creation_count(); - for _ in 0..(self.total_block_tx_count - contract_creation_count) { - let to = Address::from_slice(&r[..20]); - tos.push(to); - r.advance(20); - } - self.tx_tos = tos; - Ok(()) - } - - /// Decode the transaction data from a reader. - pub fn decode_tx_datas(&mut self, r: &mut &[u8]) -> Result<(), SpanBatchError> { - let mut tx_datas = Vec::new(); - let mut tx_types = Vec::new(); - - // Do not need the transaction data header because the RLP stream already includes the - // length information. - for _ in 0..self.total_block_tx_count { - let (tx_data, tx_type) = read_tx_data(r)?; - tx_datas.push(tx_data); - tx_types.push(tx_type); - if matches!(tx_type, TxType::Legacy) { - self.legacy_tx_count += 1; - } - } - - self.tx_datas = tx_datas; - self.tx_types = tx_types; - - Ok(()) - } - - /// Returns the number of contract creation transactions in the span batch. - pub fn contract_creation_count(&self) -> u64 { - self.contract_creation_bits.0.iter().map(|b| b.count_ones() as u64).sum() - } - - /// Recover the `v` values of the transaction signatures. - pub fn recover_v(&mut self, chain_id: u64) -> Result<(), SpanBatchError> { - if self.tx_sigs.len() != self.tx_types.len() { - return Err(SpanBatchError::Decoding(SpanDecodingError::TypeSignatureLenMismatch)); - } - let mut protected_bits_idx = 0; - for (i, tx_type) in self.tx_types.iter().enumerate() { - let bit = self.y_parity_bits.get_bit(i).ok_or(SpanBatchError::BitfieldTooLong)?; - let v = match tx_type { - TxType::Legacy => { - // Legacy transaction - let protected_bit = self.protected_bits.get_bit(protected_bits_idx); - protected_bits_idx += 1; - if protected_bit.is_none() || protected_bit.is_some_and(|b| b == 0) { - Ok(27 + bit as u64) - } else { - // EIP-155 - Ok(chain_id * 2 + 35 + bit as u64) - } - } - TxType::Eip2930 | TxType::Eip1559 => Ok(bit as u64), - _ => Err(SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionType)), - }?; - self.tx_sigs.get_mut(i).expect("Transaction must exist").v = v; - } - Ok(()) - } - - /// Retrieve all of the raw transactions from the [SpanBatchTransactions]. - pub fn full_txs(&self, chain_id: u64) -> Result>, SpanBatchError> { - let mut txs = Vec::new(); - let mut to_idx = 0; - for idx in 0..self.total_block_tx_count { - let mut datas = self.tx_datas[idx as usize].as_slice(); - let tx = SpanBatchTransactionData::decode(&mut datas) - .map_err(|_| SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionData))?; - let nonce = self - .tx_nonces - .get(idx as usize) - .ok_or(SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionData))?; - let gas = self - .tx_gases - .get(idx as usize) - .ok_or(SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionData))?; - let bit = self - .contract_creation_bits - .get_bit(idx as usize) - .ok_or(SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionData))?; - let to = if bit == 0 { - if self.tx_tos.len() <= to_idx { - return Err(SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionData)); - } - to_idx += 1; - Some(self.tx_tos[to_idx - 1]) - } else { - None - }; - let sig = *self - .tx_sigs - .get(idx as usize) - .ok_or(SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionData))?; - let tx_envelope = tx.to_enveloped_tx(*nonce, *gas, to, chain_id, sig.try_into()?)?; - let mut buf = Vec::new(); - tx_envelope.encode_2718(&mut buf); - txs.push(buf); - } - Ok(txs) - } - - /// Add raw transactions into the [SpanBatchTransactions]. - pub fn add_txs(&mut self, txs: Vec, chain_id: u64) -> Result<(), SpanBatchError> { - let total_block_tx_count = txs.len() as u64; - let offset = self.total_block_tx_count; - - for i in 0..total_block_tx_count { - let tx_enveloped = TxEnvelope::decode(&mut txs[i as usize].as_ref()) - .map_err(|_| SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionData))?; - let span_batch_tx = SpanBatchTransactionData::try_from(&tx_enveloped)?; - - let tx_type = tx_enveloped.tx_type(); - if matches!(tx_type, TxType::Legacy) { - let protected_bit = is_protected_v(&tx_enveloped); - self.protected_bits.set_bit(self.legacy_tx_count as usize, protected_bit); - self.legacy_tx_count += 1; - } - - let (signature, to, nonce, gas, tx_chain_id) = match &tx_enveloped { - TxEnvelope::Legacy(tx) => { - let (tx, sig) = (tx.tx(), tx.signature()); - (sig, tx.to(), tx.nonce(), tx.gas_limit(), tx.chain_id()) - } - TxEnvelope::Eip2930(tx) => { - let (tx, sig) = (tx.tx(), tx.signature()); - (sig, tx.to(), tx.nonce(), tx.gas_limit(), tx.chain_id()) - } - TxEnvelope::Eip1559(tx) => { - let (tx, sig) = (tx.tx(), tx.signature()); - (sig, tx.to(), tx.nonce(), tx.gas_limit(), tx.chain_id()) - } - _ => { - return Err(SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionData)) - } - }; - - if is_protected_v(&tx_enveloped) && - tx_chain_id - .ok_or(SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionData))? != - chain_id - { - return Err(SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionData)); - } - - let signature_v = signature.v().to_u64(); - let y_parity_bit = convert_v_to_y_parity(signature_v, tx_type)?; - let contract_creation_bit = match to { - Some(address) => { - self.tx_tos.push(address); - 0 - } - None => 1, - }; - let mut tx_data_buf = Vec::new(); - span_batch_tx.encode(&mut tx_data_buf); - - self.tx_sigs.push((*signature).into()); - self.contract_creation_bits.set_bit((i + offset) as usize, contract_creation_bit == 1); - self.y_parity_bits.set_bit((i + offset) as usize, y_parity_bit); - self.tx_nonces.push(nonce); - self.tx_datas.push(tx_data_buf); - self.tx_gases.push(gas); - self.tx_types.push(tx_type); - } - self.total_block_tx_count += total_block_tx_count; - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use alloc::vec; - use alloy_consensus::{Signed, TxEip1559, TxEip2930, TxLegacy}; - use alloy_primitives::{address, Signature, TxKind}; - - #[test] - fn test_span_batch_transactions_add_empty_txs() { - let mut span_batch_txs = SpanBatchTransactions::default(); - let txs = vec![]; - let chain_id = 1; - let result = span_batch_txs.add_txs(txs, chain_id); - assert!(result.is_ok()); - assert_eq!(span_batch_txs.total_block_tx_count, 0); - } - - #[test] - fn test_span_batch_transactions_add_invalid_legacy_parity_decoding() { - let sig = Signature::test_signature(); - let to = address!("0123456789012345678901234567890123456789"); - let tx = TxEnvelope::Legacy(Signed::new_unchecked( - TxLegacy { to: TxKind::Call(to), ..Default::default() }, - sig, - Default::default(), - )); - let mut span_batch_txs = SpanBatchTransactions::default(); - let mut buf = vec![]; - tx.encode(&mut buf); - let txs = vec![Bytes::from(buf)]; - let chain_id = 1; - let err = span_batch_txs.add_txs(txs, chain_id).unwrap_err(); - assert_eq!(err, SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionData)); - } - - #[test] - fn test_span_batch_transactions_add_eip2930_tx_wrong_chain_id() { - let sig = Signature::test_signature(); - let to = address!("0123456789012345678901234567890123456789"); - let tx = TxEnvelope::Eip2930(Signed::new_unchecked( - TxEip2930 { to: TxKind::Call(to), ..Default::default() }, - sig, - Default::default(), - )); - let mut span_batch_txs = SpanBatchTransactions::default(); - let mut buf = vec![]; - tx.encode(&mut buf); - let txs = vec![Bytes::from(buf)]; - let chain_id = 1; - let err = span_batch_txs.add_txs(txs, chain_id).unwrap_err(); - assert_eq!(err, SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionData)); - } - - #[test] - fn test_span_batch_transactions_add_eip2930_tx() { - let sig = Signature::test_signature(); - let to = address!("0123456789012345678901234567890123456789"); - let tx = TxEnvelope::Eip2930(Signed::new_unchecked( - TxEip2930 { to: TxKind::Call(to), chain_id: 1, ..Default::default() }, - sig, - Default::default(), - )); - let mut span_batch_txs = SpanBatchTransactions::default(); - let mut buf = vec![]; - tx.encode(&mut buf); - let txs = vec![Bytes::from(buf)]; - let chain_id = 1; - let result = span_batch_txs.add_txs(txs, chain_id); - assert_eq!(result, Ok(())); - assert_eq!(span_batch_txs.total_block_tx_count, 1); - } - - #[test] - fn test_span_batch_transactions_add_eip1559_tx() { - let sig = Signature::test_signature(); - let to = address!("0123456789012345678901234567890123456789"); - let tx = TxEnvelope::Eip1559(Signed::new_unchecked( - TxEip1559 { to: TxKind::Call(to), chain_id: 1, ..Default::default() }, - sig, - Default::default(), - )); - let mut span_batch_txs = SpanBatchTransactions::default(); - let mut buf = vec![]; - tx.encode(&mut buf); - let txs = vec![Bytes::from(buf)]; - let chain_id = 1; - let result = span_batch_txs.add_txs(txs, chain_id); - assert_eq!(result, Ok(())); - assert_eq!(span_batch_txs.total_block_tx_count, 1); - } -} diff --git a/crates/derive/src/batch/span_batch/tx_data/eip1559.rs b/crates/derive/src/batch/span_batch/tx_data/eip1559.rs deleted file mode 100644 index 279b81251..000000000 --- a/crates/derive/src/batch/span_batch/tx_data/eip1559.rs +++ /dev/null @@ -1,85 +0,0 @@ -//! This module contains the eip1559 transaction data type for a span batch. - -use crate::batch::{SpanBatchError, SpanDecodingError}; -use alloy_consensus::{SignableTransaction, Signed, TxEip1559, TxEnvelope}; -use alloy_eips::eip2930::AccessList; -use alloy_primitives::{Address, Signature, TxKind, U256}; -use alloy_rlp::{Bytes, RlpDecodable, RlpEncodable}; - -/// The transaction data for an EIP-1559 transaction within a span batch. -#[derive(Debug, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)] -pub struct SpanBatchEip1559TransactionData { - /// The ETH value of the transaction. - pub value: U256, - /// Maximum priority fee per gas. - pub max_priority_fee_per_gas: U256, - /// Maximum fee per gas. - pub max_fee_per_gas: U256, - /// Transaction calldata. - pub data: Bytes, - /// Access list, used to pre-warm storage slots through static declaration. - pub access_list: AccessList, -} - -impl SpanBatchEip1559TransactionData { - /// Converts [SpanBatchEip1559TransactionData] into a [TxEnvelope]. - pub fn to_enveloped_tx( - &self, - nonce: u64, - gas: u64, - to: Option
, - chain_id: u64, - signature: Signature, - ) -> Result { - let eip1559_tx = TxEip1559 { - chain_id, - nonce, - max_fee_per_gas: u128::from_be_bytes( - self.max_fee_per_gas.to_be_bytes::<32>()[16..].try_into().map_err(|_| { - SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionData) - })?, - ), - max_priority_fee_per_gas: u128::from_be_bytes( - self.max_priority_fee_per_gas.to_be_bytes::<32>()[16..].try_into().map_err( - |_| SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionData), - )?, - ), - gas_limit: gas, - to: to.map_or(TxKind::Create, TxKind::Call), - value: self.value, - input: self.data.clone().into(), - access_list: self.access_list.clone(), - }; - let signature_hash = eip1559_tx.signature_hash(); - let signed_eip1559_tx = Signed::new_unchecked(eip1559_tx, signature, signature_hash); - Ok(TxEnvelope::Eip1559(signed_eip1559_tx)) - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::batch::SpanBatchTransactionData; - use alloc::vec::Vec; - use alloy_rlp::{Decodable, Encodable}; - - #[test] - fn encode_eip1559_tx_data_roundtrip() { - let variable_fee_tx = SpanBatchEip1559TransactionData { - value: U256::from(0xFF), - max_fee_per_gas: U256::from(0xEE), - max_priority_fee_per_gas: U256::from(0xDD), - data: Bytes::from(alloc::vec![0x01, 0x02, 0x03]), - access_list: AccessList::default(), - }; - let mut encoded_buf = Vec::new(); - SpanBatchTransactionData::Eip1559(variable_fee_tx.clone()).encode(&mut encoded_buf); - - let decoded = SpanBatchTransactionData::decode(&mut encoded_buf.as_slice()).unwrap(); - let SpanBatchTransactionData::Eip1559(variable_fee_decoded) = decoded else { - panic!("Expected SpanBatchEip1559TransactionData, got {:?}", decoded); - }; - - assert_eq!(variable_fee_tx, variable_fee_decoded); - } -} diff --git a/crates/derive/src/batch/span_batch/tx_data/eip2930.rs b/crates/derive/src/batch/span_batch/tx_data/eip2930.rs deleted file mode 100644 index 0062abc63..000000000 --- a/crates/derive/src/batch/span_batch/tx_data/eip2930.rs +++ /dev/null @@ -1,78 +0,0 @@ -//! This module contains the eip2930 transaction data type for a span batch. - -use crate::batch::{SpanBatchError, SpanDecodingError}; -use alloy_consensus::{SignableTransaction, Signed, TxEip2930, TxEnvelope}; -use alloy_eips::eip2930::AccessList; -use alloy_primitives::{Address, Signature, TxKind, U256}; -use alloy_rlp::{Bytes, RlpDecodable, RlpEncodable}; - -/// The transaction data for an EIP-2930 transaction within a span batch. -#[derive(Debug, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)] -pub struct SpanBatchEip2930TransactionData { - /// The ETH value of the transaction. - pub value: U256, - /// The gas price of the transaction. - pub gas_price: U256, - /// Transaction calldata. - pub data: Bytes, - /// Access list, used to pre-warm storage slots through static declaration. - pub access_list: AccessList, -} - -impl SpanBatchEip2930TransactionData { - /// Converts [SpanBatchEip2930TransactionData] into a [TxEnvelope]. - pub fn to_enveloped_tx( - &self, - nonce: u64, - gas: u64, - to: Option
, - chain_id: u64, - signature: Signature, - ) -> Result { - let access_list_tx = TxEip2930 { - chain_id, - nonce, - gas_price: u128::from_be_bytes( - self.gas_price.to_be_bytes::<32>()[16..].try_into().map_err(|_| { - SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionData) - })?, - ), - gas_limit: gas, - to: to.map_or(TxKind::Create, TxKind::Call), - value: self.value, - input: self.data.clone().into(), - access_list: self.access_list.clone(), - }; - let signature_hash = access_list_tx.signature_hash(); - let signed_access_list_tx = - Signed::new_unchecked(access_list_tx, signature, signature_hash); - Ok(TxEnvelope::Eip2930(signed_access_list_tx)) - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::batch::SpanBatchTransactionData; - use alloc::vec::Vec; - use alloy_rlp::{Decodable, Encodable}; - - #[test] - fn encode_eip2930_tx_data_roundtrip() { - let access_list_tx = SpanBatchEip2930TransactionData { - value: U256::from(0xFF), - gas_price: U256::from(0xEE), - data: Bytes::from(alloc::vec![0x01, 0x02, 0x03]), - access_list: AccessList::default(), - }; - let mut encoded_buf = Vec::new(); - SpanBatchTransactionData::Eip2930(access_list_tx.clone()).encode(&mut encoded_buf); - - let decoded = SpanBatchTransactionData::decode(&mut encoded_buf.as_slice()).unwrap(); - let SpanBatchTransactionData::Eip2930(access_list_decoded) = decoded else { - panic!("Expected SpanBatchEip2930TransactionData, got {:?}", decoded); - }; - - assert_eq!(access_list_tx, access_list_decoded); - } -} diff --git a/crates/derive/src/batch/span_batch/tx_data/legacy.rs b/crates/derive/src/batch/span_batch/tx_data/legacy.rs deleted file mode 100644 index 0f8649f68..000000000 --- a/crates/derive/src/batch/span_batch/tx_data/legacy.rs +++ /dev/null @@ -1,106 +0,0 @@ -//! This module contains the legacy transaction data type for a span batch. - -use crate::batch::{SpanBatchError, SpanDecodingError}; -use alloy_consensus::{SignableTransaction, Signed, TxEnvelope, TxLegacy}; -use alloy_primitives::{Address, Signature, TxKind, U256}; -use alloy_rlp::{Bytes, RlpDecodable, RlpEncodable}; - -/// The transaction data for a legacy transaction within a span batch. -#[derive(Debug, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)] -pub struct SpanBatchLegacyTransactionData { - /// The ETH value of the transaction. - pub value: U256, - /// The gas price of the transaction. - pub gas_price: U256, - /// Transaction calldata. - pub data: Bytes, -} - -impl SpanBatchLegacyTransactionData { - /// Converts [SpanBatchLegacyTransactionData] into a [TxEnvelope]. - pub fn to_enveloped_tx( - &self, - nonce: u64, - gas: u64, - to: Option
, - chain_id: u64, - signature: Signature, - ) -> Result { - let legacy_tx = TxLegacy { - chain_id: Some(chain_id), - nonce, - gas_price: u128::from_be_bytes( - self.gas_price.to_be_bytes::<32>()[16..].try_into().map_err(|_| { - SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionData) - })?, - ), - gas_limit: gas, - to: to.map_or(TxKind::Create, TxKind::Call), - value: self.value, - input: self.data.clone().into(), - }; - let signature_hash = legacy_tx.signature_hash(); - let signed_legacy_tx = Signed::new_unchecked(legacy_tx, signature, signature_hash); - Ok(TxEnvelope::Legacy(signed_legacy_tx)) - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::batch::SpanBatchTransactionData; - use alloc::vec::Vec; - use alloy_rlp::{Decodable, Encodable as _}; - // use alloy_primitives::B256; - - // #[test] - // fn to_enveloped_tx() { - // let legacy_tx = SpanBatchLegacyTransactionData { - // value: U256::from(0xFF), - // gas_price: U256::from(0xEE), - // data: Bytes::from(alloc::vec![0x01, 0x02, 0x03]), - // }; - // let nonce = 0x1234; - // let gas = 0x5678; - // let to = None; - // let chain_id = 0x9ABC; - // let signature = &[0x01; 65]; - // let signature = Signature::decode(&mut &signature[..]).unwrap(); - // let enveloped_tx = legacy_tx - // .to_enveloped_tx(nonce, gas, to, chain_id, signature) - // .unwrap(); - // let expected = TxEnvelope::Legacy(crate::types::network::Signed::new_unchecked( - // crate::types::TxLegacy { - // chain_id: Some(chain_id), - // nonce, - // gas_price: 0xEE, - // gas_limit: gas, - // to: crate::types::TxKind::Create, - // value: U256::from(0xFF), - // input: Bytes::from(alloc::vec![0x01, 0x02, 0x03]).into(), - // }, - // signature, - // B256::from([0x01; 32]), - // )); - // assert_eq!(enveloped_tx, expected); - // } - - #[test] - fn encode_legacy_tx_data_roundtrip() { - let legacy_tx = SpanBatchLegacyTransactionData { - value: U256::from(0xFF), - gas_price: U256::from(0xEE), - data: Bytes::from(alloc::vec![0x01, 0x02, 0x03]), - }; - - let mut encoded_buf = Vec::new(); - SpanBatchTransactionData::Legacy(legacy_tx.clone()).encode(&mut encoded_buf); - - let decoded = SpanBatchTransactionData::decode(&mut encoded_buf.as_slice()).unwrap(); - let SpanBatchTransactionData::Legacy(legacy_decoded) = decoded else { - panic!("Expected SpanBatchLegacyTransactionData, got {:?}", decoded); - }; - - assert_eq!(legacy_tx, legacy_decoded); - } -} diff --git a/crates/derive/src/batch/span_batch/tx_data/mod.rs b/crates/derive/src/batch/span_batch/tx_data/mod.rs deleted file mode 100644 index 06f13ad11..000000000 --- a/crates/derive/src/batch/span_batch/tx_data/mod.rs +++ /dev/null @@ -1,13 +0,0 @@ -//! Contains all the Span Batch Transaction Data types. - -mod wrapper; -pub use wrapper::SpanBatchTransactionData; - -mod legacy; -pub use legacy::SpanBatchLegacyTransactionData; - -mod eip1559; -pub use eip1559::SpanBatchEip1559TransactionData; - -mod eip2930; -pub use eip2930::SpanBatchEip2930TransactionData; diff --git a/crates/derive/src/batch/span_batch/tx_data/wrapper.rs b/crates/derive/src/batch/span_batch/tx_data/wrapper.rs deleted file mode 100644 index 7e273345d..000000000 --- a/crates/derive/src/batch/span_batch/tx_data/wrapper.rs +++ /dev/null @@ -1,131 +0,0 @@ -//! This module contains the top level span batch transaction data type. - -use super::{ - SpanBatchEip1559TransactionData, SpanBatchEip2930TransactionData, - SpanBatchLegacyTransactionData, -}; -use crate::batch::{SpanBatchError, SpanDecodingError}; -use alloy_consensus::{Transaction, TxEnvelope, TxType}; -use alloy_primitives::{Address, Signature, U256}; -use alloy_rlp::{Bytes, Decodable, Encodable}; - -/// The typed transaction data for a transaction within a span batch. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum SpanBatchTransactionData { - /// Legacy transaction data. - Legacy(SpanBatchLegacyTransactionData), - /// EIP-2930 transaction data. - Eip2930(SpanBatchEip2930TransactionData), - /// EIP-1559 transaction data. - Eip1559(SpanBatchEip1559TransactionData), -} - -impl Encodable for SpanBatchTransactionData { - fn encode(&self, out: &mut dyn alloy_rlp::BufMut) { - match self { - Self::Legacy(data) => { - data.encode(out); - } - Self::Eip2930(data) => { - out.put_u8(TxType::Eip2930 as u8); - data.encode(out); - } - Self::Eip1559(data) => { - out.put_u8(TxType::Eip1559 as u8); - data.encode(out); - } - } - } -} - -impl Decodable for SpanBatchTransactionData { - fn decode(r: &mut &[u8]) -> Result { - if !r.is_empty() && r[0] > 0x7F { - // Legacy transaction - return Ok(Self::Legacy(SpanBatchLegacyTransactionData::decode(r)?)); - } - // Non-legacy transaction (EIP-2718 envelope encoding) - Self::decode_typed(r) - } -} - -impl TryFrom<&TxEnvelope> for SpanBatchTransactionData { - type Error = SpanBatchError; - - fn try_from(tx_envelope: &TxEnvelope) -> Result { - match tx_envelope { - TxEnvelope::Legacy(s) => { - let s = s.tx(); - Ok(Self::Legacy(SpanBatchLegacyTransactionData { - value: s.value, - gas_price: U256::from(s.gas_price), - data: Bytes::from(s.input().to_vec()), - })) - } - TxEnvelope::Eip2930(s) => { - let s = s.tx(); - Ok(Self::Eip2930(SpanBatchEip2930TransactionData { - value: s.value, - gas_price: U256::from(s.gas_price), - data: Bytes::from(s.input().to_vec()), - access_list: s.access_list.clone(), - })) - } - TxEnvelope::Eip1559(s) => { - let s = s.tx(); - Ok(Self::Eip1559(SpanBatchEip1559TransactionData { - value: s.value, - max_fee_per_gas: U256::from(s.max_fee_per_gas), - max_priority_fee_per_gas: U256::from(s.max_priority_fee_per_gas), - data: Bytes::from(s.input().to_vec()), - access_list: s.access_list.clone(), - })) - } - _ => Err(SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionType)), - } - } -} - -impl SpanBatchTransactionData { - /// Returns the transaction type of the [SpanBatchTransactionData]. - pub const fn tx_type(&self) -> TxType { - match self { - Self::Legacy(_) => TxType::Legacy, - Self::Eip2930(_) => TxType::Eip2930, - Self::Eip1559(_) => TxType::Eip1559, - } - } - - /// Decodes a typed transaction into a [SpanBatchTransactionData] from a byte slice. - pub fn decode_typed(b: &[u8]) -> Result { - if b.len() <= 1 { - return Err(alloy_rlp::Error::Custom("Invalid transaction data")); - } - - match b[0].try_into().map_err(|_| alloy_rlp::Error::Custom("Invalid tx type"))? { - TxType::Eip2930 => { - Ok(Self::Eip2930(SpanBatchEip2930TransactionData::decode(&mut &b[1..])?)) - } - TxType::Eip1559 => { - Ok(Self::Eip1559(SpanBatchEip1559TransactionData::decode(&mut &b[1..])?)) - } - _ => Err(alloy_rlp::Error::Custom("Invalid transaction type")), - } - } - - /// Converts the [SpanBatchTransactionData] into a [TxEnvelope]. - pub fn to_enveloped_tx( - &self, - nonce: u64, - gas: u64, - to: Option
, - chain_id: u64, - signature: Signature, - ) -> Result { - match self { - Self::Legacy(data) => data.to_enveloped_tx(nonce, gas, to, chain_id, signature), - Self::Eip2930(data) => data.to_enveloped_tx(nonce, gas, to, chain_id, signature), - Self::Eip1559(data) => data.to_enveloped_tx(nonce, gas, to, chain_id, signature), - } - } -} diff --git a/crates/derive/src/batch/span_batch/utils.rs b/crates/derive/src/batch/span_batch/utils.rs deleted file mode 100644 index 06338cbe7..000000000 --- a/crates/derive/src/batch/span_batch/utils.rs +++ /dev/null @@ -1,141 +0,0 @@ -//! Utilities for Span Batch Encoding and Decoding. - -use super::{SpanBatchError, SpanDecodingError}; -use alloc::vec::Vec; -use alloy_consensus::{TxEnvelope, TxType}; -use alloy_rlp::{Buf, Header}; - -/// Reads transaction data from a reader. -pub(crate) fn read_tx_data(r: &mut &[u8]) -> Result<(Vec, TxType), SpanBatchError> { - let mut tx_data = Vec::new(); - let first_byte = - *r.first().ok_or(SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionData))?; - let mut tx_type = 0; - if first_byte <= 0x7F { - // EIP-2718: Non-legacy tx, so write tx type - tx_type = first_byte; - tx_data.push(tx_type); - r.advance(1); - } - - // Read the RLP header with a different reader pointer. This prevents the initial pointer from - // being advanced in the case that what we read is invalid. - let rlp_header = Header::decode(&mut (**r).as_ref()) - .map_err(|_| SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionData))?; - - let tx_payload = if rlp_header.list { - // Grab the raw RLP for the transaction data from `r`. It was unaffected since we copied it. - let payload_length_with_header = rlp_header.payload_length + rlp_header.length(); - let payload = r[0..payload_length_with_header].to_vec(); - r.advance(payload_length_with_header); - Ok(payload) - } else { - Err(SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionData)) - }?; - tx_data.extend_from_slice(&tx_payload); - - Ok(( - tx_data, - tx_type - .try_into() - .map_err(|_| SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionType))?, - )) -} - -/// Converts a `v` value to a y parity bit, from the transaaction type. -pub(crate) const fn convert_v_to_y_parity(v: u64, tx_type: TxType) -> Result { - match tx_type { - TxType::Legacy => { - if v != 27 && v != 28 { - // EIP-155: v = 2 * chain_id + 35 + yParity - Ok((v - 35) & 1 == 1) - } else { - // Unprotected legacy txs must have v = 27 or 28 - Ok(v - 27 == 1) - } - } - TxType::Eip2930 | TxType::Eip1559 => Ok(v == 1), - _ => Err(SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionType)), - } -} - -/// Checks if the signature of the passed [TxEnvelope] is protected. -pub(crate) const fn is_protected_v(tx: &TxEnvelope) -> bool { - match tx { - TxEnvelope::Legacy(tx) => { - let v = tx.signature().v().to_u64(); - if 64 - v.leading_zeros() <= 8 { - return v != 27 && v != 28 && v != 1 && v != 0; - } - // anything not 27 or 28 is considered protected - true - } - _ => true, - } -} - -#[cfg(test)] -mod tests { - use super::*; - use alloy_consensus::{ - Signed, TxEip1559, TxEip2930, TxEip4844, TxEip4844Variant, TxEip7702, TxLegacy, - }; - use alloy_primitives::{b256, Signature}; - - #[test] - fn test_convert_v_to_y_parity() { - assert_eq!(convert_v_to_y_parity(27, TxType::Legacy), Ok(false)); - assert_eq!(convert_v_to_y_parity(28, TxType::Legacy), Ok(true)); - assert_eq!(convert_v_to_y_parity(36, TxType::Legacy), Ok(true)); - assert_eq!(convert_v_to_y_parity(37, TxType::Legacy), Ok(false)); - assert_eq!(convert_v_to_y_parity(1, TxType::Eip2930), Ok(true)); - assert_eq!(convert_v_to_y_parity(1, TxType::Eip1559), Ok(true)); - assert_eq!( - convert_v_to_y_parity(1, TxType::Eip4844), - Err(SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionType)) - ); - assert_eq!( - convert_v_to_y_parity(0, TxType::Eip7702), - Err(SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionType)) - ); - } - - #[test] - fn test_is_protected_v() { - let sig = Signature::test_signature(); - assert!(!is_protected_v(&TxEnvelope::Legacy(Signed::new_unchecked( - TxLegacy::default(), - sig, - Default::default(), - )))); - let r = b256!("840cfc572845f5786e702984c2a582528cad4b49b2a10b9db1be7fca90058565"); - let s = b256!("25e7109ceb98168d95b09b18bbf6b685130e0562f233877d492b94eee0c5b6d1"); - let v = 27; - let valid_sig = Signature::from_scalars_and_parity(r, s, v).unwrap(); - assert!(!is_protected_v(&TxEnvelope::Legacy(Signed::new_unchecked( - TxLegacy::default(), - valid_sig, - Default::default(), - )))); - assert!(is_protected_v(&TxEnvelope::Eip2930(Signed::new_unchecked( - TxEip2930::default(), - sig, - Default::default(), - )))); - assert!(is_protected_v(&TxEnvelope::Eip1559(Signed::new_unchecked( - TxEip1559::default(), - sig, - Default::default(), - )))); - assert!(is_protected_v(&TxEnvelope::Eip4844(Signed::new_unchecked( - TxEip4844Variant::TxEip4844(TxEip4844::default()), - sig, - Default::default(), - )))); - assert!(is_protected_v(&TxEnvelope::Eip7702(Signed::new_unchecked( - TxEip7702::default(), - sig, - Default::default(), - )))); - } -} diff --git a/crates/derive/src/batch/validity.rs b/crates/derive/src/batch/validity.rs deleted file mode 100644 index 665163375..000000000 --- a/crates/derive/src/batch/validity.rs +++ /dev/null @@ -1,38 +0,0 @@ -//! Contains the [BatchValidity] and its encodings. - -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -/// Batch Validity -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum BatchValidity { - /// The batch is invalid now and in the future, unless we reorg - Drop, - /// The batch is valid and should be processed - Accept, - /// We are lacking L1 information until we can proceed batch filtering - Undecided, - /// The batch may be valid, but cannot be processed yet and should be checked again later - Future, - /// Introduced in Holocene, a special variant of the Drop variant that signals not to flush - /// the active batch and channel, in the case of processing an old batch - Past, -} - -impl BatchValidity { - /// Returns if the batch is dropped. - pub const fn is_drop(&self) -> bool { - matches!(self, Self::Drop) - } - - /// Returns if the batch is outdated. - pub const fn is_outdated(&self) -> bool { - matches!(self, Self::Past) - } - - /// Returns if the batch is future. - pub const fn is_future(&self) -> bool { - matches!(self, Self::Future) - } -} diff --git a/crates/derive/src/errors.rs b/crates/derive/src/errors.rs index adaf77b4d..6a4e1bf7e 100644 --- a/crates/derive/src/errors.rs +++ b/crates/derive/src/errors.rs @@ -1,11 +1,10 @@ //! This module contains derivation errors thrown within the pipeline. -use crate::batch::{SpanBatchError, MAX_SPAN_BATCH_ELEMENTS}; use alloc::string::String; use alloy_eips::BlockNumHash; use alloy_primitives::B256; use op_alloy_genesis::system::SystemConfigUpdateError; -use op_alloy_protocol::DepositError; +use op_alloy_protocol::{DepositError, SpanBatchError, MAX_SPAN_BATCH_ELEMENTS}; /// Blob Decuding Error #[derive(derive_more::Display, Debug, PartialEq, Eq)] diff --git a/crates/derive/src/lib.rs b/crates/derive/src/lib.rs index 2e6691467..bee907787 100644 --- a/crates/derive/src/lib.rs +++ b/crates/derive/src/lib.rs @@ -21,7 +21,6 @@ pub mod prelude { } pub mod attributes; -pub mod batch; pub mod errors; pub mod pipeline; pub mod sources; diff --git a/crates/derive/src/stages/attributes_queue.rs b/crates/derive/src/stages/attributes_queue.rs index 4546ba7bf..f746d365a 100644 --- a/crates/derive/src/stages/attributes_queue.rs +++ b/crates/derive/src/stages/attributes_queue.rs @@ -1,7 +1,6 @@ //! Contains the logic for the `AttributesQueue` stage. use crate::{ - batch::SingleBatch, errors::{PipelineError, PipelineResult, ResetError}, traits::{ AttributesBuilder, AttributesProvider, NextAttributes, OriginAdvancer, OriginProvider, @@ -12,7 +11,7 @@ use alloc::{boxed::Box, sync::Arc}; use async_trait::async_trait; use core::fmt::Debug; use op_alloy_genesis::RollupConfig; -use op_alloy_protocol::{BlockInfo, L2BlockInfo}; +use op_alloy_protocol::{BlockInfo, L2BlockInfo, SingleBatch}; use op_alloy_rpc_types_engine::{OpAttributesWithParent, OpPayloadAttributes}; use tracing::info; diff --git a/crates/derive/src/stages/batch/batch_provider.rs b/crates/derive/src/stages/batch/batch_provider.rs index b30093390..315aa74af 100644 --- a/crates/derive/src/stages/batch/batch_provider.rs +++ b/crates/derive/src/stages/batch/batch_provider.rs @@ -2,7 +2,6 @@ use super::NextBatchProvider; use crate::{ - batch::SingleBatch, errors::{PipelineError, PipelineResult}, stages::{BatchQueue, BatchValidator}, traits::{ @@ -13,7 +12,7 @@ use alloc::{boxed::Box, sync::Arc}; use async_trait::async_trait; use core::fmt::Debug; use op_alloy_genesis::RollupConfig; -use op_alloy_protocol::{BlockInfo, L2BlockInfo}; +use op_alloy_protocol::{BlockInfo, L2BlockInfo, SingleBatch}; /// The [BatchProvider] stage is a mux between the [BatchQueue] and [BatchValidator] stages. /// diff --git a/crates/derive/src/stages/batch/batch_queue.rs b/crates/derive/src/stages/batch/batch_queue.rs index f6ab8087b..dc8ba759f 100644 --- a/crates/derive/src/stages/batch/batch_queue.rs +++ b/crates/derive/src/stages/batch/batch_queue.rs @@ -2,7 +2,6 @@ use super::NextBatchProvider; use crate::{ - batch::{Batch, BatchValidity, BatchWithInclusionBlock, SingleBatch}, errors::{PipelineEncodingError, PipelineError, PipelineErrorKind, PipelineResult, ResetError}, traits::{ AttributesProvider, L2ChainProvider, OriginAdvancer, OriginProvider, ResetSignal, Signal, @@ -13,7 +12,9 @@ use alloc::{boxed::Box, sync::Arc, vec::Vec}; use async_trait::async_trait; use core::fmt::Debug; use op_alloy_genesis::RollupConfig; -use op_alloy_protocol::{BlockInfo, L2BlockInfo}; +use op_alloy_protocol::{ + Batch, BatchValidity, BatchWithInclusionBlock, BlockInfo, L2BlockInfo, SingleBatch, +}; use tracing::{error, info, warn}; /// [BatchQueue] is responsible for o rdering unordered batches @@ -53,7 +54,7 @@ where pub(crate) batches: Vec, /// A set of cached [SingleBatch]es derived from [SpanBatch]es. /// - /// [SpanBatch]: crate::batch::SpanBatch + /// [SpanBatch]: op_alloy_protocol::SpanBatch pub(crate) next_spans: Vec, /// Used to validate the batches. pub(crate) fetcher: BF, diff --git a/crates/derive/src/stages/batch/batch_stream.rs b/crates/derive/src/stages/batch/batch_stream.rs index 8999ca126..b932420f1 100644 --- a/crates/derive/src/stages/batch/batch_stream.rs +++ b/crates/derive/src/stages/batch/batch_stream.rs @@ -1,7 +1,6 @@ //! This module contains the `BatchStream` stage. use crate::{ - batch::{Batch, BatchValidity, BatchWithInclusionBlock, SingleBatch, SpanBatch}, errors::{PipelineEncodingError, PipelineError, PipelineResult}, stages::NextBatchProvider, traits::{L2ChainProvider, OriginAdvancer, OriginProvider, Signal, SignalReceiver}, @@ -10,7 +9,9 @@ use alloc::{boxed::Box, collections::VecDeque, sync::Arc}; use async_trait::async_trait; use core::fmt::Debug; use op_alloy_genesis::RollupConfig; -use op_alloy_protocol::{BlockInfo, L2BlockInfo}; +use op_alloy_protocol::{ + Batch, BatchValidity, BatchWithInclusionBlock, BlockInfo, L2BlockInfo, SingleBatch, SpanBatch, +}; use tracing::{error, trace}; /// Provides [Batch]es for the [BatchStream] stage. @@ -220,11 +221,11 @@ where mod test { use super::*; use crate::{ - batch::{SingleBatch, SpanBatchElement}, test_utils::{CollectingLayer, TestBatchStreamProvider, TestL2ChainProvider, TraceStorage}, traits::ResetSignal, }; use alloc::vec; + use op_alloy_protocol::{SingleBatch, SpanBatchElement}; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; #[tokio::test] diff --git a/crates/derive/src/stages/batch/batch_validator.rs b/crates/derive/src/stages/batch/batch_validator.rs index 906a11ee4..11c59fb6f 100644 --- a/crates/derive/src/stages/batch/batch_validator.rs +++ b/crates/derive/src/stages/batch/batch_validator.rs @@ -2,7 +2,6 @@ use super::NextBatchProvider; use crate::{ - batch::{Batch, BatchValidity, SingleBatch}, errors::ResetError, pipeline::{OriginAdvancer, PipelineResult, Signal, SignalReceiver}, prelude::{OriginProvider, PipelineError, PipelineErrorKind}, @@ -12,7 +11,7 @@ use alloc::{boxed::Box, sync::Arc, vec::Vec}; use async_trait::async_trait; use core::fmt::Debug; use op_alloy_genesis::RollupConfig; -use op_alloy_protocol::{BlockInfo, L2BlockInfo}; +use op_alloy_protocol::{Batch, BatchValidity, BlockInfo, L2BlockInfo, SingleBatch}; use tracing::{debug, error, info, warn}; /// The [BatchValidator] stage is responsible for validating the [SingleBatch]es from @@ -312,7 +311,6 @@ where #[cfg(test)] mod test { use crate::{ - batch::{Batch, SingleBatch, SpanBatch}, errors::{PipelineError, PipelineErrorKind, PipelineResult, ResetError}, stages::{BatchValidator, NextBatchProvider}, test_utils::{CollectingLayer, TestNextBatchProvider, TraceStorage}, @@ -322,7 +320,7 @@ mod test { use alloy_eips::{BlockNumHash, NumHash}; use alloy_primitives::B256; use op_alloy_genesis::RollupConfig; - use op_alloy_protocol::{BlockInfo, L2BlockInfo}; + use op_alloy_protocol::{Batch, BlockInfo, L2BlockInfo, SingleBatch, SpanBatch}; use tracing::Level; use tracing_subscriber::layer::SubscriberExt; diff --git a/crates/derive/src/stages/batch/mod.rs b/crates/derive/src/stages/batch/mod.rs index 6bf52b4d8..1cdae4901 100644 --- a/crates/derive/src/stages/batch/mod.rs +++ b/crates/derive/src/stages/batch/mod.rs @@ -5,16 +5,16 @@ //! for streaming [SingleBatch]es from [SpanBatch]es, while the [BatchProvider] stage is responsible //! for ordering and validating the [Batch]es for the [AttributesQueue] stage. //! -//! [Batch]: crate::batch::Batch -//! [SingleBatch]: crate::batch::SingleBatch -//! [SpanBatch]: crate::batch::SpanBatch +//! [Batch]: op_alloy_protocol::Batch +//! [SingleBatch]: op_alloy_protocol::SingleBatch +//! [SpanBatch]: op_alloy_protocol::SpanBatch //! [ChannelReader]: crate::stages::channel::ChannelReader //! [AttributesQueue]: crate::stages::attributes_queue::AttributesQueue -use crate::{batch::Batch, pipeline::PipelineResult}; +use crate::pipeline::PipelineResult; use alloc::boxed::Box; use async_trait::async_trait; -use op_alloy_protocol::{BlockInfo, L2BlockInfo}; +use op_alloy_protocol::{Batch, BlockInfo, L2BlockInfo}; mod batch_stream; pub use batch_stream::{BatchStream, BatchStreamProvider}; @@ -47,8 +47,8 @@ pub trait NextBatchProvider { /// Returns the number of [SingleBatch]es that are currently buffered in the [BatchStream] /// from a [SpanBatch]. /// - /// [SpanBatch]: crate::batch::SpanBatch - /// [SingleBatch]: crate::batch::SingleBatch + /// [SpanBatch]: op_alloy_protocol::SpanBatch + /// [SingleBatch]: op_alloy_protocol::SingleBatch fn span_buffer_size(&self) -> usize; /// Allows the stage to flush the buffer in the [crate::stages::BatchStream] diff --git a/crates/derive/src/stages/channel/channel_reader.rs b/crates/derive/src/stages/channel/channel_reader.rs index f8a722138..3f4f0a6f1 100644 --- a/crates/derive/src/stages/channel/channel_reader.rs +++ b/crates/derive/src/stages/channel/channel_reader.rs @@ -1,7 +1,6 @@ //! This module contains the `ChannelReader` struct. use crate::{ - batch::Batch, errors::{PipelineError, PipelineResult}, stages::{decompress_brotli, BatchStreamProvider}, traits::{OriginAdvancer, OriginProvider, Signal, SignalReceiver}, @@ -15,7 +14,7 @@ use miniz_oxide::inflate::decompress_to_vec_zlib; use op_alloy_genesis::{ RollupConfig, MAX_RLP_BYTES_PER_CHANNEL_BEDROCK, MAX_RLP_BYTES_PER_CHANNEL_FJORD, }; -use op_alloy_protocol::BlockInfo; +use op_alloy_protocol::{Batch, BlockInfo}; use tracing::{debug, error, warn}; /// ZLIB Deflate Compression Method. diff --git a/crates/derive/src/stages/channel/mod.rs b/crates/derive/src/stages/channel/mod.rs index da578de32..97519695b 100644 --- a/crates/derive/src/stages/channel/mod.rs +++ b/crates/derive/src/stages/channel/mod.rs @@ -7,7 +7,7 @@ //! //! [Frame]: op_alloy_protocol::Frame //! [Channel]: op_alloy_protocol::Channel -//! [Batch]: crate::batch::Batch +//! [Batch]: op_alloy_protocol::Batch //! [FrameQueue]: crate::stages::FrameQueue //! [BatchQueue]: crate::stages::BatchQueue diff --git a/crates/derive/src/test_utils/attributes_queue.rs b/crates/derive/src/test_utils/attributes_queue.rs index c015ac97a..6d2ddb7cf 100644 --- a/crates/derive/src/test_utils/attributes_queue.rs +++ b/crates/derive/src/test_utils/attributes_queue.rs @@ -1,7 +1,6 @@ //! Testing utilities for the attributes queue stage. use crate::{ - batch::SingleBatch, errors::{BuilderError, PipelineError, PipelineErrorKind, PipelineResult}, traits::{ AttributesBuilder, AttributesProvider, OriginAdvancer, OriginProvider, Signal, @@ -11,7 +10,7 @@ use crate::{ use alloc::{boxed::Box, string::ToString, vec::Vec}; use alloy_eips::BlockNumHash; use async_trait::async_trait; -use op_alloy_protocol::{BlockInfo, L2BlockInfo}; +use op_alloy_protocol::{BlockInfo, L2BlockInfo, SingleBatch}; use op_alloy_rpc_types_engine::OpPayloadAttributes; /// An error returned by the [`TestAttributesBuilder`]. diff --git a/crates/derive/src/test_utils/batch_provider.rs b/crates/derive/src/test_utils/batch_provider.rs index 8cbdf3083..c862fdb99 100644 --- a/crates/derive/src/test_utils/batch_provider.rs +++ b/crates/derive/src/test_utils/batch_provider.rs @@ -1,14 +1,13 @@ //! A mock implementation of the [`BatchQueue`] stage for testing. use crate::{ - batch::Batch, errors::{PipelineError, PipelineResult}, stages::NextBatchProvider, traits::{OriginAdvancer, OriginProvider, Signal, SignalReceiver}, }; use alloc::{boxed::Box, vec::Vec}; use async_trait::async_trait; -use op_alloy_protocol::{BlockInfo, L2BlockInfo}; +use op_alloy_protocol::{Batch, BlockInfo, L2BlockInfo}; /// A mock provider for the [BatchQueue] stage. #[derive(Debug, Default)] diff --git a/crates/derive/src/test_utils/batch_stream.rs b/crates/derive/src/test_utils/batch_stream.rs index 4e49b8900..91ad1a445 100644 --- a/crates/derive/src/test_utils/batch_stream.rs +++ b/crates/derive/src/test_utils/batch_stream.rs @@ -3,14 +3,13 @@ //! [`BatchStream`]: crate::stages::BatchStream use crate::{ - batch::Batch, errors::{PipelineError, PipelineResult}, stages::BatchStreamProvider, traits::{OriginAdvancer, OriginProvider, Signal, SignalReceiver}, }; use alloc::{boxed::Box, vec::Vec}; use async_trait::async_trait; -use op_alloy_protocol::BlockInfo; +use op_alloy_protocol::{Batch, BlockInfo}; /// A mock provider for the [`BatchStream`] stage. /// diff --git a/crates/derive/src/test_utils/chain_providers.rs b/crates/derive/src/test_utils/chain_providers.rs index e81c35b57..899873598 100644 --- a/crates/derive/src/test_utils/chain_providers.rs +++ b/crates/derive/src/test_utils/chain_providers.rs @@ -7,7 +7,7 @@ use alloy_primitives::{map::HashMap, B256}; use async_trait::async_trait; use op_alloy_consensus::OpBlock; use op_alloy_genesis::{RollupConfig, SystemConfig}; -use op_alloy_protocol::{BlockInfo, L2BlockInfo}; +use op_alloy_protocol::{BatchValidationProvider, BlockInfo, L2BlockInfo}; /// A mock chain provider for testing. #[derive(Debug, Clone, Default)] @@ -167,7 +167,7 @@ impl TestL2ChainProvider { } #[async_trait] -impl L2ChainProvider for TestL2ChainProvider { +impl BatchValidationProvider for TestL2ChainProvider { type Error = TestProviderError; async fn l2_block_info_by_number(&mut self, number: u64) -> Result { @@ -188,7 +188,10 @@ impl L2ChainProvider for TestL2ChainProvider { .cloned() .ok_or_else(|| TestProviderError::L2BlockNotFound) } +} +#[async_trait] +impl L2ChainProvider for TestL2ChainProvider { async fn system_config_by_number( &mut self, number: u64, diff --git a/crates/derive/src/test_utils/sys_config_fetcher.rs b/crates/derive/src/test_utils/sys_config_fetcher.rs index 2a37085ab..d71768c2d 100644 --- a/crates/derive/src/test_utils/sys_config_fetcher.rs +++ b/crates/derive/src/test_utils/sys_config_fetcher.rs @@ -6,7 +6,7 @@ use alloy_primitives::map::HashMap; use async_trait::async_trait; use op_alloy_consensus::OpBlock; use op_alloy_genesis::{RollupConfig, SystemConfig}; -use op_alloy_protocol::L2BlockInfo; +use op_alloy_protocol::{BatchValidationProvider, L2BlockInfo}; /// A mock implementation of the `SystemConfigL2Fetcher` for testing. #[derive(Debug, Default)] @@ -38,9 +38,20 @@ pub enum TestSystemConfigL2FetcherError { impl core::error::Error for TestSystemConfigL2FetcherError {} #[async_trait] -impl L2ChainProvider for TestSystemConfigL2Fetcher { +impl BatchValidationProvider for TestSystemConfigL2Fetcher { type Error = TestSystemConfigL2FetcherError; + async fn block_by_number(&mut self, _: u64) -> Result { + unimplemented!() + } + + async fn l2_block_info_by_number(&mut self, _: u64) -> Result { + unimplemented!() + } +} + +#[async_trait] +impl L2ChainProvider for TestSystemConfigL2Fetcher { async fn system_config_by_number( &mut self, number: u64, @@ -51,12 +62,4 @@ impl L2ChainProvider for TestSystemConfigL2Fetcher { .cloned() .ok_or_else(|| TestSystemConfigL2FetcherError::NotFound(number)) } - - async fn l2_block_info_by_number(&mut self, _: u64) -> Result { - unimplemented!() - } - - async fn block_by_number(&mut self, _: u64) -> Result { - unimplemented!() - } } diff --git a/crates/derive/src/traits/attributes.rs b/crates/derive/src/traits/attributes.rs index d920295e8..070ac724e 100644 --- a/crates/derive/src/traits/attributes.rs +++ b/crates/derive/src/traits/attributes.rs @@ -1,10 +1,10 @@ //! Contains traits for working with payload attributes and their providers. -use crate::{batch::SingleBatch, errors::PipelineResult}; +use crate::errors::PipelineResult; use alloc::boxed::Box; use alloy_eips::BlockNumHash; use async_trait::async_trait; -use op_alloy_protocol::L2BlockInfo; +use op_alloy_protocol::{L2BlockInfo, SingleBatch}; use op_alloy_rpc_types_engine::{OpAttributesWithParent, OpPayloadAttributes}; /// [AttributesProvider] is a trait abstraction that generalizes the [BatchQueue] stage. diff --git a/crates/derive/src/traits/providers.rs b/crates/derive/src/traits/providers.rs index b28c542b3..6d845ba69 100644 --- a/crates/derive/src/traits/providers.rs +++ b/crates/derive/src/traits/providers.rs @@ -5,9 +5,8 @@ use alloy_consensus::{Header, Receipt, TxEnvelope}; use alloy_primitives::B256; use async_trait::async_trait; use core::fmt::Display; -use op_alloy_consensus::OpBlock; use op_alloy_genesis::{RollupConfig, SystemConfig}; -use op_alloy_protocol::{BlockInfo, L2BlockInfo}; +use op_alloy_protocol::{BatchValidationProvider, BlockInfo}; /// Describes the functionality of a data source that can provide information from the blockchain. #[async_trait] @@ -35,18 +34,7 @@ pub trait ChainProvider { /// Describes the functionality of a data source that fetches safe blocks. #[async_trait] -pub trait L2ChainProvider { - /// The error type for the [L2ChainProvider]. - type Error: Display + ToString; - - /// Returns the L2 block info given a block number. - /// Errors if the block does not exist. - async fn l2_block_info_by_number(&mut self, number: u64) -> Result; - - /// Returns the block for a given number. - /// Errors if no block is available for the given block number. - async fn block_by_number(&mut self, number: u64) -> Result; - +pub trait L2ChainProvider: BatchValidationProvider { /// Returns the [SystemConfig] by L2 number. async fn system_config_by_number( &mut self, diff --git a/crates/derive/testdata/raw_batch.hex b/crates/derive/testdata/raw_batch.hex deleted file mode 100644 index 311a7b99981d2e3edfca3ee9e98693e25810f904..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23886 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 L`qSZsTvhddvFU9}