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