diff --git a/.monorepo b/.monorepo index 07b23a47d..2782e1dae 100644 --- a/.monorepo +++ b/.monorepo @@ -1 +1 @@ -gk/holocene-derivation-action-testing-stacked +afe849ea0b0f5ee5889e0fe440ad7827d7ceef5f diff --git a/Cargo.lock b/Cargo.lock index 365d6178f..f885df7e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2192,6 +2192,7 @@ dependencies = [ "anyhow", "async-trait", "cfg-if", + "derive_more", "kona-common", "kona-common-proc", "kona-derive", diff --git a/bin/client/Cargo.toml b/bin/client/Cargo.toml index cea581cbd..ea8990276 100644 --- a/bin/client/Cargo.toml +++ b/bin/client/Cargo.toml @@ -42,6 +42,7 @@ anyhow.workspace = true tracing.workspace = true serde_json.workspace = true async-trait.workspace = true +derive_more = { workspace = true, features = ["full"] } # `tracing-subscriber` feature dependencies tracing-subscriber = { workspace = true, optional = true, features = ["fmt"] } diff --git a/bin/client/src/errors.rs b/bin/client/src/errors.rs new file mode 100644 index 000000000..834fcf5a8 --- /dev/null +++ b/bin/client/src/errors.rs @@ -0,0 +1,45 @@ +//! Error types for the client program. + +use alloc::string::ToString; +use derive_more::derive::Display; +use kona_derive::errors::{PipelineError, PipelineErrorKind}; +use kona_mpt::OrderedListWalkerError; +use kona_preimage::errors::PreimageOracleError; +use op_alloy_protocol::{FromBlockError, OpBlockConversionError}; + +/// Error from an oracle-backed provider. +#[derive(Display, Debug)] +pub enum OracleProviderError { + /// Requested block number is past the chain head. + #[display("Block number ({_0}) past chain head ({_1})")] + BlockNumberPastHead(u64, u64), + /// Preimage oracle error. + #[display("Preimage oracle error: {_0}")] + Preimage(PreimageOracleError), + /// List walker error. + #[display("Trie walker error: {_0}")] + TrieWalker(OrderedListWalkerError), + /// BlockInfo error. + #[display("From block error: {_0}")] + BlockInfo(FromBlockError), + /// Op Block conversion error. + #[display("Op block conversion error: {_0}")] + OpBlockConversion(OpBlockConversionError), + /// Error decoding or encoding RLP. + #[display("RLP error: {_0}")] + Rlp(alloy_rlp::Error), + /// Slice conversion error. + #[display("Slice conversion error: {_0}")] + SliceConversion(core::array::TryFromSliceError), +} + +impl core::error::Error for OracleProviderError {} + +impl From for PipelineErrorKind { + fn from(val: OracleProviderError) -> Self { + match val { + OracleProviderError::BlockNumberPastHead(_, _) => PipelineError::EndOfSource.crit(), + _ => PipelineError::Provider(val.to_string()).crit(), + } + } +} diff --git a/bin/client/src/kona.rs b/bin/client/src/kona.rs index 1824bfb8f..cc14ce88b 100644 --- a/bin/client/src/kona.rs +++ b/bin/client/src/kona.rs @@ -13,10 +13,12 @@ use kona_client::{ l2::OracleL2ChainProvider, BootInfo, CachingOracle, }; +use kona_common::io; use kona_common_proc::client_entry; pub(crate) mod fault; use fault::{fpvm_handle_register, HINT_WRITER, ORACLE_READER}; +use tracing::{error, info, warn}; /// The size of the LRU cache in the oracle. const ORACLE_LRU_SIZE: usize = 1024; @@ -43,6 +45,17 @@ fn main() -> Result<()> { let l2_provider = OracleL2ChainProvider::new(boot.clone(), oracle.clone()); let beacon = OracleBlobProvider::new(oracle.clone()); + // If the genesis block is claimed, we can exit early. + // The agreed upon prestate is consented to by all parties, and there is no state + // transition, so the claim is valid if the claimed output root matches the agreed + // upon output root. + if boot.claimed_l2_block_number == 0 { + warn!("Genesis block claimed. Exiting early."); + let exit_code = + if boot.agreed_l2_output_root == boot.claimed_l2_output_root { 0 } else { 1 }; + io::exit(exit_code); + } + //////////////////////////////////////////////////////////////// // DERIVATION & EXECUTION // //////////////////////////////////////////////////////////////// @@ -55,37 +68,40 @@ fn main() -> Result<()> { // Run the derivation pipeline until we are able to produce the output root of the claimed // L2 block. let (number, output_root) = driver - .produce_output(&boot.rollup_config, &l2_provider, &l2_provider, fpvm_handle_register) + .advance_to_target( + &boot.rollup_config, + &l2_provider, + &l2_provider, + fpvm_handle_register, + ) .await?; //////////////////////////////////////////////////////////////// // EPILOGUE // //////////////////////////////////////////////////////////////// - if number != boot.claimed_l2_block_number || output_root != boot.claimed_l2_output_root { - tracing::error!( + if output_root != boot.claimed_l2_output_root { + error!( target: "client", "Failed to validate L2 block #{number} with output root {output_root}", number = number, output_root = output_root ); - kona_common::io::print(&alloc::format!( + io::print(&alloc::format!( "Failed to validate L2 block #{} with output root {}\n", number, output_root )); - - kona_common::io::exit(1); + io::exit(1); } - tracing::info!( + info!( target: "client", "Successfully validated L2 block #{number} with output root {output_root}", number = number, output_root = output_root ); - - kona_common::io::print(&alloc::format!( + io::print(&alloc::format!( "Successfully validated L2 block #{} with output root {}\n", number, output_root diff --git a/bin/client/src/l1/chain_provider.rs b/bin/client/src/l1/chain_provider.rs index 4ab363da3..85104ae9b 100644 --- a/bin/client/src/l1/chain_provider.rs +++ b/bin/client/src/l1/chain_provider.rs @@ -1,12 +1,12 @@ //! Contains the concrete implementation of the [ChainProvider] trait for the client program. -use crate::{BootInfo, HintType}; +use crate::{errors::OracleProviderError, BootInfo, HintType}; use alloc::{boxed::Box, sync::Arc, vec::Vec}; use alloy_consensus::{Header, Receipt, ReceiptEnvelope, TxEnvelope}; use alloy_eips::eip2718::Decodable2718; use alloy_primitives::{Bytes, B256}; use alloy_rlp::Decodable; -use anyhow::{anyhow, Result}; +use anyhow::Result; use async_trait::async_trait; use kona_derive::traits::ChainProvider; use kona_mpt::{OrderedListWalker, TrieProvider}; @@ -31,28 +31,33 @@ impl OracleL1ChainProvider { #[async_trait] impl ChainProvider for OracleL1ChainProvider { - type Error = anyhow::Error; + type Error = OracleProviderError; - async fn header_by_hash(&mut self, hash: B256) -> Result
{ + async fn header_by_hash(&mut self, hash: B256) -> Result { // Send a hint for the block header. - self.oracle.write(&HintType::L1BlockHeader.encode_with(&[hash.as_ref()])).await?; + self.oracle + .write(&HintType::L1BlockHeader.encode_with(&[hash.as_ref()])) + .await + .map_err(OracleProviderError::Preimage)?; // Fetch the header RLP from the oracle. - let header_rlp = - self.oracle.get(PreimageKey::new(*hash, PreimageKeyType::Keccak256)).await?; + let header_rlp = self + .oracle + .get(PreimageKey::new(*hash, PreimageKeyType::Keccak256)) + .await + .map_err(OracleProviderError::Preimage)?; // Decode the header RLP into a Header. - Header::decode(&mut header_rlp.as_slice()) - .map_err(|e| anyhow!("Failed to decode header RLP: {e}")) + Header::decode(&mut header_rlp.as_slice()).map_err(OracleProviderError::Rlp) } - async fn block_info_by_number(&mut self, block_number: u64) -> Result { + async fn block_info_by_number(&mut self, block_number: u64) -> Result { // Fetch the starting block header. let mut header = self.header_by_hash(self.boot_info.l1_head).await?; // Check if the block number is in range. If not, we can fail early. if block_number > header.number { - anyhow::bail!("Block number past L1 head."); + return Err(OracleProviderError::BlockNumberPastHead(block_number, header.number)); } // Walk back the block headers to the desired block number. @@ -68,24 +73,28 @@ impl ChainProvider for OracleL1ChainProvider { }) } - async fn receipts_by_hash(&mut self, hash: B256) -> Result> { + async fn receipts_by_hash(&mut self, hash: B256) -> Result, Self::Error> { // Fetch the block header to find the receipts root. let header = self.header_by_hash(hash).await?; // Send a hint for the block's receipts, and walk through the receipts trie in the header to // verify them. - self.oracle.write(&HintType::L1Receipts.encode_with(&[hash.as_ref()])).await?; - let trie_walker = OrderedListWalker::try_new_hydrated(header.receipts_root, self)?; + self.oracle + .write(&HintType::L1Receipts.encode_with(&[hash.as_ref()])) + .await + .map_err(OracleProviderError::Preimage)?; + let trie_walker = OrderedListWalker::try_new_hydrated(header.receipts_root, self) + .map_err(OracleProviderError::TrieWalker)?; // Decode the receipts within the transactions trie. let receipts = trie_walker .into_iter() .map(|(_, rlp)| { - let envelope = ReceiptEnvelope::decode_2718(&mut rlp.as_ref()) - .map_err(|e| anyhow!("Failed to decode ReceiptEnvelope RLP: {e}"))?; + let envelope = ReceiptEnvelope::decode_2718(&mut rlp.as_ref())?; Ok(envelope.as_receipt().expect("Infallible").clone()) }) - .collect::>>()?; + .collect::, _>>() + .map_err(OracleProviderError::Rlp)?; Ok(receipts) } @@ -93,7 +102,7 @@ impl ChainProvider for OracleL1ChainProvider { async fn block_info_and_transactions_by_hash( &mut self, hash: B256, - ) -> Result<(BlockInfo, Vec)> { + ) -> Result<(BlockInfo, Vec), Self::Error> { // Fetch the block header to construct the block info. let header = self.header_by_hash(hash).await?; let block_info = BlockInfo { @@ -105,26 +114,32 @@ impl ChainProvider for OracleL1ChainProvider { // Send a hint for the block's transactions, and walk through the transactions trie in the // header to verify them. - self.oracle.write(&HintType::L1Transactions.encode_with(&[hash.as_ref()])).await?; - let trie_walker = OrderedListWalker::try_new_hydrated(header.transactions_root, self)?; + self.oracle + .write(&HintType::L1Transactions.encode_with(&[hash.as_ref()])) + .await + .map_err(OracleProviderError::Preimage)?; + let trie_walker = OrderedListWalker::try_new_hydrated(header.transactions_root, self) + .map_err(OracleProviderError::TrieWalker)?; // Decode the transactions within the transactions trie. let transactions = trie_walker .into_iter() .map(|(_, rlp)| { - TxEnvelope::decode_2718(&mut rlp.as_ref()) - .map_err(|e| anyhow!("Failed to decode TxEnvelope RLP: {e}")) + // note: not short-handed for error type coersion w/ `?`. + let rlp = TxEnvelope::decode_2718(&mut rlp.as_ref())?; + Ok(rlp) }) - .collect::>>()?; + .collect::, _>>() + .map_err(OracleProviderError::Rlp)?; Ok((block_info, transactions)) } } impl TrieProvider for OracleL1ChainProvider { - type Error = anyhow::Error; + type Error = OracleProviderError; - fn trie_node_preimage(&self, key: B256) -> Result { + fn trie_node_preimage(&self, key: B256) -> Result { // On L1, trie node preimages are stored as keccak preimage types in the oracle. We assume // that a hint for these preimages has already been sent, prior to this call. kona_common::block_on(async move { @@ -132,15 +147,15 @@ impl TrieProvider for OracleL1ChainProvider { .get(PreimageKey::new(*key, PreimageKeyType::Keccak256)) .await .map(Into::into) - .map_err(Into::into) + .map_err(OracleProviderError::Preimage) }) } - fn bytecode_by_hash(&self, _: B256) -> Result { + fn bytecode_by_hash(&self, _: B256) -> Result { unimplemented!("TrieProvider::bytecode_by_hash unimplemented for OracleL1ChainProvider") } - fn header_by_hash(&self, _: B256) -> Result
{ + fn header_by_hash(&self, _: B256) -> Result { unimplemented!("TrieProvider::header_by_hash unimplemented for OracleL1ChainProvider") } } diff --git a/bin/client/src/l1/driver.rs b/bin/client/src/l1/driver.rs index a91049d81..e5a05343d 100644 --- a/bin/client/src/l1/driver.rs +++ b/bin/client/src/l1/driver.rs @@ -5,14 +5,15 @@ use super::OracleL1ChainProvider; use crate::{l2::OracleL2ChainProvider, BootInfo, FlushableCache, HintType}; -use alloc::{sync::Arc, vec::Vec}; -use alloy_consensus::{Header, Sealed}; +use alloc::{string::ToString, sync::Arc, vec::Vec}; +use alloy_consensus::{BlockBody, Header, Sealable, Sealed}; use alloy_primitives::B256; +use alloy_rlp::Decodable; use anyhow::{anyhow, Result}; use core::fmt::Debug; use kona_derive::{ attributes::StatefulAttributesBuilder, - errors::{PipelineErrorKind, ResetError}, + errors::{PipelineError, PipelineErrorKind, PipelineResult, ResetError}, pipeline::{DerivationPipeline, PipelineBuilder}, sources::EthereumDataSource, stages::{ @@ -27,7 +28,7 @@ use kona_derive::{ use kona_executor::{KonaHandleRegister, StatelessL2BlockExecutor}; use kona_mpt::{TrieHinter, TrieProvider}; use kona_preimage::{CommsClient, PreimageKey, PreimageKeyType}; -use op_alloy_consensus::OpTxType; +use op_alloy_consensus::{OpBlock, OpTxEnvelope, OpTxType}; use op_alloy_genesis::RollupConfig; use op_alloy_protocol::{BatchValidationProvider, BlockInfo, L2BlockInfo}; use op_alloy_rpc_types_engine::OpAttributesWithParent; @@ -80,6 +81,10 @@ where l2_safe_head: L2BlockInfo, /// The header of the L2 safe head. l2_safe_head_header: Sealed
, + /// The output root of the L2 safe head. + l2_safe_head_output_root: B256, + /// The target L2 block number. + target_block_number: u64, /// The inner pipeline. pipeline: OraclePipeline, /// The caching oracle. @@ -127,7 +132,7 @@ where let cfg = Arc::new(boot_info.rollup_config.clone()); // Fetch the startup information. - let (l1_origin, l2_safe_head, l2_safe_head_header) = Self::find_startup_info( + let (l1_origin, l2_safe_head, l2_safe_head_header) = Self::sync_start( caching_oracle, boot_info, &mut chain_provider, @@ -135,14 +140,6 @@ where ) .await?; - // Construct the pipeline. - let attributes = StatefulAttributesBuilder::new( - cfg.clone(), - l2_chain_provider.clone(), - chain_provider.clone(), - ); - let dap = EthereumDataSource::new(chain_provider.clone(), blob_provider, &cfg); - // Walk back the starting L1 block by `channel_timeout` to ensure that the full channel is // captured. let channel_timeout = @@ -153,6 +150,14 @@ where } let l1_origin = chain_provider.block_info_by_number(l1_origin_number).await?; + // Construct the pipeline. + let attributes = StatefulAttributesBuilder::new( + cfg.clone(), + l2_chain_provider.clone(), + chain_provider.clone(), + ); + let dap = EthereumDataSource::new(chain_provider.clone(), blob_provider, &cfg); + let pipeline = PipelineBuilder::new() .rollup_config(cfg) .dap_source(dap) @@ -165,12 +170,14 @@ where Ok(Self { l2_safe_head, l2_safe_head_header, + l2_safe_head_output_root: boot_info.agreed_l2_output_root, + target_block_number: boot_info.claimed_l2_block_number, pipeline, caching_oracle: caching_oracle.clone(), }) } - /// Produces the output root of the next L2 block. + /// Advances the derivation pipeline to the target block number. /// /// ## Takes /// - `cfg`: The rollup configuration. @@ -182,7 +189,7 @@ where /// - `Ok((number, output_root))` - A tuple containing the number of the produced block and the /// output root. /// - `Err(e)` - An error if the block could not be produced. - pub async fn produce_output( + pub async fn advance_to_target( &mut self, cfg: &RollupConfig, provider: &P, @@ -194,11 +201,31 @@ where H: TrieHinter + Send + Sync + Clone, { loop { - let OpAttributesWithParent { mut attributes, .. } = self.produce_payload().await?; + // Check if we have reached the target block number. + if self.l2_safe_head().block_info.number >= self.target_block_number { + info!(target: "client", "Derivation complete, reached L2 safe head."); + return Ok((self.l2_safe_head.block_info.number, self.l2_safe_head_output_root)); + } + + let OpAttributesWithParent { mut attributes, .. } = match self.produce_payload().await { + Ok(attrs) => attrs, + Err(PipelineErrorKind::Critical(PipelineError::EndOfSource)) => { + warn!(target: "client", "Exhausted data source; Halting derivation and using current safe head."); + + // Adjust the target block number to the current safe head, as no more blocks + // can be produced. + self.target_block_number = self.l2_safe_head.block_info.number; + continue; + } + Err(e) => { + error!(target: "client", "Failed to produce payload: {:?}", e); + return Err(e.into()); + } + }; let mut executor = self.new_executor(cfg, provider, hinter, handle_register); - let number = match executor.execute_payload(attributes.clone()) { - Ok(Header { number, .. }) => *number, + let header = match executor.execute_payload(attributes.clone()) { + Ok(header) => header, Err(e) => { error!(target: "client", "Failed to execute L2 block: {}", e); @@ -221,8 +248,8 @@ where // Retry the execution. executor = self.new_executor(cfg, provider, hinter, handle_register); - match executor.execute_payload(attributes) { - Ok(Header { number, .. }) => *number, + match executor.execute_payload(attributes.clone()) { + Ok(header) => header, Err(e) => { error!( target: "client", @@ -232,19 +259,41 @@ where } } } else { + // Pre-Holocene, discard the block if execution fails. continue; } } }; - let output_root = executor.compute_output_root()?; - return Ok((number, output_root)); + // Construct the block. + let block = OpBlock { + header: header.clone(), + body: BlockBody { + transactions: attributes + .transactions + .unwrap_or_default() + .into_iter() + .map(|tx| { + OpTxEnvelope::decode(&mut tx.as_ref()) + .map_err(|e| anyhow!("Failed to decode transaction: {}", e)) + }) + .collect::>>()?, + ommers: Vec::new(), + withdrawals: None, + }, + }; + + // Update the safe head. + self.l2_safe_head = + L2BlockInfo::from_block_and_genesis(&block, &self.pipeline.rollup_config.genesis)?; + self.l2_safe_head_header = header.clone().seal_slow(); + self.l2_safe_head_output_root = executor.compute_output_root()?; } } /// Produces the disputed [OpAttributesWithParent] payload, directly after the starting L2 /// output root passed through the [BootInfo]. - async fn produce_payload(&mut self) -> Result { + async fn produce_payload(&mut self) -> PipelineResult { // As we start the safe head at the disputed block's parent, we step the pipeline until the // first attributes are produced. All batches at and before the safe head will be // dropped, so the first payload will always be the disputed one. @@ -263,7 +312,7 @@ where // complete the current step. In this case, we retry the step to see if other // stages can make progress. match e { - PipelineErrorKind::Temporary(_) => { /* continue */ } + PipelineErrorKind::Temporary(_) => continue, PipelineErrorKind::Reset(e) => { let system_config = self .pipeline @@ -272,7 +321,9 @@ where self.l2_safe_head.block_info.number, self.pipeline.rollup_config.clone(), ) - .await?; + .await + .map_err(|e| PipelineError::Provider(e.to_string()).temp())?; + if matches!(e, ResetError::HoloceneActivation) { self.pipeline .signal( @@ -281,16 +332,18 @@ where l1_origin: self .pipeline .origin() - .ok_or_else(|| anyhow!("Missing L1 origin"))?, + .ok_or(PipelineError::MissingOrigin.crit())?, system_config: Some(system_config), } .signal(), ) .await?; } else { + // Flush the caching oracle if a reorg is detected. if matches!(e, ResetError::ReorgDetected(_, _)) { self.caching_oracle.as_ref().flush(); } + // Reset the pipeline to the initial L2 safe head and L1 origin, // and try again. self.pipeline @@ -300,7 +353,7 @@ where l1_origin: self .pipeline .origin() - .ok_or_else(|| anyhow!("Missing L1 origin"))?, + .ok_or(PipelineError::MissingOrigin.crit())?, system_config: Some(system_config), } .signal(), @@ -308,7 +361,7 @@ where .await?; } } - PipelineErrorKind::Critical(_) => return Err(e.into()), + PipelineErrorKind::Critical(_) => return Err(e), } } } @@ -329,7 +382,7 @@ where /// /// ## Returns /// - A tuple containing the L1 origin block information and the L2 safe head information. - async fn find_startup_info( + async fn sync_start( caching_oracle: &O, boot_info: &BootInfo, chain_provider: &mut OracleL1ChainProvider, diff --git a/bin/client/src/l2/chain_provider.rs b/bin/client/src/l2/chain_provider.rs index 2a7a1e5c4..dc39d3b5a 100644 --- a/bin/client/src/l2/chain_provider.rs +++ b/bin/client/src/l2/chain_provider.rs @@ -1,12 +1,11 @@ //! Contains the concrete implementation of the [L2ChainProvider] trait for the client program. -use crate::{BootInfo, HintType}; +use crate::{errors::OracleProviderError, BootInfo, HintType}; use alloc::{boxed::Box, sync::Arc, vec::Vec}; use alloy_consensus::{BlockBody, Header}; use alloy_eips::eip2718::Decodable2718; use alloy_primitives::{Address, Bytes, B256}; use alloy_rlp::Decodable; -use anyhow::{anyhow, Result}; use async_trait::async_trait; use kona_derive::traits::L2ChainProvider; use kona_mpt::{OrderedListWalker, TrieHinter, TrieProvider}; @@ -34,31 +33,32 @@ impl OracleL2ChainProvider { impl OracleL2ChainProvider { /// Returns a [Header] corresponding to the given L2 block number, by walking back from the /// L2 safe head. - async fn header_by_number(&mut self, block_number: u64) -> Result
{ + async fn header_by_number(&mut self, block_number: u64) -> Result { // Fetch the starting L2 output preimage. self.oracle .write( &HintType::StartingL2Output .encode_with(&[self.boot_info.agreed_l2_output_root.as_ref()]), ) - .await?; + .await + .map_err(OracleProviderError::Preimage)?; let output_preimage = self .oracle .get(PreimageKey::new( *self.boot_info.agreed_l2_output_root, PreimageKeyType::Keccak256, )) - .await?; + .await + .map_err(OracleProviderError::Preimage)?; // Fetch the starting block header. - let block_hash = output_preimage[96..128] - .try_into() - .map_err(|e| anyhow!("Failed to extract block hash from output preimage: {e}"))?; + let block_hash = + output_preimage[96..128].try_into().map_err(OracleProviderError::SliceConversion)?; let mut header = self.header_by_hash(block_hash)?; // Check if the block number is in range. If not, we can fail early. if block_number > header.number { - anyhow::bail!("Block number past L1 head."); + return Err(OracleProviderError::BlockNumberPastHead(block_number, header.number)); } // Walk back the block headers to the desired block number. @@ -72,35 +72,40 @@ impl OracleL2ChainProvider { #[async_trait] impl BatchValidationProvider for OracleL2ChainProvider { - type Error = anyhow::Error; + type Error = OracleProviderError; - async fn l2_block_info_by_number(&mut self, number: u64) -> Result { + async fn l2_block_info_by_number(&mut self, number: u64) -> Result { // Get the block at the given number. let block = self.block_by_number(number).await?; // Construct the system config from the payload. L2BlockInfo::from_block_and_genesis(&block, &self.boot_info.rollup_config.genesis) - .map_err(Into::into) + .map_err(OracleProviderError::BlockInfo) } - async fn block_by_number(&mut self, number: u64) -> Result { + async fn block_by_number(&mut self, number: u64) -> Result { // Fetch the header for the given block number. let header @ Header { transactions_root, timestamp, .. } = self.header_by_number(number).await?; let header_hash = header.hash_slow(); // Fetch the transactions in the block. - self.oracle.write(&HintType::L2Transactions.encode_with(&[header_hash.as_ref()])).await?; - let trie_walker = OrderedListWalker::try_new_hydrated(transactions_root, self)?; + self.oracle + .write(&HintType::L2Transactions.encode_with(&[header_hash.as_ref()])) + .await + .map_err(OracleProviderError::Preimage)?; + let trie_walker = OrderedListWalker::try_new_hydrated(transactions_root, self) + .map_err(OracleProviderError::TrieWalker)?; // Decode the transactions within the transactions trie. let transactions = trie_walker .into_iter() .map(|(_, rlp)| { - OpTxEnvelope::decode_2718(&mut rlp.as_ref()) - .map_err(|e| anyhow!("Failed to decode TxEnvelope RLP: {e}")) + let res = OpTxEnvelope::decode_2718(&mut rlp.as_ref())?; + Ok(res) }) - .collect::>>()?; + .collect::, _>>() + .map_err(OracleProviderError::Rlp)?; let optimism_block = OpBlock { header, @@ -120,23 +125,26 @@ impl BatchValidationProvider for OracleL2ChainProv #[async_trait] impl L2ChainProvider for OracleL2ChainProvider { + type Error = OracleProviderError; + async fn system_config_by_number( &mut self, number: u64, rollup_config: Arc, - ) -> Result { + ) -> Result::Error> { // Get the block at the given number. let block = self.block_by_number(number).await?; // Construct the system config from the payload. - to_system_config(&block, rollup_config.as_ref()).map_err(Into::into) + to_system_config(&block, rollup_config.as_ref()) + .map_err(OracleProviderError::OpBlockConversion) } } impl TrieProvider for OracleL2ChainProvider { - type Error = anyhow::Error; + type Error = OracleProviderError; - fn trie_node_preimage(&self, key: B256) -> Result { + fn trie_node_preimage(&self, key: B256) -> Result { // On L2, trie node preimages are stored as keccak preimage types in the oracle. We assume // that a hint for these preimages has already been sent, prior to this call. kona_common::block_on(async move { @@ -144,49 +152,57 @@ impl TrieProvider for OracleL2ChainProvider { .get(PreimageKey::new(*key, PreimageKeyType::Keccak256)) .await .map(Into::into) - .map_err(Into::into) + .map_err(OracleProviderError::Preimage) }) } - fn bytecode_by_hash(&self, hash: B256) -> Result { + fn bytecode_by_hash(&self, hash: B256) -> Result { // Fetch the bytecode preimage from the caching oracle. kona_common::block_on(async move { - self.oracle.write(&HintType::L2Code.encode_with(&[hash.as_ref()])).await?; + self.oracle + .write(&HintType::L2Code.encode_with(&[hash.as_ref()])) + .await + .map_err(OracleProviderError::Preimage)?; self.oracle .get(PreimageKey::new(*hash, PreimageKeyType::Keccak256)) .await .map(Into::into) - .map_err(Into::into) + .map_err(OracleProviderError::Preimage) }) } - fn header_by_hash(&self, hash: B256) -> Result
{ + fn header_by_hash(&self, hash: B256) -> Result { // Fetch the header from the caching oracle. kona_common::block_on(async move { - self.oracle.write(&HintType::L2BlockHeader.encode_with(&[hash.as_ref()])).await?; + self.oracle + .write(&HintType::L2BlockHeader.encode_with(&[hash.as_ref()])) + .await + .map_err(OracleProviderError::Preimage)?; - let header_bytes = - self.oracle.get(PreimageKey::new(*hash, PreimageKeyType::Keccak256)).await?; - Header::decode(&mut header_bytes.as_slice()) - .map_err(|e| anyhow!("Failed to RLP decode Header: {e}")) + let header_bytes = self + .oracle + .get(PreimageKey::new(*hash, PreimageKeyType::Keccak256)) + .await + .map_err(OracleProviderError::Preimage)?; + Header::decode(&mut header_bytes.as_slice()).map_err(OracleProviderError::Rlp) }) } } impl TrieHinter for OracleL2ChainProvider { - type Error = anyhow::Error; + type Error = OracleProviderError; - fn hint_trie_node(&self, hash: B256) -> Result<()> { + fn hint_trie_node(&self, hash: B256) -> Result<(), Self::Error> { kona_common::block_on(async move { self.oracle .write(&HintType::L2StateNode.encode_with(&[hash.as_slice()])) .await - .map_err(Into::into) + .map_err(OracleProviderError::Preimage) }) } - fn hint_account_proof(&self, address: Address, block_number: u64) -> Result<()> { + fn hint_account_proof(&self, address: Address, block_number: u64) -> Result<(), Self::Error> { kona_common::block_on(async move { self.oracle .write( @@ -194,7 +210,7 @@ impl TrieHinter for OracleL2ChainProvider { .encode_with(&[block_number.to_be_bytes().as_ref(), address.as_slice()]), ) .await - .map_err(Into::into) + .map_err(OracleProviderError::Preimage) }) } @@ -203,7 +219,7 @@ impl TrieHinter for OracleL2ChainProvider { address: alloy_primitives::Address, slot: alloy_primitives::U256, block_number: u64, - ) -> Result<()> { + ) -> Result<(), Self::Error> { kona_common::block_on(async move { self.oracle .write(&HintType::L2AccountStorageProof.encode_with(&[ @@ -212,7 +228,7 @@ impl TrieHinter for OracleL2ChainProvider { slot.to_be_bytes::<32>().as_ref(), ])) .await - .map_err(Into::into) + .map_err(OracleProviderError::Preimage) }) } } diff --git a/bin/client/src/lib.rs b/bin/client/src/lib.rs index d327a3546..6429a2da7 100644 --- a/bin/client/src/lib.rs +++ b/bin/client/src/lib.rs @@ -10,6 +10,8 @@ pub mod l1; pub mod l2; +pub mod errors; + mod hint; pub use hint::HintType; diff --git a/crates/derive-alloy/Cargo.toml b/crates/derive-alloy/Cargo.toml index 92c4bbc99..30121f0b8 100644 --- a/crates/derive-alloy/Cargo.toml +++ b/crates/derive-alloy/Cargo.toml @@ -33,12 +33,12 @@ reqwest.workspace = true tracing.workspace = true alloy-serde.workspace = true async-trait.workspace = true +derive_more = { workspace = true, features = ["full"] } # Workspace kona-derive = { workspace = true, features = ["serde"] } # `test-utils` feature dependencies -derive_more = { workspace = true, features = ["full"], optional = true } alloy-rpc-client = { workspace = true, optional = true } alloy-node-bindings = { workspace = true, optional = true } alloy-transport-http = { workspace = true, optional = true, features = ["reqwest"] } @@ -55,7 +55,6 @@ kona-derive = { workspace = true, features = ["serde", "test-utils"] } [features] default = [] test-utils = [ - "dep:derive_more", "dep:alloy-rpc-client", "dep:alloy-node-bindings", "dep:alloy-transport-http", diff --git a/crates/derive-alloy/src/alloy_providers.rs b/crates/derive-alloy/src/alloy_providers.rs index da1601c16..73bb7bf49 100644 --- a/crates/derive-alloy/src/alloy_providers.rs +++ b/crates/derive-alloy/src/alloy_providers.rs @@ -1,5 +1,6 @@ //! Providers that use alloy provider types on the backend. +use crate::AlloyProviderError; use alloy_consensus::{Block, Header, Receipt, ReceiptWithBloom, TxEnvelope, TxType}; use alloy_primitives::{Bytes, B256, U64}; use alloy_provider::{Provider, ReqwestProvider}; @@ -68,21 +69,22 @@ impl AlloyChainProvider { #[async_trait] impl ChainProvider for AlloyChainProvider { - type Error = RpcError; + type Error = AlloyProviderError; async fn header_by_hash(&mut self, hash: B256) -> Result { if let Some(header) = self.header_by_hash_cache.get(&hash) { return Ok(header.clone()); } - let raw_header: Bytes = self.inner.raw_request("debug_getRawHeader".into(), [hash]).await?; - match Header::decode(&mut raw_header.as_ref()) { - Ok(header) => { - self.header_by_hash_cache.put(hash, header.clone()); - Ok(header) - } - Err(e) => Err(RpcError::LocalUsageError(Box::new(e))), - } + let raw_header: Bytes = self + .inner + .raw_request("debug_getRawHeader".into(), [hash]) + .await + .map_err(AlloyProviderError::Rpc)?; + let header = Header::decode(&mut raw_header.as_ref()).map_err(AlloyProviderError::Rlp)?; + + self.header_by_hash_cache.put(hash, header.clone()); + Ok(header) } async fn block_info_by_number(&mut self, number: u64) -> Result { @@ -90,10 +92,12 @@ impl ChainProvider for AlloyChainProvider { return Ok(*block_info); } - let raw_header: Bytes = - self.inner.raw_request("debug_getRawHeader".into(), [U64::from(number)]).await?; - let header = Header::decode(&mut raw_header.as_ref()) - .map_err(|e| RpcError::LocalUsageError(Box::new(e)))?; + let raw_header: Bytes = self + .inner + .raw_request("debug_getRawHeader".into(), [U64::from(number)]) + .await + .map_err(AlloyProviderError::Rpc)?; + let header = Header::decode(&mut raw_header.as_ref()).map_err(AlloyProviderError::Rlp)?; let block_info = BlockInfo { hash: header.hash_slow(), @@ -110,8 +114,11 @@ impl ChainProvider for AlloyChainProvider { return Ok(receipts.clone()); } - let raw_receipts: Vec = - self.inner.raw_request("debug_getRawReceipts".into(), [hash]).await?; + let raw_receipts: Vec = self + .inner + .raw_request("debug_getRawReceipts".into(), [hash]) + .await + .map_err(AlloyProviderError::Rpc)?; let receipts = raw_receipts .iter() .map(|r| { @@ -122,9 +129,7 @@ impl ChainProvider for AlloyChainProvider { r.advance(1); } - Ok(ReceiptWithBloom::decode(r) - .map_err(|e| RpcError::LocalUsageError(Box::new(e)))? - .receipt) + Ok(ReceiptWithBloom::decode(r).map_err(AlloyProviderError::Rlp)?.receipt) }) .collect::, Self::Error>>()?; self.receipts_by_hash_cache.put(hash, receipts.clone()); @@ -140,9 +145,12 @@ impl ChainProvider for AlloyChainProvider { return Ok(block_info_and_txs.clone()); } - let raw_block: Bytes = self.inner.raw_request("debug_getRawBlock".into(), [hash]).await?; - let block = Block::decode(&mut raw_block.as_ref()) - .map_err(|e| RpcError::LocalUsageError(Box::new(e)))?; + let raw_block: Bytes = self + .inner + .raw_request("debug_getRawBlock".into(), [hash]) + .await + .map_err(AlloyProviderError::Rpc)?; + let block = Block::decode(&mut raw_block.as_ref()).map_err(AlloyProviderError::Rlp)?; let block_info = BlockInfo { hash: block.header.hash_slow(), @@ -207,7 +215,7 @@ impl AlloyL2ChainProvider { #[async_trait] impl BatchValidationProvider for AlloyL2ChainProvider { - type Error = RpcError; + type Error = AlloyProviderError; async fn l2_block_info_by_number(&mut self, number: u64) -> Result { if let Some(l2_block_info) = self.l2_block_info_by_number_cache.get(&number) { @@ -217,7 +225,7 @@ impl BatchValidationProvider for AlloyL2ChainProvider { let block = self.block_by_number(number).await?; let l2_block_info = L2BlockInfo::from_block_and_genesis(&block, &self.rollup_config.genesis) - .map_err(|e| RpcError::LocalUsageError(Box::new(e)))?; + .map_err(AlloyProviderError::BlockInfo)?; self.l2_block_info_by_number_cache.put(number, l2_block_info); Ok(l2_block_info) } @@ -227,10 +235,12 @@ impl BatchValidationProvider for AlloyL2ChainProvider { return Ok(block.clone()); } - let raw_block: Bytes = - self.inner.raw_request("debug_getRawBlock".into(), [U64::from(number)]).await?; - let block = OpBlock::decode(&mut raw_block.as_ref()) - .map_err(|e| RpcError::LocalUsageError(Box::new(e)))?; + let raw_block: Bytes = self + .inner + .raw_request("debug_getRawBlock".into(), [U64::from(number)]) + .await + .map_err(AlloyProviderError::Rpc)?; + let block = OpBlock::decode(&mut raw_block.as_ref()).map_err(AlloyProviderError::Rlp)?; self.block_by_number_cache.put(number, block.clone()); Ok(block) } @@ -238,18 +248,20 @@ impl BatchValidationProvider for AlloyL2ChainProvider { #[async_trait] impl L2ChainProvider for AlloyL2ChainProvider { + type Error = AlloyProviderError; + async fn system_config_by_number( &mut self, number: u64, rollup_config: Arc, - ) -> Result { + ) -> Result::Error> { if let Some(system_config) = self.system_config_by_number_cache.get(&number) { return Ok(*system_config); } let block = self.block_by_number(number).await?; let sys_config = to_system_config(&block, &rollup_config) - .map_err(|e| RpcError::LocalUsageError(Box::new(e)))?; + .map_err(AlloyProviderError::OpBlockConversion)?; self.system_config_by_number_cache.put(number, sys_config); Ok(sys_config) } diff --git a/crates/derive-alloy/src/errors.rs b/crates/derive-alloy/src/errors.rs new file mode 100644 index 000000000..5c433c776 --- /dev/null +++ b/crates/derive-alloy/src/errors.rs @@ -0,0 +1,36 @@ +//! Errors for the alloy-backed derivation providers. + +use alloy_transport::{RpcError, TransportErrorKind}; +use derive_more::{Display, Error}; +use kona_derive::errors::{PipelineError, PipelineErrorKind}; +use op_alloy_protocol::{FromBlockError, OpBlockConversionError}; + +/// Error from an alloy-backed provider. +#[derive(Error, Display, Debug)] +pub enum AlloyProviderError { + /// An [RpcError] occurred. + #[display("RPC Error: {_0}")] + Rpc(RpcError), + /// A [alloy_rlp::Error] occurred. + #[display("RLP Error: {_0}")] + Rlp(alloy_rlp::Error), + /// BlockInfo error. + #[display("From block error: {_0}")] + BlockInfo(FromBlockError), + /// Op Block conversion error. + #[display("Op block conversion error: {_0}")] + OpBlockConversion(OpBlockConversionError), +} + +impl From for PipelineErrorKind { + fn from(val: AlloyProviderError) -> Self { + match val { + AlloyProviderError::Rlp(e) => PipelineError::Provider(e.to_string()).crit(), + AlloyProviderError::BlockInfo(e) => PipelineError::Provider(e.to_string()).crit(), + AlloyProviderError::OpBlockConversion(e) => { + PipelineError::Provider(e.to_string()).crit() + } + AlloyProviderError::Rpc(e) => PipelineError::Provider(e.to_string()).temp(), + } + } +} diff --git a/crates/derive-alloy/src/lib.rs b/crates/derive-alloy/src/lib.rs index c107e8132..57649f566 100644 --- a/crates/derive-alloy/src/lib.rs +++ b/crates/derive-alloy/src/lib.rs @@ -35,5 +35,8 @@ pub use blob_provider::{ OnlineBlobProviderWithFallback, }; +pub mod errors; +pub use errors::AlloyProviderError; + #[cfg(any(test, feature = "test-utils"))] pub mod test_utils; diff --git a/crates/derive/src/attributes/stateful.rs b/crates/derive/src/attributes/stateful.rs index f61e04133..37ccc9dff 100644 --- a/crates/derive/src/attributes/stateful.rs +++ b/crates/derive/src/attributes/stateful.rs @@ -65,17 +65,14 @@ where .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())?; + .map_err(Into::into)?; // 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())?; + let header = + self.receipts_fetcher.header_by_hash(epoch.hash).await.map_err(Into::into)?; if l2_parent.l1_origin.hash != header.parent_hash { return Err(PipelineErrorKind::Reset( BuilderError::BlockMismatchEpochReset( @@ -86,11 +83,8 @@ where .into(), )); } - let receipts = self - .receipts_fetcher - .receipts_by_hash(epoch.hash) - .await - .map_err(|e| PipelineError::Provider(e.to_string()).temp())?; + let receipts = + self.receipts_fetcher.receipts_by_hash(epoch.hash).await.map_err(Into::into)?; let deposits = derive_deposits(epoch.hash, &receipts, self.rollup_cfg.deposit_contract_address) .await @@ -113,11 +107,8 @@ where )); } - let header = self - .receipts_fetcher - .header_by_hash(epoch.hash) - .await - .map_err(|e| PipelineError::Provider(e.to_string()).temp())?; + let header = + self.receipts_fetcher.header_by_hash(epoch.hash).await.map_err(Into::into)?; l1_header = header; deposit_transactions = vec![]; l2_parent.seq_num + 1 diff --git a/crates/derive/src/errors/pipeline.rs b/crates/derive/src/errors/pipeline.rs index 4af333a74..ee808e87a 100644 --- a/crates/derive/src/errors/pipeline.rs +++ b/crates/derive/src/errors/pipeline.rs @@ -110,6 +110,9 @@ pub enum PipelineError { /// [PipelineEncodingError] variant. #[display("Decode error: {_0}")] BadEncoding(PipelineEncodingError), + /// The data source can no longer provide any more data. + #[display("Data source exhausted")] + EndOfSource, /// Provider error variant. #[display("Blob provider error: {_0}")] Provider(String), diff --git a/crates/derive/src/pipeline/core.rs b/crates/derive/src/pipeline/core.rs index 79e4de598..2559a0dbf 100644 --- a/crates/derive/src/pipeline/core.rs +++ b/crates/derive/src/pipeline/core.rs @@ -7,7 +7,7 @@ use crate::{ Pipeline, ResetSignal, Signal, SignalReceiver, StepResult, }, }; -use alloc::{boxed::Box, collections::VecDeque, string::ToString, sync::Arc}; +use alloc::{boxed::Box, collections::VecDeque, sync::Arc}; use async_trait::async_trait; use core::fmt::Debug; use op_alloy_genesis::RollupConfig; @@ -101,7 +101,7 @@ where Arc::clone(&self.rollup_config), ) .await - .map_err(|e| PipelineError::Provider(e.to_string()).temp())?; + .map_err(Into::into)?; s = s.with_system_config(system_config); match self.attributes.signal(s).await { Ok(()) => trace!(target: "pipeline", "Stages reset"), diff --git a/crates/derive/src/sources/calldata.rs b/crates/derive/src/sources/calldata.rs index 71f442f38..d3adbc50c 100644 --- a/crates/derive/src/sources/calldata.rs +++ b/crates/derive/src/sources/calldata.rs @@ -4,7 +4,7 @@ use crate::{ errors::{PipelineError, PipelineResult}, traits::{AsyncIterator, ChainProvider}, }; -use alloc::{boxed::Box, collections::VecDeque, format}; +use alloc::{boxed::Box, collections::VecDeque}; use alloy_consensus::{Transaction, TxEnvelope}; use alloy_primitives::{Address, Bytes}; use async_trait::async_trait; @@ -89,13 +89,7 @@ impl AsyncIterator for CalldataSource { type Item = Bytes; async fn next(&mut self) -> PipelineResult { - if self.load_calldata().await.is_err() { - return Err(PipelineError::Provider(format!( - "Failed to load calldata for block {}", - self.block_ref.hash - )) - .temp()); - } + self.load_calldata().await.map_err(Into::into)?; self.calldata.pop_front().ok_or(PipelineError::Eof.temp()) } } diff --git a/crates/derive/src/stages/l1_traversal.rs b/crates/derive/src/stages/l1_traversal.rs index f43e3cbb9..ac8a97745 100644 --- a/crates/derive/src/stages/l1_traversal.rs +++ b/crates/derive/src/stages/l1_traversal.rs @@ -8,7 +8,7 @@ use crate::{ SignalReceiver, }, }; -use alloc::{boxed::Box, string::ToString, sync::Arc}; +use alloc::{boxed::Box, sync::Arc}; use alloy_primitives::Address; use async_trait::async_trait; use op_alloy_genesis::{RollupConfig, SystemConfig}; @@ -80,10 +80,8 @@ impl OriginAdvancer for L1Traversal { return Err(PipelineError::Eof.temp()); } }; - let next_l1_origin = match self.data_source.block_info_by_number(block.number + 1).await { - Ok(block) => block, - Err(e) => return Err(PipelineError::Provider(e.to_string()).temp()), - }; + let next_l1_origin = + self.data_source.block_info_by_number(block.number + 1).await.map_err(Into::into)?; // Check block hashes for reorgs. if block.hash != next_l1_origin.parent_hash { @@ -91,10 +89,8 @@ impl OriginAdvancer for L1Traversal { } // Fetch receipts for the next l1 block and update the system config. - let receipts = match self.data_source.receipts_by_hash(next_l1_origin.hash).await { - Ok(receipts) => receipts, - Err(e) => return Err(PipelineError::Provider(e.to_string()).temp()), - }; + let receipts = + self.data_source.receipts_by_hash(next_l1_origin.hash).await.map_err(Into::into)?; if let Err(e) = self.system_config.update_with_receipts( receipts.as_slice(), diff --git a/crates/derive/src/test_utils/chain_providers.rs b/crates/derive/src/test_utils/chain_providers.rs index 899873598..6dd6c0707 100644 --- a/crates/derive/src/test_utils/chain_providers.rs +++ b/crates/derive/src/test_utils/chain_providers.rs @@ -1,7 +1,10 @@ //! Test Utilities for chain provider traits -use crate::traits::{ChainProvider, L2ChainProvider}; -use alloc::{boxed::Box, sync::Arc, vec::Vec}; +use crate::{ + errors::{PipelineError, PipelineErrorKind}, + traits::{ChainProvider, L2ChainProvider}, +}; +use alloc::{boxed::Box, string::ToString, sync::Arc, vec::Vec}; use alloy_consensus::{Header, Receipt, TxEnvelope}; use alloy_primitives::{map::HashMap, B256}; use async_trait::async_trait; @@ -92,6 +95,12 @@ pub enum TestProviderError { SystemConfigNotFound(u64), } +impl From for PipelineErrorKind { + fn from(val: TestProviderError) -> Self { + PipelineError::Provider(val.to_string()).temp() + } +} + impl core::error::Error for TestProviderError {} #[async_trait] @@ -192,11 +201,13 @@ impl BatchValidationProvider for TestL2ChainProvider { #[async_trait] impl L2ChainProvider for TestL2ChainProvider { + type Error = TestProviderError; + async fn system_config_by_number( &mut self, number: u64, _: Arc, - ) -> Result { + ) -> Result::Error> { self.system_configs .get(&number) .ok_or_else(|| TestProviderError::SystemConfigNotFound(number)) diff --git a/crates/derive/src/test_utils/sys_config_fetcher.rs b/crates/derive/src/test_utils/sys_config_fetcher.rs index d71768c2d..960f59c97 100644 --- a/crates/derive/src/test_utils/sys_config_fetcher.rs +++ b/crates/derive/src/test_utils/sys_config_fetcher.rs @@ -1,7 +1,10 @@ //! Implements a mock [L2SystemConfigFetcher] for testing. -use crate::traits::L2ChainProvider; -use alloc::{boxed::Box, sync::Arc}; +use crate::{ + errors::{PipelineError, PipelineErrorKind}, + traits::L2ChainProvider, +}; +use alloc::{boxed::Box, string::ToString, sync::Arc}; use alloy_primitives::map::HashMap; use async_trait::async_trait; use op_alloy_consensus::OpBlock; @@ -35,6 +38,12 @@ pub enum TestSystemConfigL2FetcherError { NotFound(u64), } +impl From for PipelineErrorKind { + fn from(val: TestSystemConfigL2FetcherError) -> Self { + PipelineError::Provider(val.to_string()).temp() + } +} + impl core::error::Error for TestSystemConfigL2FetcherError {} #[async_trait] @@ -52,11 +61,13 @@ impl BatchValidationProvider for TestSystemConfigL2Fetcher { #[async_trait] impl L2ChainProvider for TestSystemConfigL2Fetcher { + type Error = TestSystemConfigL2FetcherError; + async fn system_config_by_number( &mut self, number: u64, _: Arc, - ) -> Result { + ) -> Result::Error> { self.system_configs .get(&number) .cloned() diff --git a/crates/derive/src/traits/mod.rs b/crates/derive/src/traits/mod.rs index 170d6325b..5fcb7ee17 100644 --- a/crates/derive/src/traits/mod.rs +++ b/crates/derive/src/traits/mod.rs @@ -5,7 +5,7 @@ mod pipeline; pub use pipeline::{ActivationSignal, Pipeline, ResetSignal, Signal, StepResult}; mod providers; -pub use providers::{ChainProvider, L2ChainProvider}; +pub use providers::{BatchValidationProviderDerive, ChainProvider, L2ChainProvider}; mod attributes; pub use attributes::{AttributesBuilder, AttributesProvider, NextAttributes}; diff --git a/crates/derive/src/traits/providers.rs b/crates/derive/src/traits/providers.rs index 6d845ba69..fad1410d9 100644 --- a/crates/derive/src/traits/providers.rs +++ b/crates/derive/src/traits/providers.rs @@ -1,5 +1,6 @@ //! Chain providers for the derivation pipeline. +use crate::errors::PipelineErrorKind; use alloc::{boxed::Box, string::ToString, sync::Arc, vec::Vec}; use alloy_consensus::{Header, Receipt, TxEnvelope}; use alloy_primitives::B256; @@ -12,7 +13,7 @@ use op_alloy_protocol::{BatchValidationProvider, BlockInfo}; #[async_trait] pub trait ChainProvider { /// The error type for the [ChainProvider]. - type Error: Display + ToString; + type Error: Display + ToString + Into; /// Fetch the L1 [Header] for the given [B256] hash. async fn header_by_hash(&mut self, hash: B256) -> Result; @@ -34,11 +35,27 @@ pub trait ChainProvider { /// Describes the functionality of a data source that fetches safe blocks. #[async_trait] -pub trait L2ChainProvider: BatchValidationProvider { +pub trait L2ChainProvider: BatchValidationProviderDerive { + /// The error type for the [L2ChainProvider]. + type Error: Display + ToString + Into; + /// Returns the [SystemConfig] by L2 number. async fn system_config_by_number( &mut self, number: u64, rollup_config: Arc, - ) -> Result; + ) -> Result::Error>; +} + +/// A super-trait for [BatchValidationProvider] that binds `Self::Error` to have a conversion into +/// [PipelineErrorKind]. +pub trait BatchValidationProviderDerive: BatchValidationProvider {} + +// Auto-implement the [BatchValidationProviderDerive] trait for all types that implement +// [BatchValidationProvider] where the error can be converted into [PipelineErrorKind]. +impl BatchValidationProviderDerive for T +where + T: BatchValidationProvider, + ::Error: Into, +{ }