diff --git a/crates/derive/src/online/alloy_providers.rs b/crates/derive/src/online/alloy_providers.rs index 75e11b7b..ce7b11dd 100644 --- a/crates/derive/src/online/alloy_providers.rs +++ b/crates/derive/src/online/alloy_providers.rs @@ -3,7 +3,10 @@ use crate::{ traits::{ChainProvider, L2ChainProvider}, - types::{Block, BlockInfo, L2BlockInfo, L2ExecutionPayloadEnvelope, OpBlock, RollupConfig}, + types::{ + Block, BlockInfo, L2BlockInfo, L2ExecutionPayloadEnvelope, OpBlock, RollupConfig, + SystemConfig, + }, }; use alloc::{boxed::Box, sync::Arc, vec::Vec}; use alloy_consensus::{Header, Receipt, ReceiptWithBloom, TxEnvelope, TxType}; @@ -165,6 +168,8 @@ pub struct AlloyL2ChainProvider>> { payload_by_number_cache: LruCache, /// `l2_block_info_by_number` LRU cache. l2_block_info_by_number_cache: LruCache, + /// `system_config_by_l2_hash` LRU cache. + system_config_by_number_cache: LruCache, } impl>> AlloyL2ChainProvider { @@ -175,6 +180,7 @@ impl>> AlloyL2ChainProvider { rollup_config, payload_by_number_cache: LruCache::new(NonZeroUsize::new(CACHE_SIZE).unwrap()), l2_block_info_by_number_cache: LruCache::new(NonZeroUsize::new(CACHE_SIZE).unwrap()), + system_config_by_number_cache: LruCache::new(NonZeroUsize::new(CACHE_SIZE).unwrap()), } } } @@ -209,4 +215,17 @@ impl>> L2ChainProvider for AlloyL2ChainProvide self.payload_by_number_cache.put(number, payload_envelope.clone()); Ok(payload_envelope) } + + async fn system_config_by_number( + &mut self, + number: u64, + rollup_config: Arc, + ) -> Result { + if let Some(system_config) = self.system_config_by_number_cache.get(&number) { + return Ok(*system_config); + } + + let envelope = self.payload_by_number(number).await?; + envelope.to_system_config(&rollup_config) + } } diff --git a/crates/derive/src/sources/factory.rs b/crates/derive/src/sources/factory.rs index 231d42c2..1d39d4b2 100644 --- a/crates/derive/src/sources/factory.rs +++ b/crates/derive/src/sources/factory.rs @@ -41,7 +41,7 @@ where blob_provider: blobs, ecotone_timestamp: cfg.ecotone_time, plasma_enabled: cfg.is_plasma_enabled(), - signer: cfg.l1_signer_address(), + signer: cfg.genesis.system_config.batcher_addr, } } } diff --git a/crates/derive/src/stages/attributes_queue.rs b/crates/derive/src/stages/attributes_queue.rs index 29eb2d1e..ac83943e 100644 --- a/crates/derive/src/stages/attributes_queue.rs +++ b/crates/derive/src/stages/attributes_queue.rs @@ -16,7 +16,7 @@ mod deposits; pub(crate) use deposits::derive_deposits; mod builder; -pub use builder::{AttributesBuilder, StatefulAttributesBuilder, SystemConfigL2Fetcher}; +pub use builder::{AttributesBuilder, StatefulAttributesBuilder}; /// [AttributesProvider] is a trait abstraction that generalizes the [BatchQueue] stage. /// diff --git a/crates/derive/src/stages/attributes_queue/builder.rs b/crates/derive/src/stages/attributes_queue/builder.rs index ee0d9ac6..4282671d 100644 --- a/crates/derive/src/stages/attributes_queue/builder.rs +++ b/crates/derive/src/stages/attributes_queue/builder.rs @@ -3,14 +3,13 @@ use super::derive_deposits; use crate::{ params::SEQUENCER_FEE_VAULT_ADDRESS, - traits::ChainProvider, + traits::{ChainProvider, L2ChainProvider}, types::{ BlockID, BuilderError, EcotoneTransactionBuilder, L1BlockInfoTx, L2BlockInfo, - L2PayloadAttributes, RawTransaction, RollupConfig, SystemConfig, + L2PayloadAttributes, RawTransaction, RollupConfig, }, }; use alloc::{boxed::Box, fmt::Debug, sync::Arc, vec, vec::Vec}; -use alloy_primitives::B256; use alloy_rlp::Encodable; use async_trait::async_trait; @@ -32,43 +31,37 @@ pub trait AttributesBuilder { ) -> Result; } -/// The [SystemConfigL2Fetcher] fetches the system config by L2 hash. -pub trait SystemConfigL2Fetcher { - /// Fetch the system config by L2 hash. - fn system_config_by_l2_hash(&self, hash: B256) -> anyhow::Result; -} - /// A stateful implementation of the [AttributesBuilder]. #[derive(Debug, Default)] -pub struct StatefulAttributesBuilder +pub struct StatefulAttributesBuilder where - S: SystemConfigL2Fetcher + Debug, - R: ChainProvider + Debug, + L1P: ChainProvider + Debug, + L2P: L2ChainProvider + Debug, { /// The rollup config. rollup_cfg: Arc, /// The system config fetcher. - config_fetcher: S, + config_fetcher: L2P, /// The L1 receipts fetcher. - receipts_fetcher: R, + receipts_fetcher: L1P, } -impl StatefulAttributesBuilder +impl StatefulAttributesBuilder where - S: SystemConfigL2Fetcher + Debug, - R: ChainProvider + Debug, + L1P: ChainProvider + Debug, + L2P: L2ChainProvider + Debug, { /// Create a new [StatefulAttributesBuilder] with the given epoch. - pub fn new(rcfg: Arc, cfg: S, receipts: R) -> Self { - Self { rollup_cfg: rcfg, config_fetcher: cfg, receipts_fetcher: receipts } + pub fn new(rcfg: Arc, sys_cfg_fetcher: L2P, receipts: L1P) -> Self { + Self { rollup_cfg: rcfg, config_fetcher: sys_cfg_fetcher, receipts_fetcher: receipts } } } #[async_trait] -impl AttributesBuilder for StatefulAttributesBuilder +impl AttributesBuilder for StatefulAttributesBuilder where - S: SystemConfigL2Fetcher + Send + Debug, - R: ChainProvider + Send + Debug, + L1P: ChainProvider + Debug + Send, + L2P: L2ChainProvider + Debug + Send, { async fn prepare_payload_attributes( &mut self, @@ -77,8 +70,10 @@ where ) -> Result { let l1_header; let deposit_transactions: Vec; - let mut sys_config = - self.config_fetcher.system_config_by_l2_hash(l2_parent.block_info.hash)?; + let mut sys_config = self + .config_fetcher + .system_config_by_number(l2_parent.block_info.number, self.rollup_cfg.clone()) + .await?; // If the L1 origin changed in this block, then we are in the first block of the epoch. // In this case we need to fetch all transaction receipts from the L1 origin block so @@ -177,27 +172,28 @@ where mod tests { use super::*; use crate::{ - stages::test_utils::MockSystemConfigL2Fetcher, traits::test_utils::TestChainProvider, - types::BlockInfo, + stages::test_utils::MockSystemConfigL2Fetcher, + traits::test_utils::TestChainProvider, + types::{BlockInfo, SystemConfig}, }; use alloy_consensus::Header; - use alloy_primitives::b256; + use alloy_primitives::B256; #[tokio::test] async fn test_prepare_payload_block_mismatch_epoch_reset() { let cfg = Arc::new(RollupConfig::default()); - let l2_hash = b256!("0000000000000000000000000000000000000000000000000000000000000002"); + let l2_number = 1; let mut fetcher = MockSystemConfigL2Fetcher::default(); - fetcher.insert(l2_hash, SystemConfig::default()); + fetcher.insert(l2_number, SystemConfig::default()); let mut provider = TestChainProvider::default(); let header = Header::default(); let hash = header.hash_slow(); provider.insert_header(hash, header); let mut builder = StatefulAttributesBuilder::new(cfg, fetcher, provider); - let epoch = BlockID { hash, number: 1 }; + let epoch = BlockID { hash, number: l2_number }; let l2_parent = L2BlockInfo { - block_info: BlockInfo { hash: l2_hash, number: 1, ..Default::default() }, - l1_origin: BlockID { hash: l2_hash, number: 2 }, + block_info: BlockInfo { hash: B256::ZERO, number: l2_number, ..Default::default() }, + l1_origin: BlockID { hash: B256::left_padding_from(&[0xFF]), number: 2 }, seq_num: 0, }; // This should error because the l2 parent's l1_origin.hash should equal the epoch header @@ -211,18 +207,18 @@ mod tests { #[tokio::test] async fn test_prepare_payload_block_mismatch() { let cfg = Arc::new(RollupConfig::default()); - let l2_hash = b256!("0000000000000000000000000000000000000000000000000000000000000002"); + let l2_number = 1; let mut fetcher = MockSystemConfigL2Fetcher::default(); - fetcher.insert(l2_hash, SystemConfig::default()); + fetcher.insert(l2_number, SystemConfig::default()); let mut provider = TestChainProvider::default(); let header = Header::default(); let hash = header.hash_slow(); provider.insert_header(hash, header); let mut builder = StatefulAttributesBuilder::new(cfg, fetcher, provider); - let epoch = BlockID { hash, number: 1 }; + let epoch = BlockID { hash, number: l2_number }; let l2_parent = L2BlockInfo { - block_info: BlockInfo { hash: l2_hash, number: 1, ..Default::default() }, - l1_origin: BlockID { hash: l2_hash, number: 1 }, + block_info: BlockInfo { hash: B256::ZERO, number: l2_number, ..Default::default() }, + l1_origin: BlockID { hash: B256::ZERO, number: l2_number }, seq_num: 0, }; // This should error because the l2 parent's l1_origin.hash should equal the epoch hash @@ -237,18 +233,18 @@ mod tests { let block_time = 10; let timestamp = 100; let cfg = Arc::new(RollupConfig { block_time, ..Default::default() }); - let l2_hash = b256!("0000000000000000000000000000000000000000000000000000000000000002"); + let l2_number = 1; let mut fetcher = MockSystemConfigL2Fetcher::default(); - fetcher.insert(l2_hash, SystemConfig::default()); + fetcher.insert(l2_number, SystemConfig::default()); let mut provider = TestChainProvider::default(); let header = Header { timestamp, ..Default::default() }; let hash = header.hash_slow(); provider.insert_header(hash, header); let mut builder = StatefulAttributesBuilder::new(cfg, fetcher, provider); - let epoch = BlockID { hash, number: 1 }; + let epoch = BlockID { hash, number: l2_number }; let l2_parent = L2BlockInfo { - block_info: BlockInfo { hash: l2_hash, number: 1, ..Default::default() }, - l1_origin: BlockID { hash, number: 1 }, + block_info: BlockInfo { hash: B256::ZERO, number: l2_number, ..Default::default() }, + l1_origin: BlockID { hash, number: l2_number }, seq_num: 0, }; let next_l2_time = l2_parent.block_info.timestamp + block_time; @@ -268,19 +264,24 @@ mod tests { let block_time = 10; let timestamp = 100; let cfg = Arc::new(RollupConfig { block_time, ..Default::default() }); - let l2_hash = b256!("0000000000000000000000000000000000000000000000000000000000000002"); + let l2_number = 1; let mut fetcher = MockSystemConfigL2Fetcher::default(); - fetcher.insert(l2_hash, SystemConfig::default()); + fetcher.insert(l2_number, SystemConfig::default()); let mut provider = TestChainProvider::default(); let header = Header { timestamp, ..Default::default() }; let prev_randao = header.mix_hash; let hash = header.hash_slow(); provider.insert_header(hash, header); let mut builder = StatefulAttributesBuilder::new(cfg, fetcher, provider); - let epoch = BlockID { hash, number: 1 }; + let epoch = BlockID { hash, number: l2_number }; let l2_parent = L2BlockInfo { - block_info: BlockInfo { hash: l2_hash, number: 1, timestamp, parent_hash: hash }, - l1_origin: BlockID { hash, number: 1 }, + block_info: BlockInfo { + hash: B256::ZERO, + number: l2_number, + timestamp, + parent_hash: hash, + }, + l1_origin: BlockID { hash, number: l2_number }, seq_num: 0, }; let next_l2_time = l2_parent.block_info.timestamp + block_time; @@ -306,19 +307,24 @@ mod tests { let block_time = 10; let timestamp = 100; let cfg = Arc::new(RollupConfig { block_time, canyon_time: Some(0), ..Default::default() }); - let l2_hash = b256!("0000000000000000000000000000000000000000000000000000000000000002"); + let l2_number = 1; let mut fetcher = MockSystemConfigL2Fetcher::default(); - fetcher.insert(l2_hash, SystemConfig::default()); + fetcher.insert(l2_number, SystemConfig::default()); let mut provider = TestChainProvider::default(); let header = Header { timestamp, ..Default::default() }; let prev_randao = header.mix_hash; let hash = header.hash_slow(); provider.insert_header(hash, header); let mut builder = StatefulAttributesBuilder::new(cfg, fetcher, provider); - let epoch = BlockID { hash, number: 1 }; + let epoch = BlockID { hash, number: l2_number }; let l2_parent = L2BlockInfo { - block_info: BlockInfo { hash: l2_hash, number: 1, timestamp, parent_hash: hash }, - l1_origin: BlockID { hash, number: 1 }, + block_info: BlockInfo { + hash: B256::ZERO, + number: l2_number, + timestamp, + parent_hash: hash, + }, + l1_origin: BlockID { hash, number: l2_number }, seq_num: 0, }; let next_l2_time = l2_parent.block_info.timestamp + block_time; @@ -345,9 +351,9 @@ mod tests { let timestamp = 100; let cfg = Arc::new(RollupConfig { block_time, ecotone_time: Some(0), ..Default::default() }); - let l2_hash = b256!("0000000000000000000000000000000000000000000000000000000000000002"); + let l2_number = 1; let mut fetcher = MockSystemConfigL2Fetcher::default(); - fetcher.insert(l2_hash, SystemConfig::default()); + fetcher.insert(l2_number, SystemConfig::default()); let mut provider = TestChainProvider::default(); let header = Header { timestamp, ..Default::default() }; let parent_beacon_block_root = Some(header.parent_beacon_block_root.unwrap_or_default()); @@ -355,10 +361,15 @@ mod tests { let hash = header.hash_slow(); provider.insert_header(hash, header); let mut builder = StatefulAttributesBuilder::new(cfg, fetcher, provider); - let epoch = BlockID { hash, number: 1 }; + let epoch = BlockID { hash, number: l2_number }; let l2_parent = L2BlockInfo { - block_info: BlockInfo { hash: l2_hash, number: 1, timestamp, parent_hash: hash }, - l1_origin: BlockID { hash, number: 1 }, + block_info: BlockInfo { + hash: B256::ZERO, + number: l2_number, + timestamp, + parent_hash: hash, + }, + l1_origin: BlockID { hash, number: l2_number }, seq_num: 0, }; let next_l2_time = l2_parent.block_info.timestamp + block_time; diff --git a/crates/derive/src/stages/mod.rs b/crates/derive/src/stages/mod.rs index 5b637cec..2034cf5d 100644 --- a/crates/derive/src/stages/mod.rs +++ b/crates/derive/src/stages/mod.rs @@ -34,7 +34,7 @@ pub use batch_queue::{BatchQueue, BatchQueueProvider}; mod attributes_queue; pub use attributes_queue::{ AttributesBuilder, AttributesProvider, AttributesQueue, NextAttributes, - StatefulAttributesBuilder, SystemConfigL2Fetcher, + StatefulAttributesBuilder, }; #[cfg(test)] diff --git a/crates/derive/src/stages/test_utils/sys_config_fetcher.rs b/crates/derive/src/stages/test_utils/sys_config_fetcher.rs index 2a34ff69..53489faa 100644 --- a/crates/derive/src/stages/test_utils/sys_config_fetcher.rs +++ b/crates/derive/src/stages/test_utils/sys_config_fetcher.rs @@ -1,20 +1,25 @@ //! Implements a mock [L2SystemConfigFetcher] for testing. -use crate::{stages::attributes_queue::SystemConfigL2Fetcher, types::SystemConfig}; -use alloy_primitives::B256; +use crate::{ + traits::L2ChainProvider, + types::{L2BlockInfo, L2ExecutionPayloadEnvelope, RollupConfig, SystemConfig}, +}; +use alloc::{boxed::Box, sync::Arc}; +use anyhow::Result; +use async_trait::async_trait; use hashbrown::HashMap; /// A mock implementation of the [`SystemConfigL2Fetcher`] for testing. #[derive(Debug, Default)] pub struct MockSystemConfigL2Fetcher { - /// A map from [B256] block hash to a [SystemConfig]. - pub system_configs: HashMap, + /// A map from [u64] block number to a [SystemConfig]. + pub system_configs: HashMap, } impl MockSystemConfigL2Fetcher { - /// Inserts a new system config into the mock fetcher with the given hash. - pub fn insert(&mut self, hash: B256, config: SystemConfig) { - self.system_configs.insert(hash, config); + /// Inserts a new system config into the mock fetcher with the given block number. + pub fn insert(&mut self, number: u64, config: SystemConfig) { + self.system_configs.insert(number, config); } /// Clears all system configs from the mock fetcher. @@ -23,11 +28,24 @@ impl MockSystemConfigL2Fetcher { } } -impl SystemConfigL2Fetcher for MockSystemConfigL2Fetcher { - fn system_config_by_l2_hash(&self, hash: B256) -> anyhow::Result { +#[async_trait] +impl L2ChainProvider for MockSystemConfigL2Fetcher { + async fn system_config_by_number( + &mut self, + number: u64, + _: Arc, + ) -> Result { self.system_configs - .get(&hash) + .get(&number) .cloned() - .ok_or_else(|| anyhow::anyhow!("system config not found")) + .ok_or_else(|| anyhow::anyhow!("system config not found: {number}")) + } + + async fn l2_block_info_by_number(&mut self, _: u64) -> Result { + unimplemented!() + } + + async fn payload_by_number(&mut self, _: u64) -> Result { + unimplemented!() } } diff --git a/crates/derive/src/traits/data_sources.rs b/crates/derive/src/traits/data_sources.rs index 6c61b0b2..1061b84e 100644 --- a/crates/derive/src/traits/data_sources.rs +++ b/crates/derive/src/traits/data_sources.rs @@ -2,9 +2,10 @@ //! pipeline's stages. use crate::types::{ - Blob, BlockInfo, IndexedBlobHash, L2BlockInfo, L2ExecutionPayloadEnvelope, StageResult, + Blob, BlockInfo, IndexedBlobHash, L2BlockInfo, L2ExecutionPayloadEnvelope, RollupConfig, + StageResult, SystemConfig, }; -use alloc::{boxed::Box, fmt::Debug, vec::Vec}; +use alloc::{boxed::Box, fmt::Debug, sync::Arc, vec::Vec}; use alloy_consensus::{Header, Receipt, TxEnvelope}; use alloy_primitives::{Address, Bytes, B256}; use anyhow::Result; @@ -41,6 +42,13 @@ pub trait L2ChainProvider { /// Returns an execution payload for a given number. /// Errors if the execution payload does not exist. async fn payload_by_number(&mut self, number: u64) -> Result; + + /// Returns the [SystemConfig] by L2 number. + async fn system_config_by_number( + &mut self, + number: u64, + rollup_config: Arc, + ) -> Result; } /// The BlobProvider trait specifies the functionality of a data source that can provide blobs. diff --git a/crates/derive/src/traits/test_utils/data_sources.rs b/crates/derive/src/traits/test_utils/data_sources.rs index ae122c56..1fb473f3 100644 --- a/crates/derive/src/traits/test_utils/data_sources.rs +++ b/crates/derive/src/traits/test_utils/data_sources.rs @@ -2,13 +2,14 @@ use crate::{ traits::{ChainProvider, L2ChainProvider}, - types::{BlockInfo, L2BlockInfo, L2ExecutionPayloadEnvelope}, + types::{BlockInfo, L2BlockInfo, L2ExecutionPayloadEnvelope, RollupConfig, SystemConfig}, }; -use alloc::{boxed::Box, vec::Vec}; +use alloc::{boxed::Box, sync::Arc, vec::Vec}; use alloy_consensus::{Header, Receipt, TxEnvelope}; use alloy_primitives::B256; use anyhow::Result; use async_trait::async_trait; +use hashbrown::HashMap; /// A mock block fetcher. #[derive(Debug, Default)] @@ -17,12 +18,18 @@ pub struct MockBlockFetcher { pub blocks: Vec, /// Payloads pub payloads: Vec, + /// System configs + pub system_configs: HashMap, } impl MockBlockFetcher { /// Creates a new [MockBlockFetcher] with the given origin and batches. - pub fn new(blocks: Vec, payloads: Vec) -> Self { - Self { blocks, payloads } + pub fn new( + blocks: Vec, + payloads: Vec, + system_configs: HashMap, + ) -> Self { + Self { blocks, payloads, system_configs } } } @@ -43,6 +50,17 @@ impl L2ChainProvider for MockBlockFetcher { .cloned() .ok_or_else(|| anyhow::anyhow!("Payload not found")) } + + async fn system_config_by_number( + &mut self, + number: u64, + _: Arc, + ) -> Result { + self.system_configs + .get(&number) + .ok_or_else(|| anyhow::anyhow!("System config not found")) + .cloned() + } } /// A mock chain provider for testing. diff --git a/crates/derive/src/types/batch/span_batch/batch.rs b/crates/derive/src/types/batch/span_batch/batch.rs index 8ac80bf4..528ded28 100644 --- a/crates/derive/src/types/batch/span_batch/batch.rs +++ b/crates/derive/src/types/batch/span_batch/batch.rs @@ -158,7 +158,7 @@ impl SpanBatch { 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, current_epoch: {}", - epoch.id(), + epoch.id() ); return BatchValidity::Drop; } @@ -745,7 +745,7 @@ mod tests { block_info: BlockInfo { number: 40, ..Default::default() }, ..Default::default() }; - let mut fetcher = MockBlockFetcher { blocks: vec![l2_block], payloads: vec![] }; + let mut fetcher = MockBlockFetcher { 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 { @@ -784,7 +784,7 @@ mod tests { block_info: BlockInfo { number: 40, ..Default::default() }, ..Default::default() }; - let mut fetcher = MockBlockFetcher { blocks: vec![l2_block], payloads: vec![] }; + let mut fetcher = MockBlockFetcher { 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 { @@ -827,7 +827,7 @@ mod tests { block_info: BlockInfo { number: 40, ..Default::default() }, ..Default::default() }; - let mut fetcher = MockBlockFetcher { blocks: vec![l2_block], payloads: vec![] }; + let mut fetcher = MockBlockFetcher { 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 { @@ -877,7 +877,7 @@ mod tests { block_info: BlockInfo { number: 40, ..Default::default() }, ..Default::default() }; - let mut fetcher = MockBlockFetcher { blocks: vec![l2_block], payloads: vec![] }; + let mut fetcher = MockBlockFetcher { 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 { @@ -926,7 +926,7 @@ mod tests { block_info: BlockInfo { number: 40, ..Default::default() }, ..Default::default() }; - let mut fetcher = MockBlockFetcher { blocks: vec![l2_block], payloads: vec![] }; + let mut fetcher = MockBlockFetcher { 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 { @@ -972,7 +972,7 @@ mod tests { block_info: BlockInfo { number: 40, ..Default::default() }, ..Default::default() }; - let mut fetcher = MockBlockFetcher { blocks: vec![l2_block], payloads: vec![] }; + let mut fetcher = MockBlockFetcher { 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 { @@ -1029,7 +1029,7 @@ mod tests { block_info: BlockInfo { number: 40, ..Default::default() }, ..Default::default() }; - let mut fetcher = MockBlockFetcher { blocks: vec![l2_block], payloads: vec![] }; + let mut fetcher = MockBlockFetcher { 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() }; @@ -1079,7 +1079,7 @@ mod tests { block_info: BlockInfo { number: 40, ..Default::default() }, ..Default::default() }; - let mut fetcher = MockBlockFetcher { blocks: vec![l2_block], payloads: vec![] }; + let mut fetcher = MockBlockFetcher { 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![] }; @@ -1132,7 +1132,7 @@ mod tests { block_info: BlockInfo { number: 40, ..Default::default() }, ..Default::default() }; - let mut fetcher = MockBlockFetcher { blocks: vec![l2_block], payloads: vec![] }; + let mut fetcher = MockBlockFetcher { blocks: vec![l2_block], ..Default::default() }; let first = SpanBatchElement { epoch_num: 10, timestamp: 20, @@ -1195,7 +1195,7 @@ mod tests { block_info: BlockInfo { number: 40, ..Default::default() }, ..Default::default() }; - let mut fetcher = MockBlockFetcher { blocks: vec![l2_block], payloads: vec![] }; + let mut fetcher = MockBlockFetcher { blocks: vec![l2_block], ..Default::default() }; let first = SpanBatchElement { epoch_num: 10, timestamp: 20, @@ -1254,7 +1254,7 @@ mod tests { block_info: BlockInfo { number: 40, ..Default::default() }, ..Default::default() }; - let mut fetcher = MockBlockFetcher { blocks: vec![l2_block], payloads: vec![] }; + let mut fetcher = MockBlockFetcher { blocks: vec![l2_block], ..Default::default() }; let filler_bytes = RawTransaction(Bytes::copy_from_slice(&[OpTxType::Eip1559 as u8])); let first = SpanBatchElement { epoch_num: 10, @@ -1312,7 +1312,7 @@ mod tests { block_info: BlockInfo { number: 40, ..Default::default() }, ..Default::default() }; - let mut fetcher = MockBlockFetcher { blocks: vec![l2_block], payloads: vec![] }; + let mut fetcher = MockBlockFetcher { 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 { @@ -1366,7 +1366,11 @@ mod tests { parent_beacon_block_root: None, execution_payload: L2ExecutionPayload { block_number: 41, ..Default::default() }, }; - let mut fetcher = MockBlockFetcher { blocks: vec![l2_block], payloads: vec![payload] }; + let mut fetcher = MockBlockFetcher { + blocks: vec![l2_block], + payloads: vec![payload], + ..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 { @@ -1430,7 +1434,11 @@ mod tests { ..Default::default() }, }; - let mut fetcher = MockBlockFetcher { blocks: vec![l2_block], payloads: vec![payload] }; + let mut fetcher = MockBlockFetcher { + blocks: vec![l2_block], + payloads: vec![payload], + ..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 { @@ -1491,7 +1499,11 @@ mod tests { ..Default::default() }, }; - let mut fetcher = MockBlockFetcher { blocks: vec![l2_block], payloads: vec![payload] }; + let mut fetcher = MockBlockFetcher { + blocks: vec![l2_block], + payloads: vec![payload], + ..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 { diff --git a/crates/derive/src/types/batch/span_batch/bits.rs b/crates/derive/src/types/batch/span_batch/bits.rs index 7a986afa..32cf8fe3 100644 --- a/crates/derive/src/types/batch/span_batch/bits.rs +++ b/crates/derive/src/types/batch/span_batch/bits.rs @@ -4,6 +4,7 @@ use crate::types::{SpanBatchError, MAX_SPAN_BATCH_SIZE}; use alloc::{vec, vec::Vec}; use alloy_rlp::Buf; use anyhow::Result; +use core::cmp::Ordering; /// Type for span batch bits. #[derive(Debug, Default, Clone, PartialEq, Eq)] @@ -47,12 +48,11 @@ impl SpanBatchBits { b.advance(buffer_len); v }; - let sb_bits = SpanBatchBits(bits.to_vec()); + let sb_bits = SpanBatchBits(bits); - // TODO(clabby): Why doesn't this check work? - // if sb_bits.bit_len() > bit_length { - // return Err(SpanBatchError::BitfieldTooLong); - // } + if sb_bits.bit_len() > bit_length { + return Err(SpanBatchError::BitfieldTooLong); + } Ok(sb_bits) } @@ -65,10 +65,9 @@ impl SpanBatchBits { bit_length: usize, bits: &SpanBatchBits, ) -> Result<(), SpanBatchError> { - // TODO(clabby): Why doesn't this check work? - // if bits.bit_len() > bit_length { - // return Err(SpanBatchError::BitfieldTooLong); - // } + 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. @@ -90,12 +89,12 @@ impl SpanBatchBits { // 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[byte_index]; + 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 << (8 - bit_index)) != 0 { 1 } else { 0 }) + Some(if byte & (1 << bit_index) != 0 { 1 } else { 0 }) } else { // Return None if the index is out of bounds None @@ -110,34 +109,58 @@ impl SpanBatchBits { // 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.0.resize(byte_index + 1, 0); + Self::resize_from_right(&mut self.0, byte_index + 1); } // Retrieve the specific byte to modify - let byte = &mut self.0[byte_index]; + let len = self.0.len(); + let byte = &mut self.0[len - byte_index - 1]; if value { // Set the bit to 1 - *byte |= 1 << (8 - bit_index); + *byte |= 1 << bit_index; } else { // Set the bit to 0 - *byte &= !(1 << (8 - bit_index)); + *byte &= !(1 << bit_index); } } /// Calculates the bit length of the [SpanBatchBits] bitfield. pub fn bit_len(&self) -> usize { - if let Some((top_word, rest)) = self.0.split_last() { - // Calculate bit length. Rust's leading_zeros counts zeros from the MSB, so subtract - // from total bits. - let significant_bits = 8 - top_word.leading_zeros() as usize; - - // Return total bits, taking into account the full words in `rest` and the significant - // bits in `top`. - rest.len() * 8 + significant_bits - } else { - // If the slice is empty, return 0. - 0 + // 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. */ } } } } @@ -156,6 +179,48 @@ mod test { 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] diff --git a/crates/derive/src/types/l1_block_info.rs b/crates/derive/src/types/l1_block_info.rs index 11f3d938..053f7117 100644 --- a/crates/derive/src/types/l1_block_info.rs +++ b/crates/derive/src/types/l1_block_info.rs @@ -226,8 +226,8 @@ impl L1BlockInfoTx { /// Encodes the [L1BlockInfoTx] object into Ethereum transaction calldata. pub fn encode_calldata(&self) -> Bytes { match self { - Self::Ecotone(ecotone_tx) => ecotone_tx.encode_calldata(), Self::Bedrock(bedrock_tx) => bedrock_tx.encode_calldata(), + Self::Ecotone(ecotone_tx) => ecotone_tx.encode_calldata(), } } @@ -243,11 +243,27 @@ impl L1BlockInfoTx { } } + /// Returns the L1 fee overhead for the info transaction. After ecotone, this value is ignored. + pub fn l1_fee_overhead(&self) -> U256 { + match self { + Self::Bedrock(L1BlockInfoBedrock { l1_fee_overhead, .. }) => *l1_fee_overhead, + Self::Ecotone(_) => U256::ZERO, + } + } + + /// Returns the batcher address for the info transaction + pub fn batcher_address(&self) -> Address { + match self { + Self::Bedrock(L1BlockInfoBedrock { batcher_address, .. }) => *batcher_address, + Self::Ecotone(L1BlockInfoEcotone { batcher_address, .. }) => *batcher_address, + } + } + /// Returns the sequence number for the info transaction pub fn sequence_number(&self) -> u64 { match self { - Self::Ecotone(L1BlockInfoEcotone { sequence_number, .. }) => *sequence_number, Self::Bedrock(L1BlockInfoBedrock { sequence_number, .. }) => *sequence_number, + Self::Ecotone(L1BlockInfoEcotone { sequence_number, .. }) => *sequence_number, } } } diff --git a/crates/derive/src/types/payload.rs b/crates/derive/src/types/payload.rs index 2803bd9c..ae1e10e8 100644 --- a/crates/derive/src/types/payload.rs +++ b/crates/derive/src/types/payload.rs @@ -1,7 +1,7 @@ //! Contains the execution payload type. use alloc::vec::Vec; -use alloy_primitives::{Address, Bloom, Bytes, B256}; +use alloy_primitives::{Address, Bloom, Bytes, B256, U256}; use anyhow::Result; use op_alloy_consensus::TxDeposit; @@ -13,7 +13,11 @@ pub const PAYLOAD_MEM_FIXED_COST: u64 = 1000; /// 24 bytes per tx overhead (size of slice header in memory). pub const PAYLOAD_TX_MEM_OVERHEAD: u64 = 24; -use super::{Block, BlockInfo, L1BlockInfoTx, L2BlockInfo, OpBlock, RollupConfig, Withdrawal}; +use crate::types::{L1BlockInfoBedrock, L1BlockInfoEcotone}; + +use super::{ + Block, BlockInfo, L1BlockInfoTx, L2BlockInfo, OpBlock, RollupConfig, SystemConfig, Withdrawal, +}; use alloy_rlp::{Decodable, Encodable}; #[cfg(feature = "serde")] @@ -153,6 +157,57 @@ impl L2ExecutionPayloadEnvelope { seq_num: sequence_number, }) } + + /// Converts the [L2ExecutionPayloadEnvelope] to a partial [SystemConfig]. + pub fn to_system_config(&self, rollup_config: &RollupConfig) -> Result { + let L2ExecutionPayloadEnvelope { execution_payload, .. } = self; + + if execution_payload.block_number == rollup_config.genesis.l2.number { + if execution_payload.block_hash != rollup_config.genesis.l2.hash { + anyhow::bail!("Invalid genesis hash"); + } + return Ok(rollup_config.genesis.system_config); + } + + if execution_payload.transactions.is_empty() { + anyhow::bail!( + "L2 block is missing L1 info deposit transaction, block hash: {}", + execution_payload.block_hash + ); + } + let tx = OpTxEnvelope::decode(&mut execution_payload.transactions[0].as_ref()) + .map_err(|e| anyhow::anyhow!(e))?; + + let OpTxEnvelope::Deposit(tx) = tx else { + anyhow::bail!("First payload transaction has unexpected type: {:?}", tx.tx_type()); + }; + + let l1_info = L1BlockInfoTx::decode_calldata(tx.input.as_ref())?; + let l1_fee_scalar = match l1_info { + L1BlockInfoTx::Bedrock(L1BlockInfoBedrock { l1_fee_scalar, .. }) => l1_fee_scalar, + L1BlockInfoTx::Ecotone(L1BlockInfoEcotone { + blob_base_fee, + blob_base_fee_scalar, + .. + }) => { + // Translate Ecotone values back into encoded scalar if needed. + // We do not know if it was derived from a v0 or v1 scalar, + // but v1 is fine, a 0 blob base fee has the same effect. + let mut buf = B256::ZERO; + buf[0] = 0x01; + buf[24..28].copy_from_slice(blob_base_fee_scalar.to_be_bytes().as_ref()); + buf[28..32].copy_from_slice(blob_base_fee.to_be_bytes().as_ref()); + buf.into() + } + }; + + Ok(SystemConfig { + batcher_addr: l1_info.batcher_address(), + l1_fee_overhead: l1_info.l1_fee_overhead(), + l1_fee_scalar, + gas_limit: U256::from(execution_payload.gas_limit), + }) + } } impl From for L2ExecutionPayloadEnvelope { diff --git a/crates/derive/src/types/rollup_config.rs b/crates/derive/src/types/rollup_config.rs index 751f4413..c27739f7 100644 --- a/crates/derive/src/types/rollup_config.rs +++ b/crates/derive/src/types/rollup_config.rs @@ -84,11 +84,6 @@ impl RollupConfig { self.regolith_time.map_or(false, |t| timestamp >= t) } - /// Returns the L1 Signer Address. - pub fn l1_signer_address(&self) -> Address { - self.genesis.system_config.unsafe_block_signer - } - /// Returns true if Canyon is active at the given timestamp. pub fn is_canyon_active(&self, timestamp: u64) -> bool { self.canyon_time.map_or(false, |t| timestamp >= t) diff --git a/crates/derive/src/types/system_config.rs b/crates/derive/src/types/system_config.rs index 7a3d8363..2f5391fe 100644 --- a/crates/derive/src/types/system_config.rs +++ b/crates/derive/src/types/system_config.rs @@ -22,8 +22,6 @@ pub struct SystemConfig { /// Fee scalar #[cfg_attr(feature = "serde", serde(rename = "scalar"))] pub l1_fee_scalar: U256, - /// Sequencer's signer for unsafe blocks - pub unsafe_block_signer: Address, } /// Represents type of update to the system config.