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