From a862a4b738b65b8e7cb94533762bc2411240ef65 Mon Sep 17 00:00:00 2001 From: refcell Date: Wed, 30 Oct 2024 16:16:56 -0400 Subject: [PATCH] chore(derive): Cleanup Exports (#757) * feat: clean exports * fix: lints * Update crates/derive/src/attributes/mod.rs Co-authored-by: clabby * fix --------- Co-authored-by: clabby --- crates/derive/src/attributes/mod.rs | 620 +---------------------- crates/derive/src/attributes/stateful.rs | 615 ++++++++++++++++++++++ crates/derive/src/lib.rs | 10 +- 3 files changed, 625 insertions(+), 620 deletions(-) create mode 100644 crates/derive/src/attributes/stateful.rs diff --git a/crates/derive/src/attributes/mod.rs b/crates/derive/src/attributes/mod.rs index 16d8d062e..5691685f9 100644 --- a/crates/derive/src/attributes/mod.rs +++ b/crates/derive/src/attributes/mod.rs @@ -1,616 +1,6 @@ -//! The [`AttributesBuilder`] and it's default implementation. +//! Module containing the [AttributesBuilder] trait implementations. +//! +//! [AttributesBuilder]: crate::traits::AttributesBuilder -use crate::{ - errors::{ - BuilderError, PipelineEncodingError, PipelineError, PipelineErrorKind, PipelineResult, - }, - traits::{AttributesBuilder, ChainProvider, L2ChainProvider}, -}; -use alloc::{boxed::Box, fmt::Debug, string::ToString, sync::Arc, vec, vec::Vec}; -use alloy_consensus::{Eip658Value, Receipt}; -use alloy_eips::{eip2718::Encodable2718, BlockNumHash}; -use alloy_primitives::{address, Address, Bytes, B256}; -use alloy_rlp::Encodable; -use alloy_rpc_types_engine::PayloadAttributes; -use async_trait::async_trait; -use op_alloy_consensus::Hardforks; -use op_alloy_genesis::RollupConfig; -use op_alloy_protocol::{decode_deposit, L1BlockInfoTx, L2BlockInfo, DEPOSIT_EVENT_ABI_HASH}; -use op_alloy_rpc_types_engine::OpPayloadAttributes; - -/// The sequencer fee vault address. -pub const SEQUENCER_FEE_VAULT_ADDRESS: Address = - address!("4200000000000000000000000000000000000011"); - -/// A stateful implementation of the [AttributesBuilder]. -#[derive(Debug, Default)] -pub struct StatefulAttributesBuilder -where - L1P: ChainProvider + Debug, - L2P: L2ChainProvider + Debug, -{ - /// The rollup config. - rollup_cfg: Arc, - /// The system config fetcher. - config_fetcher: L2P, - /// The L1 receipts fetcher. - receipts_fetcher: L1P, -} - -impl StatefulAttributesBuilder -where - L1P: ChainProvider + Debug, - L2P: L2ChainProvider + Debug, -{ - /// Create a new [StatefulAttributesBuilder] with the given epoch. - pub const 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 -where - L1P: ChainProvider + Debug + Send, - L2P: L2ChainProvider + Debug + Send, -{ - async fn prepare_payload_attributes( - &mut self, - l2_parent: L2BlockInfo, - epoch: BlockNumHash, - ) -> PipelineResult { - let l1_header; - let deposit_transactions: Vec; - - let mut sys_config = self - .config_fetcher - .system_config_by_number(l2_parent.block_info.number, self.rollup_cfg.clone()) - .await - .map_err(|e| PipelineError::Provider(e.to_string()).temp())?; - - // 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 - // we can scan for user deposits. - let sequence_number = if l2_parent.l1_origin.number != epoch.number { - let header = self - .receipts_fetcher - .header_by_hash(epoch.hash) - .await - .map_err(|e| PipelineError::Provider(e.to_string()).temp())?; - if l2_parent.l1_origin.hash != header.parent_hash { - return Err(PipelineErrorKind::Reset( - BuilderError::BlockMismatchEpochReset( - epoch, - l2_parent.l1_origin, - header.parent_hash, - ) - .into(), - )); - } - let receipts = self - .receipts_fetcher - .receipts_by_hash(epoch.hash) - .await - .map_err(|e| PipelineError::Provider(e.to_string()).temp())?; - let deposits = - derive_deposits(epoch.hash, &receipts, self.rollup_cfg.deposit_contract_address) - .await - .map_err(|e| PipelineError::BadEncoding(e).crit())?; - sys_config - .update_with_receipts( - &receipts, - self.rollup_cfg.l1_system_config_address, - self.rollup_cfg.is_ecotone_active(header.timestamp), - ) - .map_err(|e| PipelineError::SystemConfigUpdate(e).crit())?; - l1_header = header; - deposit_transactions = deposits; - 0 - } else { - #[allow(clippy::collapsible_else_if)] - if l2_parent.l1_origin.hash != epoch.hash { - return Err(PipelineErrorKind::Reset( - BuilderError::BlockMismatch(epoch, l2_parent.l1_origin).into(), - )); - } - - let header = self - .receipts_fetcher - .header_by_hash(epoch.hash) - .await - .map_err(|e| PipelineError::Provider(e.to_string()).temp())?; - l1_header = header; - deposit_transactions = vec![]; - l2_parent.seq_num + 1 - }; - - // Sanity check the L1 origin was correctly selected to maintain the time invariant - // between L1 and L2. - let next_l2_time = l2_parent.block_info.timestamp + self.rollup_cfg.block_time; - if next_l2_time < l1_header.timestamp { - return Err(PipelineErrorKind::Reset( - BuilderError::BrokenTimeInvariant( - l2_parent.l1_origin, - next_l2_time, - BlockNumHash { hash: l1_header.hash_slow(), number: l1_header.number }, - l1_header.timestamp, - ) - .into(), - )); - } - - let mut upgrade_transactions: Vec = vec![]; - if self.rollup_cfg.is_ecotone_active(next_l2_time) && - !self.rollup_cfg.is_ecotone_active(l2_parent.block_info.timestamp) - { - upgrade_transactions = Hardforks::ecotone_txs(); - } - if self.rollup_cfg.is_fjord_active(next_l2_time) && - !self.rollup_cfg.is_fjord_active(l2_parent.block_info.timestamp) - { - upgrade_transactions.append(&mut Hardforks::fjord_txs()); - } - - // Build and encode the L1 info transaction for the current payload. - let (_, l1_info_tx_envelope) = L1BlockInfoTx::try_new_with_deposit_tx( - &self.rollup_cfg, - &sys_config, - sequence_number, - &l1_header, - next_l2_time, - ) - .map_err(|e| { - PipelineError::AttributesBuilder(BuilderError::Custom(e.to_string())).crit() - })?; - let mut encoded_l1_info_tx = Vec::with_capacity(l1_info_tx_envelope.length()); - l1_info_tx_envelope.encode_2718(&mut encoded_l1_info_tx); - - let mut txs = - Vec::with_capacity(1 + deposit_transactions.len() + upgrade_transactions.len()); - txs.push(encoded_l1_info_tx.into()); - txs.extend(deposit_transactions); - txs.extend(upgrade_transactions); - - let mut withdrawals = None; - if self.rollup_cfg.is_canyon_active(next_l2_time) { - withdrawals = Some(Vec::default()); - } - - let mut parent_beacon_root = None; - if self.rollup_cfg.is_ecotone_active(next_l2_time) { - // if the parent beacon root is not available, default to zero hash - parent_beacon_root = Some(l1_header.parent_beacon_block_root.unwrap_or_default()); - } - - Ok(OpPayloadAttributes { - payload_attributes: PayloadAttributes { - timestamp: next_l2_time, - prev_randao: l1_header.mix_hash, - suggested_fee_recipient: SEQUENCER_FEE_VAULT_ADDRESS, - parent_beacon_block_root: parent_beacon_root, - withdrawals, - }, - transactions: Some(txs), - no_tx_pool: Some(true), - gas_limit: Some(u64::from_be_bytes( - alloy_primitives::U64::from(sys_config.gas_limit).to_be_bytes(), - )), - eip_1559_params: sys_config.eip_1559_params( - &self.rollup_cfg, - l2_parent.block_info.timestamp, - next_l2_time, - ), - }) - } -} - -/// Derive deposits as `Vec` for transaction receipts. -/// -/// Successful deposits must be emitted by the deposit contract and have the correct event -/// signature. So the receipt address must equal the specified deposit contract and the first topic -/// must be the [DEPOSIT_EVENT_ABI_HASH]. -async fn derive_deposits( - block_hash: B256, - receipts: &[Receipt], - deposit_contract: Address, -) -> Result, PipelineEncodingError> { - let mut global_index = 0; - let mut res = Vec::new(); - for r in receipts.iter() { - if Eip658Value::Eip658(false) == r.status { - continue; - } - for l in r.logs.iter() { - let curr_index = global_index; - global_index += 1; - if !l.data.topics().first().map_or(false, |i| *i == DEPOSIT_EVENT_ABI_HASH) { - continue; - } - if l.address != deposit_contract { - continue; - } - let decoded = decode_deposit(block_hash, curr_index, l)?; - res.push(decoded); - } - } - Ok(res) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - errors::ResetError, - test_utils::{TestChainProvider, TestSystemConfigL2Fetcher}, - }; - use alloc::vec; - use alloy_consensus::Header; - use alloy_primitives::{Log, LogData, B256, U256, U64}; - use op_alloy_genesis::SystemConfig; - use op_alloy_protocol::{BlockInfo, DepositError}; - - fn generate_valid_log() -> Log { - let deposit_contract = address!("1111111111111111111111111111111111111111"); - let mut data = vec![0u8; 192]; - let offset: [u8; 8] = U64::from(32).to_be_bytes(); - data[24..32].copy_from_slice(&offset); - let len: [u8; 8] = U64::from(128).to_be_bytes(); - data[56..64].copy_from_slice(&len); - // Copy the u128 mint value - let mint: [u8; 16] = 10_u128.to_be_bytes(); - data[80..96].copy_from_slice(&mint); - // Copy the tx value - let value: [u8; 32] = U256::from(100).to_be_bytes(); - data[96..128].copy_from_slice(&value); - // Copy the gas limit - let gas: [u8; 8] = 1000_u64.to_be_bytes(); - data[128..136].copy_from_slice(&gas); - // Copy the isCreation flag - data[136] = 1; - let from = address!("2222222222222222222222222222222222222222"); - let mut from_bytes = vec![0u8; 32]; - from_bytes[12..32].copy_from_slice(from.as_slice()); - let to = address!("3333333333333333333333333333333333333333"); - let mut to_bytes = vec![0u8; 32]; - to_bytes[12..32].copy_from_slice(to.as_slice()); - Log { - address: deposit_contract, - data: LogData::new_unchecked( - vec![ - DEPOSIT_EVENT_ABI_HASH, - B256::from_slice(&from_bytes), - B256::from_slice(&to_bytes), - B256::default(), - ], - Bytes::from(data), - ), - } - } - - fn generate_valid_receipt() -> Receipt { - let mut bad_dest_log = generate_valid_log(); - bad_dest_log.data.topics_mut()[1] = B256::default(); - let mut invalid_topic_log = generate_valid_log(); - invalid_topic_log.data.topics_mut()[0] = B256::default(); - Receipt { - status: Eip658Value::Eip658(true), - logs: vec![generate_valid_log(), bad_dest_log, invalid_topic_log], - ..Default::default() - } - } - - #[tokio::test] - async fn test_derive_deposits_empty() { - let receipts = vec![]; - let deposit_contract = Address::default(); - let result = derive_deposits(B256::default(), &receipts, deposit_contract).await; - assert!(result.unwrap().is_empty()); - } - - #[tokio::test] - async fn test_derive_deposits_non_deposit_events_filtered_out() { - let deposit_contract = address!("1111111111111111111111111111111111111111"); - let mut invalid = generate_valid_receipt(); - invalid.logs[0].data = LogData::new_unchecked(vec![], Bytes::default()); - let receipts = vec![generate_valid_receipt(), generate_valid_receipt(), invalid]; - let result = derive_deposits(B256::default(), &receipts, deposit_contract).await; - assert_eq!(result.unwrap().len(), 5); - } - - #[tokio::test] - async fn test_derive_deposits_non_deposit_contract_addr() { - let deposit_contract = address!("1111111111111111111111111111111111111111"); - let mut invalid = generate_valid_receipt(); - invalid.logs[0].address = Address::default(); - let receipts = vec![generate_valid_receipt(), generate_valid_receipt(), invalid]; - let result = derive_deposits(B256::default(), &receipts, deposit_contract).await; - assert_eq!(result.unwrap().len(), 5); - } - - #[tokio::test] - async fn test_derive_deposits_decoding_errors() { - let deposit_contract = address!("1111111111111111111111111111111111111111"); - let mut invalid = generate_valid_receipt(); - invalid.logs[0].data = - LogData::new_unchecked(vec![DEPOSIT_EVENT_ABI_HASH], Bytes::default()); - let receipts = vec![generate_valid_receipt(), generate_valid_receipt(), invalid]; - let result = derive_deposits(B256::default(), &receipts, deposit_contract).await; - let downcasted = result.unwrap_err(); - assert_eq!(downcasted, DepositError::UnexpectedTopicsLen(1).into()); - } - - #[tokio::test] - async fn test_derive_deposits_succeeds() { - let deposit_contract = address!("1111111111111111111111111111111111111111"); - let receipts = vec![generate_valid_receipt(), generate_valid_receipt()]; - let result = derive_deposits(B256::default(), &receipts, deposit_contract).await; - assert_eq!(result.unwrap().len(), 4); - } - - #[tokio::test] - async fn test_prepare_payload_block_mismatch_epoch_reset() { - let cfg = Arc::new(RollupConfig::default()); - let l2_number = 1; - let mut fetcher = TestSystemConfigL2Fetcher::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 = BlockNumHash { hash, number: l2_number }; - let l2_parent = L2BlockInfo { - block_info: BlockInfo { hash: B256::ZERO, number: l2_number, ..Default::default() }, - l1_origin: BlockNumHash { 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 - // hash. Here we use the default header whose hash will not equal the custom `l2_hash`. - let expected = - BuilderError::BlockMismatchEpochReset(epoch, l2_parent.l1_origin, B256::default()); - let err = builder.prepare_payload_attributes(l2_parent, epoch).await.unwrap_err(); - assert_eq!(err, PipelineErrorKind::Reset(expected.into())); - } - - #[tokio::test] - async fn test_prepare_payload_block_mismatch() { - let cfg = Arc::new(RollupConfig::default()); - let l2_number = 1; - let mut fetcher = TestSystemConfigL2Fetcher::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 = BlockNumHash { hash, number: l2_number }; - let l2_parent = L2BlockInfo { - block_info: BlockInfo { hash: B256::ZERO, number: l2_number, ..Default::default() }, - l1_origin: BlockNumHash { 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 - // Here the default header is used whose hash will not equal the custom `l2_hash` above. - let expected = BuilderError::BlockMismatch(epoch, l2_parent.l1_origin); - let err = builder.prepare_payload_attributes(l2_parent, epoch).await.unwrap_err(); - assert_eq!(err, PipelineErrorKind::Reset(ResetError::AttributesBuilder(expected))); - } - - #[tokio::test] - async fn test_prepare_payload_broken_time_invariant() { - let block_time = 10; - let timestamp = 100; - let cfg = Arc::new(RollupConfig { block_time, ..Default::default() }); - let l2_number = 1; - let mut fetcher = TestSystemConfigL2Fetcher::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 = BlockNumHash { hash, number: l2_number }; - let l2_parent = L2BlockInfo { - block_info: BlockInfo { hash: B256::ZERO, number: l2_number, ..Default::default() }, - l1_origin: BlockNumHash { hash, number: l2_number }, - seq_num: 0, - }; - let next_l2_time = l2_parent.block_info.timestamp + block_time; - let block_id = BlockNumHash { hash, number: 0 }; - let expected = BuilderError::BrokenTimeInvariant( - l2_parent.l1_origin, - next_l2_time, - block_id, - timestamp, - ); - let err = builder.prepare_payload_attributes(l2_parent, epoch).await.unwrap_err(); - assert_eq!(err, PipelineErrorKind::Reset(ResetError::AttributesBuilder(expected))); - } - - #[tokio::test] - async fn test_prepare_payload_without_forks() { - let block_time = 10; - let timestamp = 100; - let cfg = Arc::new(RollupConfig { block_time, ..Default::default() }); - let l2_number = 1; - let mut fetcher = TestSystemConfigL2Fetcher::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 = BlockNumHash { hash, number: l2_number }; - let l2_parent = L2BlockInfo { - block_info: BlockInfo { - hash: B256::ZERO, - number: l2_number, - timestamp, - parent_hash: hash, - }, - l1_origin: BlockNumHash { hash, number: l2_number }, - seq_num: 0, - }; - let next_l2_time = l2_parent.block_info.timestamp + block_time; - let payload = builder.prepare_payload_attributes(l2_parent, epoch).await.unwrap(); - let expected = OpPayloadAttributes { - payload_attributes: PayloadAttributes { - timestamp: next_l2_time, - prev_randao, - suggested_fee_recipient: SEQUENCER_FEE_VAULT_ADDRESS, - parent_beacon_block_root: None, - withdrawals: None, - }, - transactions: payload.transactions.clone(), - no_tx_pool: Some(true), - gas_limit: Some(u64::from_be_bytes( - alloy_primitives::U64::from(SystemConfig::default().gas_limit).to_be_bytes(), - )), - eip_1559_params: None, - }; - assert_eq!(payload, expected); - assert_eq!(payload.transactions.unwrap().len(), 1); - } - - #[tokio::test] - async fn test_prepare_payload_with_canyon() { - let block_time = 10; - let timestamp = 100; - let cfg = Arc::new(RollupConfig { block_time, canyon_time: Some(0), ..Default::default() }); - let l2_number = 1; - let mut fetcher = TestSystemConfigL2Fetcher::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 = BlockNumHash { hash, number: l2_number }; - let l2_parent = L2BlockInfo { - block_info: BlockInfo { - hash: B256::ZERO, - number: l2_number, - timestamp, - parent_hash: hash, - }, - l1_origin: BlockNumHash { hash, number: l2_number }, - seq_num: 0, - }; - let next_l2_time = l2_parent.block_info.timestamp + block_time; - let payload = builder.prepare_payload_attributes(l2_parent, epoch).await.unwrap(); - let expected = OpPayloadAttributes { - payload_attributes: PayloadAttributes { - timestamp: next_l2_time, - prev_randao, - suggested_fee_recipient: SEQUENCER_FEE_VAULT_ADDRESS, - parent_beacon_block_root: None, - withdrawals: Some(Vec::default()), - }, - transactions: payload.transactions.clone(), - no_tx_pool: Some(true), - gas_limit: Some(u64::from_be_bytes( - alloy_primitives::U64::from(SystemConfig::default().gas_limit).to_be_bytes(), - )), - eip_1559_params: None, - }; - assert_eq!(payload, expected); - assert_eq!(payload.transactions.unwrap().len(), 1); - } - - #[tokio::test] - async fn test_prepare_payload_with_ecotone() { - let block_time = 2; - let timestamp = 100; - let cfg = - Arc::new(RollupConfig { block_time, ecotone_time: Some(102), ..Default::default() }); - let l2_number = 1; - let mut fetcher = TestSystemConfigL2Fetcher::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()); - 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 = BlockNumHash { hash, number: l2_number }; - let l2_parent = L2BlockInfo { - block_info: BlockInfo { - hash: B256::ZERO, - number: l2_number, - timestamp, - parent_hash: hash, - }, - l1_origin: BlockNumHash { hash, number: l2_number }, - seq_num: 0, - }; - let next_l2_time = l2_parent.block_info.timestamp + block_time; - let payload = builder.prepare_payload_attributes(l2_parent, epoch).await.unwrap(); - let expected = OpPayloadAttributes { - payload_attributes: PayloadAttributes { - timestamp: next_l2_time, - prev_randao, - suggested_fee_recipient: SEQUENCER_FEE_VAULT_ADDRESS, - parent_beacon_block_root, - withdrawals: Some(vec![]), - }, - transactions: payload.transactions.clone(), - no_tx_pool: Some(true), - gas_limit: Some(u64::from_be_bytes( - alloy_primitives::U64::from(SystemConfig::default().gas_limit).to_be_bytes(), - )), - eip_1559_params: None, - }; - assert_eq!(payload, expected); - assert_eq!(payload.transactions.unwrap().len(), 7); - } - - #[tokio::test] - async fn test_prepare_payload_with_fjord() { - let block_time = 2; - let timestamp = 100; - let cfg = - Arc::new(RollupConfig { block_time, fjord_time: Some(102), ..Default::default() }); - let l2_number = 1; - let mut fetcher = TestSystemConfigL2Fetcher::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 = BlockNumHash { hash, number: l2_number }; - let l2_parent = L2BlockInfo { - block_info: BlockInfo { - hash: B256::ZERO, - number: l2_number, - timestamp, - parent_hash: hash, - }, - l1_origin: BlockNumHash { hash, number: l2_number }, - seq_num: 0, - }; - let next_l2_time = l2_parent.block_info.timestamp + block_time; - let payload = builder.prepare_payload_attributes(l2_parent, epoch).await.unwrap(); - let expected = OpPayloadAttributes { - payload_attributes: PayloadAttributes { - timestamp: next_l2_time, - prev_randao, - suggested_fee_recipient: SEQUENCER_FEE_VAULT_ADDRESS, - parent_beacon_block_root: Some(B256::ZERO), - withdrawals: Some(vec![]), - }, - transactions: payload.transactions.clone(), - no_tx_pool: Some(true), - gas_limit: Some(u64::from_be_bytes( - alloy_primitives::U64::from(SystemConfig::default().gas_limit).to_be_bytes(), - )), - eip_1559_params: None, - }; - assert_eq!(payload.transactions.as_ref().unwrap().len(), 10); - assert_eq!(payload, expected); - } -} +mod stateful; +pub use stateful::StatefulAttributesBuilder; diff --git a/crates/derive/src/attributes/stateful.rs b/crates/derive/src/attributes/stateful.rs new file mode 100644 index 000000000..f61e04133 --- /dev/null +++ b/crates/derive/src/attributes/stateful.rs @@ -0,0 +1,615 @@ +//! The [`AttributesBuilder`] and it's default implementation. + +use crate::{ + errors::{ + BuilderError, PipelineEncodingError, PipelineError, PipelineErrorKind, PipelineResult, + }, + traits::{AttributesBuilder, ChainProvider, L2ChainProvider}, +}; +use alloc::{boxed::Box, fmt::Debug, string::ToString, sync::Arc, vec, vec::Vec}; +use alloy_consensus::{Eip658Value, Receipt}; +use alloy_eips::{eip2718::Encodable2718, BlockNumHash}; +use alloy_primitives::{address, Address, Bytes, B256}; +use alloy_rlp::Encodable; +use alloy_rpc_types_engine::PayloadAttributes; +use async_trait::async_trait; +use op_alloy_consensus::Hardforks; +use op_alloy_genesis::RollupConfig; +use op_alloy_protocol::{decode_deposit, L1BlockInfoTx, L2BlockInfo, DEPOSIT_EVENT_ABI_HASH}; +use op_alloy_rpc_types_engine::OpPayloadAttributes; + +/// The sequencer fee vault address. +const SEQUENCER_FEE_VAULT_ADDRESS: Address = address!("4200000000000000000000000000000000000011"); + +/// A stateful implementation of the [AttributesBuilder]. +#[derive(Debug, Default)] +pub struct StatefulAttributesBuilder +where + L1P: ChainProvider + Debug, + L2P: L2ChainProvider + Debug, +{ + /// The rollup config. + rollup_cfg: Arc, + /// The system config fetcher. + config_fetcher: L2P, + /// The L1 receipts fetcher. + receipts_fetcher: L1P, +} + +impl StatefulAttributesBuilder +where + L1P: ChainProvider + Debug, + L2P: L2ChainProvider + Debug, +{ + /// Create a new [StatefulAttributesBuilder] with the given epoch. + pub const 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 +where + L1P: ChainProvider + Debug + Send, + L2P: L2ChainProvider + Debug + Send, +{ + async fn prepare_payload_attributes( + &mut self, + l2_parent: L2BlockInfo, + epoch: BlockNumHash, + ) -> PipelineResult { + let l1_header; + let deposit_transactions: Vec; + + let mut sys_config = self + .config_fetcher + .system_config_by_number(l2_parent.block_info.number, self.rollup_cfg.clone()) + .await + .map_err(|e| PipelineError::Provider(e.to_string()).temp())?; + + // 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 + // we can scan for user deposits. + let sequence_number = if l2_parent.l1_origin.number != epoch.number { + let header = self + .receipts_fetcher + .header_by_hash(epoch.hash) + .await + .map_err(|e| PipelineError::Provider(e.to_string()).temp())?; + if l2_parent.l1_origin.hash != header.parent_hash { + return Err(PipelineErrorKind::Reset( + BuilderError::BlockMismatchEpochReset( + epoch, + l2_parent.l1_origin, + header.parent_hash, + ) + .into(), + )); + } + let receipts = self + .receipts_fetcher + .receipts_by_hash(epoch.hash) + .await + .map_err(|e| PipelineError::Provider(e.to_string()).temp())?; + let deposits = + derive_deposits(epoch.hash, &receipts, self.rollup_cfg.deposit_contract_address) + .await + .map_err(|e| PipelineError::BadEncoding(e).crit())?; + sys_config + .update_with_receipts( + &receipts, + self.rollup_cfg.l1_system_config_address, + self.rollup_cfg.is_ecotone_active(header.timestamp), + ) + .map_err(|e| PipelineError::SystemConfigUpdate(e).crit())?; + l1_header = header; + deposit_transactions = deposits; + 0 + } else { + #[allow(clippy::collapsible_else_if)] + if l2_parent.l1_origin.hash != epoch.hash { + return Err(PipelineErrorKind::Reset( + BuilderError::BlockMismatch(epoch, l2_parent.l1_origin).into(), + )); + } + + let header = self + .receipts_fetcher + .header_by_hash(epoch.hash) + .await + .map_err(|e| PipelineError::Provider(e.to_string()).temp())?; + l1_header = header; + deposit_transactions = vec![]; + l2_parent.seq_num + 1 + }; + + // Sanity check the L1 origin was correctly selected to maintain the time invariant + // between L1 and L2. + let next_l2_time = l2_parent.block_info.timestamp + self.rollup_cfg.block_time; + if next_l2_time < l1_header.timestamp { + return Err(PipelineErrorKind::Reset( + BuilderError::BrokenTimeInvariant( + l2_parent.l1_origin, + next_l2_time, + BlockNumHash { hash: l1_header.hash_slow(), number: l1_header.number }, + l1_header.timestamp, + ) + .into(), + )); + } + + let mut upgrade_transactions: Vec = vec![]; + if self.rollup_cfg.is_ecotone_active(next_l2_time) && + !self.rollup_cfg.is_ecotone_active(l2_parent.block_info.timestamp) + { + upgrade_transactions = Hardforks::ecotone_txs(); + } + if self.rollup_cfg.is_fjord_active(next_l2_time) && + !self.rollup_cfg.is_fjord_active(l2_parent.block_info.timestamp) + { + upgrade_transactions.append(&mut Hardforks::fjord_txs()); + } + + // Build and encode the L1 info transaction for the current payload. + let (_, l1_info_tx_envelope) = L1BlockInfoTx::try_new_with_deposit_tx( + &self.rollup_cfg, + &sys_config, + sequence_number, + &l1_header, + next_l2_time, + ) + .map_err(|e| { + PipelineError::AttributesBuilder(BuilderError::Custom(e.to_string())).crit() + })?; + let mut encoded_l1_info_tx = Vec::with_capacity(l1_info_tx_envelope.length()); + l1_info_tx_envelope.encode_2718(&mut encoded_l1_info_tx); + + let mut txs = + Vec::with_capacity(1 + deposit_transactions.len() + upgrade_transactions.len()); + txs.push(encoded_l1_info_tx.into()); + txs.extend(deposit_transactions); + txs.extend(upgrade_transactions); + + let mut withdrawals = None; + if self.rollup_cfg.is_canyon_active(next_l2_time) { + withdrawals = Some(Vec::default()); + } + + let mut parent_beacon_root = None; + if self.rollup_cfg.is_ecotone_active(next_l2_time) { + // if the parent beacon root is not available, default to zero hash + parent_beacon_root = Some(l1_header.parent_beacon_block_root.unwrap_or_default()); + } + + Ok(OpPayloadAttributes { + payload_attributes: PayloadAttributes { + timestamp: next_l2_time, + prev_randao: l1_header.mix_hash, + suggested_fee_recipient: SEQUENCER_FEE_VAULT_ADDRESS, + parent_beacon_block_root: parent_beacon_root, + withdrawals, + }, + transactions: Some(txs), + no_tx_pool: Some(true), + gas_limit: Some(u64::from_be_bytes( + alloy_primitives::U64::from(sys_config.gas_limit).to_be_bytes(), + )), + eip_1559_params: sys_config.eip_1559_params( + &self.rollup_cfg, + l2_parent.block_info.timestamp, + next_l2_time, + ), + }) + } +} + +/// Derive deposits as `Vec` for transaction receipts. +/// +/// Successful deposits must be emitted by the deposit contract and have the correct event +/// signature. So the receipt address must equal the specified deposit contract and the first topic +/// must be the [DEPOSIT_EVENT_ABI_HASH]. +async fn derive_deposits( + block_hash: B256, + receipts: &[Receipt], + deposit_contract: Address, +) -> Result, PipelineEncodingError> { + let mut global_index = 0; + let mut res = Vec::new(); + for r in receipts.iter() { + if Eip658Value::Eip658(false) == r.status { + continue; + } + for l in r.logs.iter() { + let curr_index = global_index; + global_index += 1; + if !l.data.topics().first().map_or(false, |i| *i == DEPOSIT_EVENT_ABI_HASH) { + continue; + } + if l.address != deposit_contract { + continue; + } + let decoded = decode_deposit(block_hash, curr_index, l)?; + res.push(decoded); + } + } + Ok(res) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + errors::ResetError, + test_utils::{TestChainProvider, TestSystemConfigL2Fetcher}, + }; + use alloc::vec; + use alloy_consensus::Header; + use alloy_primitives::{Log, LogData, B256, U256, U64}; + use op_alloy_genesis::SystemConfig; + use op_alloy_protocol::{BlockInfo, DepositError}; + + fn generate_valid_log() -> Log { + let deposit_contract = address!("1111111111111111111111111111111111111111"); + let mut data = vec![0u8; 192]; + let offset: [u8; 8] = U64::from(32).to_be_bytes(); + data[24..32].copy_from_slice(&offset); + let len: [u8; 8] = U64::from(128).to_be_bytes(); + data[56..64].copy_from_slice(&len); + // Copy the u128 mint value + let mint: [u8; 16] = 10_u128.to_be_bytes(); + data[80..96].copy_from_slice(&mint); + // Copy the tx value + let value: [u8; 32] = U256::from(100).to_be_bytes(); + data[96..128].copy_from_slice(&value); + // Copy the gas limit + let gas: [u8; 8] = 1000_u64.to_be_bytes(); + data[128..136].copy_from_slice(&gas); + // Copy the isCreation flag + data[136] = 1; + let from = address!("2222222222222222222222222222222222222222"); + let mut from_bytes = vec![0u8; 32]; + from_bytes[12..32].copy_from_slice(from.as_slice()); + let to = address!("3333333333333333333333333333333333333333"); + let mut to_bytes = vec![0u8; 32]; + to_bytes[12..32].copy_from_slice(to.as_slice()); + Log { + address: deposit_contract, + data: LogData::new_unchecked( + vec![ + DEPOSIT_EVENT_ABI_HASH, + B256::from_slice(&from_bytes), + B256::from_slice(&to_bytes), + B256::default(), + ], + Bytes::from(data), + ), + } + } + + fn generate_valid_receipt() -> Receipt { + let mut bad_dest_log = generate_valid_log(); + bad_dest_log.data.topics_mut()[1] = B256::default(); + let mut invalid_topic_log = generate_valid_log(); + invalid_topic_log.data.topics_mut()[0] = B256::default(); + Receipt { + status: Eip658Value::Eip658(true), + logs: vec![generate_valid_log(), bad_dest_log, invalid_topic_log], + ..Default::default() + } + } + + #[tokio::test] + async fn test_derive_deposits_empty() { + let receipts = vec![]; + let deposit_contract = Address::default(); + let result = derive_deposits(B256::default(), &receipts, deposit_contract).await; + assert!(result.unwrap().is_empty()); + } + + #[tokio::test] + async fn test_derive_deposits_non_deposit_events_filtered_out() { + let deposit_contract = address!("1111111111111111111111111111111111111111"); + let mut invalid = generate_valid_receipt(); + invalid.logs[0].data = LogData::new_unchecked(vec![], Bytes::default()); + let receipts = vec![generate_valid_receipt(), generate_valid_receipt(), invalid]; + let result = derive_deposits(B256::default(), &receipts, deposit_contract).await; + assert_eq!(result.unwrap().len(), 5); + } + + #[tokio::test] + async fn test_derive_deposits_non_deposit_contract_addr() { + let deposit_contract = address!("1111111111111111111111111111111111111111"); + let mut invalid = generate_valid_receipt(); + invalid.logs[0].address = Address::default(); + let receipts = vec![generate_valid_receipt(), generate_valid_receipt(), invalid]; + let result = derive_deposits(B256::default(), &receipts, deposit_contract).await; + assert_eq!(result.unwrap().len(), 5); + } + + #[tokio::test] + async fn test_derive_deposits_decoding_errors() { + let deposit_contract = address!("1111111111111111111111111111111111111111"); + let mut invalid = generate_valid_receipt(); + invalid.logs[0].data = + LogData::new_unchecked(vec![DEPOSIT_EVENT_ABI_HASH], Bytes::default()); + let receipts = vec![generate_valid_receipt(), generate_valid_receipt(), invalid]; + let result = derive_deposits(B256::default(), &receipts, deposit_contract).await; + let downcasted = result.unwrap_err(); + assert_eq!(downcasted, DepositError::UnexpectedTopicsLen(1).into()); + } + + #[tokio::test] + async fn test_derive_deposits_succeeds() { + let deposit_contract = address!("1111111111111111111111111111111111111111"); + let receipts = vec![generate_valid_receipt(), generate_valid_receipt()]; + let result = derive_deposits(B256::default(), &receipts, deposit_contract).await; + assert_eq!(result.unwrap().len(), 4); + } + + #[tokio::test] + async fn test_prepare_payload_block_mismatch_epoch_reset() { + let cfg = Arc::new(RollupConfig::default()); + let l2_number = 1; + let mut fetcher = TestSystemConfigL2Fetcher::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 = BlockNumHash { hash, number: l2_number }; + let l2_parent = L2BlockInfo { + block_info: BlockInfo { hash: B256::ZERO, number: l2_number, ..Default::default() }, + l1_origin: BlockNumHash { 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 + // hash. Here we use the default header whose hash will not equal the custom `l2_hash`. + let expected = + BuilderError::BlockMismatchEpochReset(epoch, l2_parent.l1_origin, B256::default()); + let err = builder.prepare_payload_attributes(l2_parent, epoch).await.unwrap_err(); + assert_eq!(err, PipelineErrorKind::Reset(expected.into())); + } + + #[tokio::test] + async fn test_prepare_payload_block_mismatch() { + let cfg = Arc::new(RollupConfig::default()); + let l2_number = 1; + let mut fetcher = TestSystemConfigL2Fetcher::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 = BlockNumHash { hash, number: l2_number }; + let l2_parent = L2BlockInfo { + block_info: BlockInfo { hash: B256::ZERO, number: l2_number, ..Default::default() }, + l1_origin: BlockNumHash { 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 + // Here the default header is used whose hash will not equal the custom `l2_hash` above. + let expected = BuilderError::BlockMismatch(epoch, l2_parent.l1_origin); + let err = builder.prepare_payload_attributes(l2_parent, epoch).await.unwrap_err(); + assert_eq!(err, PipelineErrorKind::Reset(ResetError::AttributesBuilder(expected))); + } + + #[tokio::test] + async fn test_prepare_payload_broken_time_invariant() { + let block_time = 10; + let timestamp = 100; + let cfg = Arc::new(RollupConfig { block_time, ..Default::default() }); + let l2_number = 1; + let mut fetcher = TestSystemConfigL2Fetcher::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 = BlockNumHash { hash, number: l2_number }; + let l2_parent = L2BlockInfo { + block_info: BlockInfo { hash: B256::ZERO, number: l2_number, ..Default::default() }, + l1_origin: BlockNumHash { hash, number: l2_number }, + seq_num: 0, + }; + let next_l2_time = l2_parent.block_info.timestamp + block_time; + let block_id = BlockNumHash { hash, number: 0 }; + let expected = BuilderError::BrokenTimeInvariant( + l2_parent.l1_origin, + next_l2_time, + block_id, + timestamp, + ); + let err = builder.prepare_payload_attributes(l2_parent, epoch).await.unwrap_err(); + assert_eq!(err, PipelineErrorKind::Reset(ResetError::AttributesBuilder(expected))); + } + + #[tokio::test] + async fn test_prepare_payload_without_forks() { + let block_time = 10; + let timestamp = 100; + let cfg = Arc::new(RollupConfig { block_time, ..Default::default() }); + let l2_number = 1; + let mut fetcher = TestSystemConfigL2Fetcher::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 = BlockNumHash { hash, number: l2_number }; + let l2_parent = L2BlockInfo { + block_info: BlockInfo { + hash: B256::ZERO, + number: l2_number, + timestamp, + parent_hash: hash, + }, + l1_origin: BlockNumHash { hash, number: l2_number }, + seq_num: 0, + }; + let next_l2_time = l2_parent.block_info.timestamp + block_time; + let payload = builder.prepare_payload_attributes(l2_parent, epoch).await.unwrap(); + let expected = OpPayloadAttributes { + payload_attributes: PayloadAttributes { + timestamp: next_l2_time, + prev_randao, + suggested_fee_recipient: SEQUENCER_FEE_VAULT_ADDRESS, + parent_beacon_block_root: None, + withdrawals: None, + }, + transactions: payload.transactions.clone(), + no_tx_pool: Some(true), + gas_limit: Some(u64::from_be_bytes( + alloy_primitives::U64::from(SystemConfig::default().gas_limit).to_be_bytes(), + )), + eip_1559_params: None, + }; + assert_eq!(payload, expected); + assert_eq!(payload.transactions.unwrap().len(), 1); + } + + #[tokio::test] + async fn test_prepare_payload_with_canyon() { + let block_time = 10; + let timestamp = 100; + let cfg = Arc::new(RollupConfig { block_time, canyon_time: Some(0), ..Default::default() }); + let l2_number = 1; + let mut fetcher = TestSystemConfigL2Fetcher::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 = BlockNumHash { hash, number: l2_number }; + let l2_parent = L2BlockInfo { + block_info: BlockInfo { + hash: B256::ZERO, + number: l2_number, + timestamp, + parent_hash: hash, + }, + l1_origin: BlockNumHash { hash, number: l2_number }, + seq_num: 0, + }; + let next_l2_time = l2_parent.block_info.timestamp + block_time; + let payload = builder.prepare_payload_attributes(l2_parent, epoch).await.unwrap(); + let expected = OpPayloadAttributes { + payload_attributes: PayloadAttributes { + timestamp: next_l2_time, + prev_randao, + suggested_fee_recipient: SEQUENCER_FEE_VAULT_ADDRESS, + parent_beacon_block_root: None, + withdrawals: Some(Vec::default()), + }, + transactions: payload.transactions.clone(), + no_tx_pool: Some(true), + gas_limit: Some(u64::from_be_bytes( + alloy_primitives::U64::from(SystemConfig::default().gas_limit).to_be_bytes(), + )), + eip_1559_params: None, + }; + assert_eq!(payload, expected); + assert_eq!(payload.transactions.unwrap().len(), 1); + } + + #[tokio::test] + async fn test_prepare_payload_with_ecotone() { + let block_time = 2; + let timestamp = 100; + let cfg = + Arc::new(RollupConfig { block_time, ecotone_time: Some(102), ..Default::default() }); + let l2_number = 1; + let mut fetcher = TestSystemConfigL2Fetcher::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()); + 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 = BlockNumHash { hash, number: l2_number }; + let l2_parent = L2BlockInfo { + block_info: BlockInfo { + hash: B256::ZERO, + number: l2_number, + timestamp, + parent_hash: hash, + }, + l1_origin: BlockNumHash { hash, number: l2_number }, + seq_num: 0, + }; + let next_l2_time = l2_parent.block_info.timestamp + block_time; + let payload = builder.prepare_payload_attributes(l2_parent, epoch).await.unwrap(); + let expected = OpPayloadAttributes { + payload_attributes: PayloadAttributes { + timestamp: next_l2_time, + prev_randao, + suggested_fee_recipient: SEQUENCER_FEE_VAULT_ADDRESS, + parent_beacon_block_root, + withdrawals: Some(vec![]), + }, + transactions: payload.transactions.clone(), + no_tx_pool: Some(true), + gas_limit: Some(u64::from_be_bytes( + alloy_primitives::U64::from(SystemConfig::default().gas_limit).to_be_bytes(), + )), + eip_1559_params: None, + }; + assert_eq!(payload, expected); + assert_eq!(payload.transactions.unwrap().len(), 7); + } + + #[tokio::test] + async fn test_prepare_payload_with_fjord() { + let block_time = 2; + let timestamp = 100; + let cfg = + Arc::new(RollupConfig { block_time, fjord_time: Some(102), ..Default::default() }); + let l2_number = 1; + let mut fetcher = TestSystemConfigL2Fetcher::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 = BlockNumHash { hash, number: l2_number }; + let l2_parent = L2BlockInfo { + block_info: BlockInfo { + hash: B256::ZERO, + number: l2_number, + timestamp, + parent_hash: hash, + }, + l1_origin: BlockNumHash { hash, number: l2_number }, + seq_num: 0, + }; + let next_l2_time = l2_parent.block_info.timestamp + block_time; + let payload = builder.prepare_payload_attributes(l2_parent, epoch).await.unwrap(); + let expected = OpPayloadAttributes { + payload_attributes: PayloadAttributes { + timestamp: next_l2_time, + prev_randao, + suggested_fee_recipient: SEQUENCER_FEE_VAULT_ADDRESS, + parent_beacon_block_root: Some(B256::ZERO), + withdrawals: Some(vec![]), + }, + transactions: payload.transactions.clone(), + no_tx_pool: Some(true), + gas_limit: Some(u64::from_be_bytes( + alloy_primitives::U64::from(SystemConfig::default().gas_limit).to_be_bytes(), + )), + eip_1559_params: None, + }; + assert_eq!(payload.transactions.as_ref().unwrap().len(), 10); + assert_eq!(payload, expected); + } +} diff --git a/crates/derive/src/lib.rs b/crates/derive/src/lib.rs index bee907787..42c81d87e 100644 --- a/crates/derive/src/lib.rs +++ b/crates/derive/src/lib.rs @@ -9,14 +9,14 @@ extern crate alloc; -/// Re-export commonly used types and traits. +/// Required types and traits for kona's derivation pipeline. pub mod prelude { pub use crate::{ - attributes::StatefulAttributesBuilder, - errors::{PipelineError, PipelineErrorKind}, + attributes::*, + errors::{PipelineError, PipelineErrorKind, PipelineResult}, pipeline::{DerivationPipeline, PipelineBuilder}, - sources::EthereumDataSource, - traits::{ChainProvider, L2ChainProvider, OriginProvider, Pipeline, StepResult}, + sources::*, + traits::*, }; }