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