reth_engine_tree/tree/
payload_validator.rs

1//! Types and traits for validating blocks and payloads.
2
3use crate::tree::{
4    cached_state::CachedStateProvider,
5    error::{InsertBlockError, InsertBlockErrorKind, InsertPayloadError},
6    instrumented_state::InstrumentedStateProvider,
7    payload_processor::{executor::WorkloadExecutor, PayloadProcessor},
8    precompile_cache::{CachedPrecompile, CachedPrecompileMetrics, PrecompileCacheMap},
9    sparse_trie::StateRootComputeOutcome,
10    EngineApiMetrics, EngineApiTreeState, ExecutionEnv, PayloadHandle, StateProviderBuilder,
11    StateProviderDatabase, TreeConfig,
12};
13use alloy_consensus::transaction::Either;
14use alloy_eip7928::BlockAccessList;
15use alloy_eips::{eip1898::BlockWithParent, NumHash};
16use alloy_evm::Evm;
17use alloy_primitives::B256;
18use reth_chain_state::{CanonicalInMemoryState, DeferredTrieData, ExecutedBlock};
19use reth_consensus::{ConsensusError, FullConsensus};
20use reth_engine_primitives::{
21    ConfigureEngineEvm, ExecutableTxIterator, ExecutionPayload, InvalidBlockHook, PayloadValidator,
22};
23use reth_errors::{BlockExecutionError, ProviderResult};
24use reth_evm::{
25    block::BlockExecutor, execute::ExecutableTxFor, ConfigureEvm, EvmEnvFor, ExecutionCtxFor,
26    SpecFor,
27};
28use reth_payload_primitives::{
29    BuiltPayload, InvalidPayloadAttributesError, NewPayloadError, PayloadTypes,
30};
31use reth_primitives_traits::{
32    AlloyBlockHeader, BlockBody, BlockTy, GotExpected, NodePrimitives, RecoveredBlock, SealedBlock,
33    SealedHeader, SignerRecoverable,
34};
35use reth_provider::{
36    providers::OverlayStateProviderFactory, BlockExecutionOutput, BlockReader,
37    DatabaseProviderFactory, ExecutionOutcome, HashedPostStateProvider, ProviderError,
38    PruneCheckpointReader, StageCheckpointReader, StateProvider, StateProviderFactory, StateReader,
39    StateRootProvider, TrieReader,
40};
41use reth_revm::db::State;
42use reth_trie::{updates::TrieUpdates, HashedPostState, TrieInputSorted};
43use reth_trie_parallel::root::{ParallelStateRoot, ParallelStateRootError};
44use revm_primitives::Address;
45use std::{
46    collections::HashMap,
47    panic::{self, AssertUnwindSafe},
48    sync::Arc,
49    time::Instant,
50};
51use tracing::{debug, debug_span, error, info, instrument, trace, warn};
52
53/// Context providing access to tree state during validation.
54///
55/// This context is provided to the [`EngineValidator`] and includes the state of the tree's
56/// internals
57pub struct TreeCtx<'a, N: NodePrimitives> {
58    /// The engine API tree state
59    state: &'a mut EngineApiTreeState<N>,
60    /// Reference to the canonical in-memory state
61    canonical_in_memory_state: &'a CanonicalInMemoryState<N>,
62}
63
64impl<'a, N: NodePrimitives> std::fmt::Debug for TreeCtx<'a, N> {
65    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66        f.debug_struct("TreeCtx")
67            .field("state", &"EngineApiTreeState")
68            .field("canonical_in_memory_state", &self.canonical_in_memory_state)
69            .finish()
70    }
71}
72
73impl<'a, N: NodePrimitives> TreeCtx<'a, N> {
74    /// Creates a new tree context
75    pub const fn new(
76        state: &'a mut EngineApiTreeState<N>,
77        canonical_in_memory_state: &'a CanonicalInMemoryState<N>,
78    ) -> Self {
79        Self { state, canonical_in_memory_state }
80    }
81
82    /// Returns a reference to the engine tree state
83    pub const fn state(&self) -> &EngineApiTreeState<N> {
84        &*self.state
85    }
86
87    /// Returns a mutable reference to the engine tree state
88    pub const fn state_mut(&mut self) -> &mut EngineApiTreeState<N> {
89        self.state
90    }
91
92    /// Returns a reference to the canonical in-memory state
93    pub const fn canonical_in_memory_state(&self) -> &'a CanonicalInMemoryState<N> {
94        self.canonical_in_memory_state
95    }
96}
97
98/// A helper type that provides reusable payload validation logic for network-specific validators.
99///
100/// This type satisfies [`EngineValidator`] and is responsible for executing blocks/payloads.
101///
102/// This type contains common validation, execution, and state root computation logic that can be
103/// used by network-specific payload validators (e.g., Ethereum, Optimism). It is not meant to be
104/// used as a standalone component, but rather as a building block for concrete implementations.
105#[derive(derive_more::Debug)]
106pub struct BasicEngineValidator<P, Evm, V>
107where
108    Evm: ConfigureEvm,
109{
110    /// Provider for database access.
111    provider: P,
112    /// Consensus implementation for validation.
113    consensus: Arc<dyn FullConsensus<Evm::Primitives, Error = ConsensusError>>,
114    /// EVM configuration.
115    evm_config: Evm,
116    /// Configuration for the tree.
117    config: TreeConfig,
118    /// Payload processor for state root computation.
119    payload_processor: PayloadProcessor<Evm>,
120    /// Precompile cache map.
121    precompile_cache_map: PrecompileCacheMap<SpecFor<Evm>>,
122    /// Precompile cache metrics.
123    precompile_cache_metrics: HashMap<alloy_primitives::Address, CachedPrecompileMetrics>,
124    /// Hook to call when invalid blocks are encountered.
125    #[debug(skip)]
126    invalid_block_hook: Box<dyn InvalidBlockHook<Evm::Primitives>>,
127    /// Metrics for the engine api.
128    metrics: EngineApiMetrics,
129    /// Validator for the payload.
130    validator: V,
131}
132
133impl<N, P, Evm, V> BasicEngineValidator<P, Evm, V>
134where
135    N: NodePrimitives,
136    P: DatabaseProviderFactory<
137            Provider: BlockReader + TrieReader + StageCheckpointReader + PruneCheckpointReader,
138        > + BlockReader<Header = N::BlockHeader>
139        + StateProviderFactory
140        + StateReader
141        + HashedPostStateProvider
142        + Clone
143        + 'static,
144    Evm: ConfigureEvm<Primitives = N> + 'static,
145{
146    /// Creates a new `TreePayloadValidator`.
147    #[allow(clippy::too_many_arguments)]
148    pub fn new(
149        provider: P,
150        consensus: Arc<dyn FullConsensus<N, Error = ConsensusError>>,
151        evm_config: Evm,
152        validator: V,
153        config: TreeConfig,
154        invalid_block_hook: Box<dyn InvalidBlockHook<N>>,
155    ) -> Self {
156        let precompile_cache_map = PrecompileCacheMap::default();
157        let payload_processor = PayloadProcessor::new(
158            WorkloadExecutor::default(),
159            evm_config.clone(),
160            &config,
161            precompile_cache_map.clone(),
162        );
163        Self {
164            provider,
165            consensus,
166            evm_config,
167            payload_processor,
168            precompile_cache_map,
169            precompile_cache_metrics: HashMap::new(),
170            config,
171            invalid_block_hook,
172            metrics: EngineApiMetrics::default(),
173            validator,
174        }
175    }
176
177    /// Converts a [`BlockOrPayload`] to a recovered block.
178    #[instrument(level = "debug", target = "engine::tree::payload_validator", skip_all)]
179    pub fn convert_to_block<T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
180        &self,
181        input: BlockOrPayload<T>,
182    ) -> Result<SealedBlock<N::Block>, NewPayloadError>
183    where
184        V: PayloadValidator<T, Block = N::Block>,
185    {
186        match input {
187            BlockOrPayload::Payload(payload) => self.validator.convert_payload_to_block(payload),
188            BlockOrPayload::Block(block) => Ok(block),
189        }
190    }
191
192    /// Returns EVM environment for the given payload or block.
193    pub fn evm_env_for<T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
194        &self,
195        input: &BlockOrPayload<T>,
196    ) -> Result<EvmEnvFor<Evm>, Evm::Error>
197    where
198        V: PayloadValidator<T, Block = N::Block>,
199        Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
200    {
201        match input {
202            BlockOrPayload::Payload(payload) => Ok(self.evm_config.evm_env_for_payload(payload)?),
203            BlockOrPayload::Block(block) => Ok(self.evm_config.evm_env(block.header())?),
204        }
205    }
206
207    /// Returns [`ExecutableTxIterator`] for the given payload or block.
208    pub fn tx_iterator_for<'a, T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
209        &'a self,
210        input: &'a BlockOrPayload<T>,
211    ) -> Result<impl ExecutableTxIterator<Evm>, NewPayloadError>
212    where
213        V: PayloadValidator<T, Block = N::Block>,
214        Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
215    {
216        match input {
217            BlockOrPayload::Payload(payload) => {
218                let (iter, convert) = self
219                    .evm_config
220                    .tx_iterator_for_payload(payload)
221                    .map_err(NewPayloadError::other)?
222                    .into();
223
224                let iter = Either::Left(iter.into_iter().map(Either::Left));
225                let convert = move |tx| {
226                    let Either::Left(tx) = tx else { unreachable!() };
227                    convert(tx).map(Either::Left).map_err(Either::Left)
228                };
229
230                // Box the closure to satisfy the `Fn` bound both here and in the branch below
231                Ok((iter, Box::new(convert) as Box<dyn Fn(_) -> _ + Send + Sync + 'static>))
232            }
233            BlockOrPayload::Block(block) => {
234                let iter =
235                    Either::Right(block.body().clone_transactions().into_iter().map(Either::Right));
236                let convert = move |tx: Either<_, N::SignedTx>| {
237                    let Either::Right(tx) = tx else { unreachable!() };
238                    tx.try_into_recovered().map(Either::Right).map_err(Either::Right)
239                };
240
241                Ok((iter, Box::new(convert)))
242            }
243        }
244    }
245
246    /// Returns a [`ExecutionCtxFor`] for the given payload or block.
247    pub fn execution_ctx_for<'a, T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
248        &self,
249        input: &'a BlockOrPayload<T>,
250    ) -> Result<ExecutionCtxFor<'a, Evm>, Evm::Error>
251    where
252        V: PayloadValidator<T, Block = N::Block>,
253        Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
254    {
255        match input {
256            BlockOrPayload::Payload(payload) => Ok(self.evm_config.context_for_payload(payload)?),
257            BlockOrPayload::Block(block) => Ok(self.evm_config.context_for_block(block)?),
258        }
259    }
260
261    /// Handles execution errors by checking if header validation errors should take precedence.
262    ///
263    /// When an execution error occurs, this function checks if there are any header validation
264    /// errors that should be reported instead, as header validation errors have higher priority.
265    fn handle_execution_error<T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
266        &self,
267        input: BlockOrPayload<T>,
268        execution_err: InsertBlockErrorKind,
269        parent_block: &SealedHeader<N::BlockHeader>,
270    ) -> Result<ExecutedBlock<N>, InsertPayloadError<N::Block>>
271    where
272        V: PayloadValidator<T, Block = N::Block>,
273    {
274        debug!(
275            target: "engine::tree::payload_validator",
276            ?execution_err,
277            block = ?input.num_hash(),
278            "Block execution failed, checking for header validation errors"
279        );
280
281        // If execution failed, we should first check if there are any header validation
282        // errors that take precedence over the execution error
283        let block = self.convert_to_block(input)?;
284
285        // Validate block consensus rules which includes header validation
286        if let Err(consensus_err) = self.validate_block_inner(&block) {
287            // Header validation error takes precedence over execution error
288            return Err(InsertBlockError::new(block, consensus_err.into()).into())
289        }
290
291        // Also validate against the parent
292        if let Err(consensus_err) =
293            self.consensus.validate_header_against_parent(block.sealed_header(), parent_block)
294        {
295            // Parent validation error takes precedence over execution error
296            return Err(InsertBlockError::new(block, consensus_err.into()).into())
297        }
298
299        // No header validation errors, return the original execution error
300        Err(InsertBlockError::new(block, execution_err).into())
301    }
302
303    /// Validates a block that has already been converted from a payload.
304    ///
305    /// This method performs:
306    /// - Consensus validation
307    /// - Block execution
308    /// - State root computation
309    /// - Fork detection
310    #[instrument(
311        level = "debug",
312        target = "engine::tree::payload_validator",
313        skip_all,
314        fields(
315            parent = ?input.parent_hash(),
316            type_name = ?input.type_name(),
317        )
318    )]
319    pub fn validate_block_with_state<T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
320        &mut self,
321        input: BlockOrPayload<T>,
322        mut ctx: TreeCtx<'_, N>,
323    ) -> ValidationOutcome<N, InsertPayloadError<N::Block>>
324    where
325        V: PayloadValidator<T, Block = N::Block>,
326        Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
327    {
328        /// A helper macro that returns the block in case there was an error
329        /// This macro is used for early returns before block conversion
330        macro_rules! ensure_ok {
331            ($expr:expr) => {
332                match $expr {
333                    Ok(val) => val,
334                    Err(e) => {
335                        let block = self.convert_to_block(input)?;
336                        return Err(InsertBlockError::new(block, e.into()).into())
337                    }
338                }
339            };
340        }
341
342        /// A helper macro for handling errors after the input has been converted to a block
343        macro_rules! ensure_ok_post_block {
344            ($expr:expr, $block:expr) => {
345                match $expr {
346                    Ok(val) => val,
347                    Err(e) => {
348                        return Err(
349                            InsertBlockError::new($block.into_sealed_block(), e.into()).into()
350                        )
351                    }
352                }
353            };
354        }
355
356        let parent_hash = input.parent_hash();
357        let block_num_hash = input.num_hash();
358
359        trace!(target: "engine::tree::payload_validator", "Fetching block state provider");
360        let _enter =
361            debug_span!(target: "engine::tree::payload_validator", "state provider").entered();
362        let Some(provider_builder) =
363            ensure_ok!(self.state_provider_builder(parent_hash, ctx.state()))
364        else {
365            // this is pre-validated in the tree
366            return Err(InsertBlockError::new(
367                self.convert_to_block(input)?,
368                ProviderError::HeaderNotFound(parent_hash.into()).into(),
369            )
370            .into())
371        };
372        let mut state_provider = ensure_ok!(provider_builder.build());
373        drop(_enter);
374
375        // fetch parent block
376        let Some(parent_block) = ensure_ok!(self.sealed_header_by_hash(parent_hash, ctx.state()))
377        else {
378            return Err(InsertBlockError::new(
379                self.convert_to_block(input)?,
380                ProviderError::HeaderNotFound(parent_hash.into()).into(),
381            )
382            .into())
383        };
384
385        let evm_env = debug_span!(target: "engine::tree::payload_validator", "evm env")
386            .in_scope(|| self.evm_env_for(&input))
387            .map_err(NewPayloadError::other)?;
388
389        let env = ExecutionEnv { evm_env, hash: input.hash(), parent_hash: input.parent_hash() };
390
391        // Plan the strategy used for state root computation.
392        let strategy = self.plan_state_root_computation();
393
394        debug!(
395            target: "engine::tree::payload_validator",
396            ?strategy,
397            "Decided which state root algorithm to run"
398        );
399
400        // use prewarming background task
401        let txs = self.tx_iterator_for(&input)?;
402
403        // Spawn the appropriate processor based on strategy
404        let mut handle = ensure_ok!(self.spawn_payload_processor(
405            env.clone(),
406            txs,
407            provider_builder,
408            parent_hash,
409            ctx.state(),
410            strategy,
411        ));
412
413        // Use cached state provider before executing, used in execution after prewarming threads
414        // complete
415        if let Some((caches, cache_metrics)) = handle.caches().zip(handle.cache_metrics()) {
416            state_provider = Box::new(CachedStateProvider::new_with_caches(
417                state_provider,
418                caches,
419                cache_metrics,
420            ));
421        };
422
423        // Execute the block and handle any execution errors
424        let (output, senders) = match if self.config.state_provider_metrics() {
425            let state_provider =
426                InstrumentedStateProvider::from_state_provider(&state_provider, "engine");
427            self.execute_block(&state_provider, env, &input, &mut handle)
428        } else {
429            self.execute_block(&state_provider, env, &input, &mut handle)
430        } {
431            Ok(output) => output,
432            Err(err) => return self.handle_execution_error(input, err, &parent_block),
433        };
434
435        // After executing the block we can stop prewarming transactions
436        handle.stop_prewarming_execution();
437
438        let block = self.convert_to_block(input)?.with_senders(senders);
439
440        let hashed_state = ensure_ok_post_block!(
441            self.validate_post_execution(&block, &parent_block, &output, &mut ctx),
442            block
443        );
444
445        let root_time = Instant::now();
446        let mut maybe_state_root = None;
447
448        match strategy {
449            StateRootStrategy::StateRootTask => {
450                debug!(target: "engine::tree::payload_validator", "Using sparse trie state root algorithm");
451                match handle.state_root() {
452                    Ok(StateRootComputeOutcome { state_root, trie_updates }) => {
453                        let elapsed = root_time.elapsed();
454                        info!(target: "engine::tree::payload_validator", ?state_root, ?elapsed, "State root task finished");
455                        // we double check the state root here for good measure
456                        if state_root == block.header().state_root() {
457                            maybe_state_root = Some((state_root, trie_updates, elapsed))
458                        } else {
459                            warn!(
460                                target: "engine::tree::payload_validator",
461                                ?state_root,
462                                block_state_root = ?block.header().state_root(),
463                                "State root task returned incorrect state root"
464                            );
465                        }
466                    }
467                    Err(error) => {
468                        debug!(target: "engine::tree::payload_validator", %error, "State root task failed");
469                    }
470                }
471            }
472            StateRootStrategy::Parallel => {
473                debug!(target: "engine::tree::payload_validator", "Using parallel state root algorithm");
474                match self.compute_state_root_parallel(
475                    block.parent_hash(),
476                    &hashed_state,
477                    ctx.state(),
478                ) {
479                    Ok(result) => {
480                        let elapsed = root_time.elapsed();
481                        info!(
482                            target: "engine::tree::payload_validator",
483                            regular_state_root = ?result.0,
484                            ?elapsed,
485                            "Regular root task finished"
486                        );
487                        maybe_state_root = Some((result.0, result.1, elapsed));
488                    }
489                    Err(error) => {
490                        debug!(target: "engine::tree::payload_validator", %error, "Parallel state root computation failed");
491                    }
492                }
493            }
494            StateRootStrategy::Synchronous => {}
495        }
496
497        // Determine the state root.
498        // If the state root was computed in parallel, we use it.
499        // Otherwise, we fall back to computing it synchronously.
500        let (state_root, trie_output, root_elapsed) = if let Some(maybe_state_root) =
501            maybe_state_root
502        {
503            maybe_state_root
504        } else {
505            // fallback is to compute the state root regularly in sync
506            if self.config.state_root_fallback() {
507                debug!(target: "engine::tree::payload_validator", "Using state root fallback for testing");
508            } else {
509                warn!(target: "engine::tree::payload_validator", "Failed to compute state root in parallel");
510                self.metrics.block_validation.state_root_parallel_fallback_total.increment(1);
511            }
512
513            let (root, updates) = ensure_ok_post_block!(
514                state_provider.state_root_with_updates(hashed_state.clone()),
515                block
516            );
517            (root, updates, root_time.elapsed())
518        };
519
520        self.metrics.block_validation.record_state_root(&trie_output, root_elapsed.as_secs_f64());
521        debug!(target: "engine::tree::payload_validator", ?root_elapsed, "Calculated state root");
522
523        // ensure state root matches
524        if state_root != block.header().state_root() {
525            // call post-block hook
526            self.on_invalid_block(
527                &parent_block,
528                &block,
529                &output,
530                Some((&trie_output, state_root)),
531                ctx.state_mut(),
532            );
533            let block_state_root = block.header().state_root();
534            return Err(InsertBlockError::new(
535                block.into_sealed_block(),
536                ConsensusError::BodyStateRootDiff(
537                    GotExpected { got: state_root, expected: block_state_root }.into(),
538                )
539                .into(),
540            )
541            .into())
542        }
543
544        // terminate prewarming task with good state output
545        handle.terminate_caching(Some(&output.state));
546
547        Ok(self.spawn_deferred_trie_task(
548            block,
549            output,
550            block_num_hash.number,
551            &ctx,
552            hashed_state,
553            trie_output,
554        ))
555    }
556
557    /// Return sealed block header from database or in-memory state by hash.
558    fn sealed_header_by_hash(
559        &self,
560        hash: B256,
561        state: &EngineApiTreeState<N>,
562    ) -> ProviderResult<Option<SealedHeader<N::BlockHeader>>> {
563        // check memory first
564        let header = state.tree_state.sealed_header_by_hash(&hash);
565
566        if header.is_some() {
567            Ok(header)
568        } else {
569            self.provider.sealed_header_by_hash(hash)
570        }
571    }
572
573    /// Validate if block is correct and satisfies all the consensus rules that concern the header
574    /// and block body itself.
575    #[instrument(level = "debug", target = "engine::tree::payload_validator", skip_all)]
576    fn validate_block_inner(&self, block: &SealedBlock<N::Block>) -> Result<(), ConsensusError> {
577        if let Err(e) = self.consensus.validate_header(block.sealed_header()) {
578            error!(target: "engine::tree::payload_validator", ?block, "Failed to validate header {}: {e}", block.hash());
579            return Err(e)
580        }
581
582        if let Err(e) = self.consensus.validate_block_pre_execution(block) {
583            error!(target: "engine::tree::payload_validator", ?block, "Failed to validate block {}: {e}", block.hash());
584            return Err(e)
585        }
586
587        Ok(())
588    }
589
590    /// Executes a block with the given state provider
591    #[instrument(level = "debug", target = "engine::tree::payload_validator", skip_all)]
592    fn execute_block<S, Err, T>(
593        &mut self,
594        state_provider: S,
595        env: ExecutionEnv<Evm>,
596        input: &BlockOrPayload<T>,
597        handle: &mut PayloadHandle<impl ExecutableTxFor<Evm>, Err>,
598    ) -> Result<(BlockExecutionOutput<N::Receipt>, Vec<Address>), InsertBlockErrorKind>
599    where
600        S: StateProvider,
601        Err: core::error::Error + Send + Sync + 'static,
602        V: PayloadValidator<T, Block = N::Block>,
603        T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>,
604        Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
605    {
606        debug!(target: "engine::tree::payload_validator", "Executing block");
607
608        let mut db = State::builder()
609            .with_database(StateProviderDatabase::new(&state_provider))
610            .with_bundle_update()
611            .without_state_clear()
612            .build();
613
614        let evm = self.evm_config.evm_with_env(&mut db, env.evm_env.clone());
615        let ctx =
616            self.execution_ctx_for(input).map_err(|e| InsertBlockErrorKind::Other(Box::new(e)))?;
617        let mut executor = self.evm_config.create_executor(evm, ctx);
618
619        if !self.config.precompile_cache_disabled() {
620            // Only cache pure precompiles to avoid issues with stateful precompiles
621            executor.evm_mut().precompiles_mut().map_pure_precompiles(|address, precompile| {
622                let metrics = self
623                    .precompile_cache_metrics
624                    .entry(*address)
625                    .or_insert_with(|| CachedPrecompileMetrics::new_with_address(*address))
626                    .clone();
627                CachedPrecompile::wrap(
628                    precompile,
629                    self.precompile_cache_map.cache_for_address(*address),
630                    *env.evm_env.spec_id(),
631                    Some(metrics),
632                )
633            });
634        }
635
636        let execution_start = Instant::now();
637        let state_hook = Box::new(handle.state_hook());
638        let (output, senders) = self.metrics.execute_metered(
639            executor,
640            handle.iter_transactions().map(|res| res.map_err(BlockExecutionError::other)),
641            state_hook,
642        )?;
643        let execution_finish = Instant::now();
644        let execution_time = execution_finish.duration_since(execution_start);
645        debug!(target: "engine::tree::payload_validator", elapsed = ?execution_time, "Executed block");
646        Ok((output, senders))
647    }
648
649    /// Compute state root for the given hashed post state in parallel.
650    ///
651    /// # Returns
652    ///
653    /// Returns `Ok(_)` if computed successfully.
654    /// Returns `Err(_)` if error was encountered during computation.
655    /// `Err(ProviderError::ConsistentView(_))` can be safely ignored and fallback computation
656    /// should be used instead.
657    #[instrument(level = "debug", target = "engine::tree::payload_validator", skip_all)]
658    fn compute_state_root_parallel(
659        &self,
660        parent_hash: B256,
661        hashed_state: &HashedPostState,
662        state: &EngineApiTreeState<N>,
663    ) -> Result<(B256, TrieUpdates), ParallelStateRootError> {
664        let (mut input, block_hash) = self.compute_trie_input(parent_hash, state)?;
665
666        // Extend state overlay with current block's sorted state.
667        input.prefix_sets.extend(hashed_state.construct_prefix_sets());
668        let sorted_hashed_state = hashed_state.clone_into_sorted();
669        Arc::make_mut(&mut input.state).extend_ref(&sorted_hashed_state);
670
671        let TrieInputSorted { nodes, state, prefix_sets: prefix_sets_mut } = input;
672
673        let factory = OverlayStateProviderFactory::new(self.provider.clone())
674            .with_block_hash(Some(block_hash))
675            .with_trie_overlay(Some(nodes))
676            .with_hashed_state_overlay(Some(state));
677
678        // The `hashed_state` argument is already taken into account as part of the overlay, but we
679        // need to use the prefix sets which were generated from it to indicate to the
680        // ParallelStateRoot which parts of the trie need to be recomputed.
681        let prefix_sets = prefix_sets_mut.freeze();
682
683        ParallelStateRoot::new(factory, prefix_sets).incremental_root_with_updates()
684    }
685
686    /// Validates the block after execution.
687    ///
688    /// This performs:
689    /// - parent header validation
690    /// - post-execution consensus validation
691    /// - state-root based post-execution validation
692    #[instrument(level = "debug", target = "engine::tree::payload_validator", skip_all)]
693    fn validate_post_execution<T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
694        &self,
695        block: &RecoveredBlock<N::Block>,
696        parent_block: &SealedHeader<N::BlockHeader>,
697        output: &BlockExecutionOutput<N::Receipt>,
698        ctx: &mut TreeCtx<'_, N>,
699    ) -> Result<HashedPostState, InsertBlockErrorKind>
700    where
701        V: PayloadValidator<T, Block = N::Block>,
702    {
703        let start = Instant::now();
704
705        trace!(target: "engine::tree::payload_validator", block=?block.num_hash(), "Validating block consensus");
706        // validate block consensus rules
707        if let Err(e) = self.validate_block_inner(block) {
708            return Err(e.into())
709        }
710
711        // now validate against the parent
712        let _enter = debug_span!(target: "engine::tree::payload_validator", "validate_header_against_parent").entered();
713        if let Err(e) =
714            self.consensus.validate_header_against_parent(block.sealed_header(), parent_block)
715        {
716            warn!(target: "engine::tree::payload_validator", ?block, "Failed to validate header {} against parent: {e}", block.hash());
717            return Err(e.into())
718        }
719        drop(_enter);
720
721        // Validate block post-execution rules
722        let _enter =
723            debug_span!(target: "engine::tree::payload_validator", "validate_block_post_execution")
724                .entered();
725        if let Err(err) = self.consensus.validate_block_post_execution(block, output) {
726            // call post-block hook
727            self.on_invalid_block(parent_block, block, output, None, ctx.state_mut());
728            return Err(err.into())
729        }
730        drop(_enter);
731
732        let _enter =
733            debug_span!(target: "engine::tree::payload_validator", "hashed_post_state").entered();
734        let hashed_state = self.provider.hashed_post_state(&output.state);
735        drop(_enter);
736
737        let _enter = debug_span!(target: "engine::tree::payload_validator", "validate_block_post_execution_with_hashed_state").entered();
738        if let Err(err) =
739            self.validator.validate_block_post_execution_with_hashed_state(&hashed_state, block)
740        {
741            // call post-block hook
742            self.on_invalid_block(parent_block, block, output, None, ctx.state_mut());
743            return Err(err.into())
744        }
745
746        // record post-execution validation duration
747        self.metrics
748            .block_validation
749            .post_execution_validation_duration
750            .record(start.elapsed().as_secs_f64());
751
752        Ok(hashed_state)
753    }
754
755    /// Spawns a payload processor task based on the state root strategy.
756    ///
757    /// This method determines how to execute the block and compute its state root based on
758    /// the selected strategy:
759    /// - `StateRootTask`: Uses a dedicated task for state root computation with proof generation
760    /// - `Parallel`: Computes state root in parallel with block execution
761    /// - `Synchronous`: Falls back to sequential execution and state root computation
762    ///
763    /// The method handles strategy fallbacks if the preferred approach fails, ensuring
764    /// block execution always completes with a valid state root.
765    #[allow(clippy::too_many_arguments)]
766    #[instrument(
767        level = "debug",
768        target = "engine::tree::payload_validator",
769        skip_all,
770        fields(strategy)
771    )]
772    fn spawn_payload_processor<T: ExecutableTxIterator<Evm>>(
773        &mut self,
774        env: ExecutionEnv<Evm>,
775        txs: T,
776        provider_builder: StateProviderBuilder<N, P>,
777        parent_hash: B256,
778        state: &EngineApiTreeState<N>,
779        strategy: StateRootStrategy,
780    ) -> Result<
781        PayloadHandle<
782            impl ExecutableTxFor<Evm> + use<N, P, Evm, V, T>,
783            impl core::error::Error + Send + Sync + 'static + use<N, P, Evm, V, T>,
784        >,
785        InsertBlockErrorKind,
786    > {
787        match strategy {
788            StateRootStrategy::StateRootTask => {
789                // Compute trie input
790                let trie_input_start = Instant::now();
791                let (trie_input, block_hash) = self.compute_trie_input(parent_hash, state)?;
792
793                // Create OverlayStateProviderFactory with sorted trie data for multiproofs
794                let TrieInputSorted { nodes, state, .. } = trie_input;
795
796                let multiproof_provider_factory =
797                    OverlayStateProviderFactory::new(self.provider.clone())
798                        .with_block_hash(Some(block_hash))
799                        .with_trie_overlay(Some(nodes))
800                        .with_hashed_state_overlay(Some(state));
801
802                // Record trie input duration including OverlayStateProviderFactory setup
803                self.metrics
804                    .block_validation
805                    .trie_input_duration
806                    .record(trie_input_start.elapsed().as_secs_f64());
807
808                let spawn_start = Instant::now();
809                let handle = self.payload_processor.spawn(
810                    env,
811                    txs,
812                    provider_builder,
813                    multiproof_provider_factory,
814                    &self.config,
815                );
816
817                // record prewarming initialization duration
818                self.metrics
819                    .block_validation
820                    .spawn_payload_processor
821                    .record(spawn_start.elapsed().as_secs_f64());
822
823                Ok(handle)
824            }
825            StateRootStrategy::Parallel | StateRootStrategy::Synchronous => {
826                let start = Instant::now();
827                let handle =
828                    self.payload_processor.spawn_cache_exclusive(env, txs, provider_builder);
829
830                // Record prewarming initialization duration
831                self.metrics
832                    .block_validation
833                    .spawn_payload_processor
834                    .record(start.elapsed().as_secs_f64());
835
836                Ok(handle)
837            }
838        }
839    }
840
841    /// Creates a `StateProviderBuilder` for the given parent hash.
842    ///
843    /// This method checks if the parent is in the tree state (in-memory) or persisted to disk,
844    /// and creates the appropriate provider builder.
845    fn state_provider_builder(
846        &self,
847        hash: B256,
848        state: &EngineApiTreeState<N>,
849    ) -> ProviderResult<Option<StateProviderBuilder<N, P>>> {
850        if let Some((historical, blocks)) = state.tree_state.blocks_by_hash(hash) {
851            debug!(target: "engine::tree::payload_validator", %hash, %historical, "found canonical state for block in memory, creating provider builder");
852            // the block leads back to the canonical chain
853            return Ok(Some(StateProviderBuilder::new(
854                self.provider.clone(),
855                historical,
856                Some(blocks),
857            )))
858        }
859
860        // Check if the block is persisted
861        if let Some(header) = self.provider.header(hash)? {
862            debug!(target: "engine::tree::payload_validator", %hash, number = %header.number(), "found canonical state for block in database, creating provider builder");
863            // For persisted blocks, we create a builder that will fetch state directly from the
864            // database
865            return Ok(Some(StateProviderBuilder::new(self.provider.clone(), hash, None)))
866        }
867
868        debug!(target: "engine::tree::payload_validator", %hash, "no canonical state found for block");
869        Ok(None)
870    }
871
872    /// Determines the state root computation strategy based on configuration.
873    ///
874    /// Note: Use state root task only if prefix sets are empty, otherwise proof generation is
875    /// too expensive because it requires walking all paths in every proof.
876    const fn plan_state_root_computation(&self) -> StateRootStrategy {
877        if self.config.state_root_fallback() || !self.config.has_enough_parallelism() {
878            StateRootStrategy::Synchronous
879        } else if self.config.use_state_root_task() {
880            StateRootStrategy::StateRootTask
881        } else {
882            StateRootStrategy::Parallel
883        }
884    }
885
886    /// Called when an invalid block is encountered during validation.
887    fn on_invalid_block(
888        &self,
889        parent_header: &SealedHeader<N::BlockHeader>,
890        block: &RecoveredBlock<N::Block>,
891        output: &BlockExecutionOutput<N::Receipt>,
892        trie_updates: Option<(&TrieUpdates, B256)>,
893        state: &mut EngineApiTreeState<N>,
894    ) {
895        if state.invalid_headers.get(&block.hash()).is_some() {
896            // we already marked this block as invalid
897            return
898        }
899        self.invalid_block_hook.on_invalid_block(parent_header, block, output, trie_updates);
900    }
901
902    /// Computes [`TrieInputSorted`] for the provided parent hash by combining database state
903    /// with in-memory overlays.
904    ///
905    /// The goal of this function is to take in-memory blocks and generate a [`TrieInputSorted`]
906    /// that extends from the highest persisted ancestor up through the parent. This enables state
907    /// root computation and proof generation without requiring all blocks to be persisted
908    /// first.
909    ///
910    /// It works as follows:
911    /// 1. Collect in-memory overlay blocks using [`crate::tree::TreeState::blocks_by_hash`]. This
912    ///    returns the highest persisted ancestor hash (`block_hash`) and the list of in-memory
913    ///    blocks building on top of it.
914    /// 2. Fast path: If the tip in-memory block's trie input is already anchored to `block_hash`
915    ///    (its `anchor_hash` matches `block_hash`), reuse it directly.
916    /// 3. Slow path: Build a new [`TrieInputSorted`] by aggregating the overlay blocks (from oldest
917    ///    to newest) on top of the database state at `block_hash`.
918    #[instrument(
919        level = "debug",
920        target = "engine::tree::payload_validator",
921        skip_all,
922        fields(parent_hash)
923    )]
924    fn compute_trie_input(
925        &self,
926        parent_hash: B256,
927        state: &EngineApiTreeState<N>,
928    ) -> ProviderResult<(TrieInputSorted, B256)> {
929        let wait_start = Instant::now();
930        let (block_hash, blocks) =
931            state.tree_state.blocks_by_hash(parent_hash).unwrap_or_else(|| (parent_hash, vec![]));
932
933        // Fast path: if the tip block's anchor matches the persisted ancestor hash, reuse its
934        // TrieInput. This means the TrieInputSorted already aggregates all in-memory overlays
935        // from that ancestor, so we can avoid re-aggregation.
936        if let Some(tip_block) = blocks.first() {
937            let data = tip_block.trie_data();
938            if let (Some(anchor_hash), Some(trie_input)) =
939                (data.anchor_hash(), data.trie_input().cloned()) &&
940                anchor_hash == block_hash
941            {
942                trace!(target: "engine::tree::payload_validator", %block_hash,"Reusing trie input with matching anchor hash");
943                self.metrics
944                    .block_validation
945                    .deferred_trie_wait_duration
946                    .record(wait_start.elapsed().as_secs_f64());
947                return Ok(((*trie_input).clone(), block_hash));
948            }
949        }
950
951        if blocks.is_empty() {
952            debug!(target: "engine::tree::payload_validator", "Parent found on disk");
953        } else {
954            debug!(target: "engine::tree::payload_validator", historical = ?block_hash, blocks = blocks.len(), "Parent found in memory");
955        }
956
957        // Extend with contents of parent in-memory blocks directly in sorted form.
958        let input = Self::merge_overlay_trie_input(&blocks);
959
960        self.metrics
961            .block_validation
962            .deferred_trie_wait_duration
963            .record(wait_start.elapsed().as_secs_f64());
964        Ok((input, block_hash))
965    }
966
967    /// Aggregates multiple in-memory blocks into a single [`TrieInputSorted`] by combining their
968    /// state changes.
969    ///
970    /// The input `blocks` vector is ordered newest -> oldest (see `TreeState::blocks_by_hash`).
971    /// We iterate it in reverse so we start with the oldest block's trie data and extend forward
972    /// toward the newest, ensuring newer state takes precedence.
973    fn merge_overlay_trie_input(blocks: &[ExecutedBlock<N>]) -> TrieInputSorted {
974        let mut input = TrieInputSorted::default();
975        let mut blocks_iter = blocks.iter().rev().peekable();
976
977        if let Some(first) = blocks_iter.next() {
978            let data = first.trie_data();
979            input.state = data.hashed_state;
980            input.nodes = data.trie_updates;
981
982            // Only clone and mutate if there are more in-memory blocks.
983            if blocks_iter.peek().is_some() {
984                let state_mut = Arc::make_mut(&mut input.state);
985                let nodes_mut = Arc::make_mut(&mut input.nodes);
986                for block in blocks_iter {
987                    let data = block.trie_data();
988                    state_mut.extend_ref(data.hashed_state.as_ref());
989                    nodes_mut.extend_ref(data.trie_updates.as_ref());
990                }
991            }
992        }
993
994        input
995    }
996
997    /// Spawns a background task to compute and sort trie data for the executed block.
998    ///
999    /// This function creates a [`DeferredTrieData`] handle with fallback inputs and spawns a
1000    /// blocking task that calls `wait_cloned()` to:
1001    /// 1. Sort the block's hashed state and trie updates
1002    /// 2. Merge ancestor overlays and extend with the sorted data
1003    /// 3. Create an [`AnchoredTrieInput`](reth_chain_state::AnchoredTrieInput) for efficient future
1004    ///    trie computations
1005    /// 4. Cache the result so subsequent calls return immediately
1006    ///
1007    /// If the background task hasn't completed when `trie_data()` is called, `wait_cloned()`
1008    /// computes from the stored inputs, eliminating deadlock risk and duplicate computation.
1009    ///
1010    /// The validation hot path can return immediately after state root verification,
1011    /// while consumers (DB writes, overlay providers, proofs) get trie data either
1012    /// from the completed task or via fallback computation.
1013    fn spawn_deferred_trie_task(
1014        &self,
1015        block: RecoveredBlock<N::Block>,
1016        output: BlockExecutionOutput<N::Receipt>,
1017        block_number: u64,
1018        ctx: &TreeCtx<'_, N>,
1019        hashed_state: HashedPostState,
1020        trie_output: TrieUpdates,
1021    ) -> ExecutedBlock<N> {
1022        // Capture parent hash and ancestor overlays for deferred trie input construction.
1023        let (anchor_hash, overlay_blocks) = ctx
1024            .state()
1025            .tree_state
1026            .blocks_by_hash(block.parent_hash())
1027            .unwrap_or_else(|| (block.parent_hash(), Vec::new()));
1028
1029        // Collect lightweight ancestor trie data handles. We don't call trie_data() here;
1030        // the merge and any fallback sorting happens in the compute_trie_input_task.
1031        let ancestors: Vec<DeferredTrieData> =
1032            overlay_blocks.iter().rev().map(|b| b.trie_data_handle()).collect();
1033
1034        // Create deferred handle with fallback inputs in case the background task hasn't completed.
1035        let deferred_trie_data = DeferredTrieData::pending(
1036            Arc::new(hashed_state),
1037            Arc::new(trie_output),
1038            anchor_hash,
1039            ancestors,
1040        );
1041        let deferred_handle_task = deferred_trie_data.clone();
1042        let deferred_compute_duration =
1043            self.metrics.block_validation.deferred_trie_compute_duration.clone();
1044
1045        // Spawn background task to compute trie data. Calling `wait_cloned` will compute from
1046        // the stored inputs and cache the result, so subsequent calls return immediately.
1047        let compute_trie_input_task = move || {
1048            let result = panic::catch_unwind(AssertUnwindSafe(|| {
1049                let compute_start = Instant::now();
1050                let _ = deferred_handle_task.wait_cloned();
1051                deferred_compute_duration.record(compute_start.elapsed().as_secs_f64());
1052            }));
1053
1054            if result.is_err() {
1055                error!(
1056                    target: "engine::tree::payload_validator",
1057                    "Deferred trie task panicked; fallback computation will be used when trie data is accessed"
1058                );
1059            }
1060        };
1061
1062        // Spawn task that computes trie data asynchronously.
1063        self.payload_processor.executor().spawn_blocking(compute_trie_input_task);
1064
1065        ExecutedBlock::with_deferred_trie_data(
1066            Arc::new(block),
1067            Arc::new(ExecutionOutcome::from((output, block_number))),
1068            deferred_trie_data,
1069        )
1070    }
1071}
1072
1073/// Output of block or payload validation.
1074pub type ValidationOutcome<N, E = InsertPayloadError<BlockTy<N>>> = Result<ExecutedBlock<N>, E>;
1075
1076/// Strategy describing how to compute the state root.
1077#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1078enum StateRootStrategy {
1079    /// Use the state root task (background sparse trie computation).
1080    StateRootTask,
1081    /// Run the parallel state root computation on the calling thread.
1082    Parallel,
1083    /// Fall back to synchronous computation via the state provider.
1084    Synchronous,
1085}
1086
1087/// Type that validates the payloads processed by the engine.
1088///
1089/// This provides the necessary functions for validating/executing payloads/blocks.
1090pub trait EngineValidator<
1091    Types: PayloadTypes,
1092    N: NodePrimitives = <<Types as PayloadTypes>::BuiltPayload as BuiltPayload>::Primitives,
1093>: Send + Sync + 'static
1094{
1095    /// Validates the payload attributes with respect to the header.
1096    ///
1097    /// By default, this enforces that the payload attributes timestamp is greater than the
1098    /// timestamp according to:
1099    ///   > 7. Client software MUST ensure that payloadAttributes.timestamp is greater than
1100    ///   > timestamp
1101    ///   > of a block referenced by forkchoiceState.headBlockHash.
1102    ///
1103    /// See also: <https://github.com/ethereum/execution-apis/blob/main/src/engine/common.md#specification-1>
1104    fn validate_payload_attributes_against_header(
1105        &self,
1106        attr: &Types::PayloadAttributes,
1107        header: &N::BlockHeader,
1108    ) -> Result<(), InvalidPayloadAttributesError>;
1109
1110    /// Ensures that the given payload does not violate any consensus rules that concern the block's
1111    /// layout.
1112    ///
1113    /// This function must convert the payload into the executable block and pre-validate its
1114    /// fields.
1115    ///
1116    /// Implementers should ensure that the checks are done in the order that conforms with the
1117    /// engine-API specification.
1118    fn convert_payload_to_block(
1119        &self,
1120        payload: Types::ExecutionData,
1121    ) -> Result<SealedBlock<N::Block>, NewPayloadError>;
1122
1123    /// Validates a payload received from engine API.
1124    fn validate_payload(
1125        &mut self,
1126        payload: Types::ExecutionData,
1127        ctx: TreeCtx<'_, N>,
1128    ) -> ValidationOutcome<N>;
1129
1130    /// Validates a block downloaded from the network.
1131    fn validate_block(
1132        &mut self,
1133        block: SealedBlock<N::Block>,
1134        ctx: TreeCtx<'_, N>,
1135    ) -> ValidationOutcome<N>;
1136
1137    /// Hook called after an executed block is inserted directly into the tree.
1138    ///
1139    /// This is invoked when blocks are inserted via `InsertExecutedBlock` (e.g., locally built
1140    /// blocks by sequencers) to allow implementations to update internal state such as caches.
1141    fn on_inserted_executed_block(&self, block: ExecutedBlock<N>);
1142}
1143
1144impl<N, Types, P, Evm, V> EngineValidator<Types> for BasicEngineValidator<P, Evm, V>
1145where
1146    P: DatabaseProviderFactory<
1147            Provider: BlockReader + TrieReader + StageCheckpointReader + PruneCheckpointReader,
1148        > + BlockReader<Header = N::BlockHeader>
1149        + StateProviderFactory
1150        + StateReader
1151        + HashedPostStateProvider
1152        + Clone
1153        + 'static,
1154    N: NodePrimitives,
1155    V: PayloadValidator<Types, Block = N::Block>,
1156    Evm: ConfigureEngineEvm<Types::ExecutionData, Primitives = N> + 'static,
1157    Types: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>,
1158{
1159    fn validate_payload_attributes_against_header(
1160        &self,
1161        attr: &Types::PayloadAttributes,
1162        header: &N::BlockHeader,
1163    ) -> Result<(), InvalidPayloadAttributesError> {
1164        self.validator.validate_payload_attributes_against_header(attr, header)
1165    }
1166
1167    fn convert_payload_to_block(
1168        &self,
1169        payload: Types::ExecutionData,
1170    ) -> Result<SealedBlock<N::Block>, NewPayloadError> {
1171        let block = self.validator.convert_payload_to_block(payload)?;
1172        Ok(block)
1173    }
1174
1175    fn validate_payload(
1176        &mut self,
1177        payload: Types::ExecutionData,
1178        ctx: TreeCtx<'_, N>,
1179    ) -> ValidationOutcome<N> {
1180        self.validate_block_with_state(BlockOrPayload::Payload(payload), ctx)
1181    }
1182
1183    fn validate_block(
1184        &mut self,
1185        block: SealedBlock<N::Block>,
1186        ctx: TreeCtx<'_, N>,
1187    ) -> ValidationOutcome<N> {
1188        self.validate_block_with_state(BlockOrPayload::Block(block), ctx)
1189    }
1190
1191    fn on_inserted_executed_block(&self, block: ExecutedBlock<N>) {
1192        self.payload_processor.on_inserted_executed_block(
1193            block.recovered_block.block_with_parent(),
1194            block.execution_output.state(),
1195        );
1196    }
1197}
1198
1199/// Enum representing either block or payload being validated.
1200#[derive(Debug)]
1201pub enum BlockOrPayload<T: PayloadTypes> {
1202    /// Payload.
1203    Payload(T::ExecutionData),
1204    /// Block.
1205    Block(SealedBlock<BlockTy<<T::BuiltPayload as BuiltPayload>::Primitives>>),
1206}
1207
1208impl<T: PayloadTypes> BlockOrPayload<T> {
1209    /// Returns the hash of the block.
1210    pub fn hash(&self) -> B256 {
1211        match self {
1212            Self::Payload(payload) => payload.block_hash(),
1213            Self::Block(block) => block.hash(),
1214        }
1215    }
1216
1217    /// Returns the number and hash of the block.
1218    pub fn num_hash(&self) -> NumHash {
1219        match self {
1220            Self::Payload(payload) => payload.num_hash(),
1221            Self::Block(block) => block.num_hash(),
1222        }
1223    }
1224
1225    /// Returns the parent hash of the block.
1226    pub fn parent_hash(&self) -> B256 {
1227        match self {
1228            Self::Payload(payload) => payload.parent_hash(),
1229            Self::Block(block) => block.parent_hash(),
1230        }
1231    }
1232
1233    /// Returns [`BlockWithParent`] for the block.
1234    pub fn block_with_parent(&self) -> BlockWithParent {
1235        match self {
1236            Self::Payload(payload) => payload.block_with_parent(),
1237            Self::Block(block) => block.block_with_parent(),
1238        }
1239    }
1240
1241    /// Returns a string showing whether or not this is a block or payload.
1242    pub const fn type_name(&self) -> &'static str {
1243        match self {
1244            Self::Payload(_) => "payload",
1245            Self::Block(_) => "block",
1246        }
1247    }
1248
1249    /// Returns the block access list if available.
1250    pub const fn block_access_list(&self) -> Option<Result<BlockAccessList, alloy_rlp::Error>> {
1251        // TODO decode and return `BlockAccessList`
1252        None
1253    }
1254}