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