1use crate::tree::{
4 cached_state::CachedStateProvider,
5 error::{InsertBlockError, InsertBlockErrorKind, InsertPayloadError},
6 instrumented_state::InstrumentedStateProvider,
7 payload_processor::{executor::WorkloadExecutor, PayloadProcessor},
8 precompile_cache::{CachedPrecompile, CachedPrecompileMetrics, PrecompileCacheMap},
9 sparse_trie::StateRootComputeOutcome,
10 EngineApiMetrics, EngineApiTreeState, ExecutionEnv, PayloadHandle, StateProviderBuilder,
11 StateProviderDatabase, TreeConfig,
12};
13use alloy_consensus::transaction::Either;
14use alloy_eip7928::BlockAccessList;
15use alloy_eips::{eip1898::BlockWithParent, NumHash};
16use alloy_evm::Evm;
17use alloy_primitives::B256;
18use rayon::prelude::*;
19use reth_chain_state::{CanonicalInMemoryState, DeferredTrieData, ExecutedBlock};
20use reth_consensus::{ConsensusError, FullConsensus};
21use reth_engine_primitives::{
22 ConfigureEngineEvm, ExecutableTxIterator, ExecutionPayload, InvalidBlockHook, PayloadValidator,
23};
24use reth_errors::{BlockExecutionError, ProviderResult};
25use reth_evm::{
26 block::BlockExecutor, execute::ExecutableTxFor, ConfigureEvm, EvmEnvFor, ExecutionCtxFor,
27 SpecFor,
28};
29use reth_payload_primitives::{
30 BuiltPayload, InvalidPayloadAttributesError, NewPayloadError, PayloadTypes,
31};
32use reth_primitives_traits::{
33 AlloyBlockHeader, BlockBody, BlockTy, GotExpected, NodePrimitives, RecoveredBlock, SealedBlock,
34 SealedHeader, SignerRecoverable,
35};
36use reth_provider::{
37 providers::OverlayStateProviderFactory, BlockExecutionOutput, BlockReader,
38 DatabaseProviderFactory, DatabaseProviderROFactory, ExecutionOutcome, HashedPostStateProvider,
39 ProviderError, PruneCheckpointReader, StageCheckpointReader, StateProvider,
40 StateProviderFactory, StateReader, TrieReader,
41};
42use reth_revm::db::State;
43use reth_storage_errors::db::DatabaseError;
44use reth_trie::{updates::TrieUpdates, HashedPostState, StateRoot, TrieInputSorted};
45use reth_trie_parallel::root::{ParallelStateRoot, ParallelStateRootError};
46use revm_primitives::Address;
47use std::{
48 collections::HashMap,
49 panic::{self, AssertUnwindSafe},
50 sync::Arc,
51 time::Instant,
52};
53use tracing::{debug, debug_span, error, info, instrument, trace, warn};
54
55pub struct TreeCtx<'a, N: NodePrimitives> {
60 state: &'a mut EngineApiTreeState<N>,
62 canonical_in_memory_state: &'a CanonicalInMemoryState<N>,
64}
65
66impl<'a, N: NodePrimitives> std::fmt::Debug for TreeCtx<'a, N> {
67 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68 f.debug_struct("TreeCtx")
69 .field("state", &"EngineApiTreeState")
70 .field("canonical_in_memory_state", &self.canonical_in_memory_state)
71 .finish()
72 }
73}
74
75impl<'a, N: NodePrimitives> TreeCtx<'a, N> {
76 pub const fn new(
78 state: &'a mut EngineApiTreeState<N>,
79 canonical_in_memory_state: &'a CanonicalInMemoryState<N>,
80 ) -> Self {
81 Self { state, canonical_in_memory_state }
82 }
83
84 pub const fn state(&self) -> &EngineApiTreeState<N> {
86 &*self.state
87 }
88
89 pub const fn state_mut(&mut self) -> &mut EngineApiTreeState<N> {
91 self.state
92 }
93
94 pub const fn canonical_in_memory_state(&self) -> &'a CanonicalInMemoryState<N> {
96 self.canonical_in_memory_state
97 }
98}
99
100#[derive(derive_more::Debug)]
108pub struct BasicEngineValidator<P, Evm, V>
109where
110 Evm: ConfigureEvm,
111{
112 provider: P,
114 consensus: Arc<dyn FullConsensus<Evm::Primitives, Error = ConsensusError>>,
116 evm_config: Evm,
118 config: TreeConfig,
120 payload_processor: PayloadProcessor<Evm>,
122 precompile_cache_map: PrecompileCacheMap<SpecFor<Evm>>,
124 precompile_cache_metrics: HashMap<alloy_primitives::Address, CachedPrecompileMetrics>,
126 #[debug(skip)]
128 invalid_block_hook: Box<dyn InvalidBlockHook<Evm::Primitives>>,
129 metrics: EngineApiMetrics,
131 validator: V,
133}
134
135impl<N, P, Evm, V> BasicEngineValidator<P, Evm, V>
136where
137 N: NodePrimitives,
138 P: DatabaseProviderFactory<
139 Provider: BlockReader + TrieReader + StageCheckpointReader + PruneCheckpointReader,
140 > + BlockReader<Header = N::BlockHeader>
141 + StateProviderFactory
142 + StateReader
143 + HashedPostStateProvider
144 + Clone
145 + 'static,
146 Evm: ConfigureEvm<Primitives = N> + 'static,
147{
148 #[allow(clippy::too_many_arguments)]
150 pub fn new(
151 provider: P,
152 consensus: Arc<dyn FullConsensus<N, Error = ConsensusError>>,
153 evm_config: Evm,
154 validator: V,
155 config: TreeConfig,
156 invalid_block_hook: Box<dyn InvalidBlockHook<N>>,
157 ) -> Self {
158 let precompile_cache_map = PrecompileCacheMap::default();
159 let payload_processor = PayloadProcessor::new(
160 WorkloadExecutor::default(),
161 evm_config.clone(),
162 &config,
163 precompile_cache_map.clone(),
164 );
165 Self {
166 provider,
167 consensus,
168 evm_config,
169 payload_processor,
170 precompile_cache_map,
171 precompile_cache_metrics: HashMap::new(),
172 config,
173 invalid_block_hook,
174 metrics: EngineApiMetrics::default(),
175 validator,
176 }
177 }
178
179 #[instrument(level = "debug", target = "engine::tree::payload_validator", skip_all)]
181 pub fn convert_to_block<T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
182 &self,
183 input: BlockOrPayload<T>,
184 ) -> Result<SealedBlock<N::Block>, NewPayloadError>
185 where
186 V: PayloadValidator<T, Block = N::Block>,
187 {
188 match input {
189 BlockOrPayload::Payload(payload) => self.validator.convert_payload_to_block(payload),
190 BlockOrPayload::Block(block) => Ok(block),
191 }
192 }
193
194 pub fn evm_env_for<T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
196 &self,
197 input: &BlockOrPayload<T>,
198 ) -> Result<EvmEnvFor<Evm>, Evm::Error>
199 where
200 V: PayloadValidator<T, Block = N::Block>,
201 Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
202 {
203 match input {
204 BlockOrPayload::Payload(payload) => Ok(self.evm_config.evm_env_for_payload(payload)?),
205 BlockOrPayload::Block(block) => Ok(self.evm_config.evm_env(block.header())?),
206 }
207 }
208
209 pub fn tx_iterator_for<'a, T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
211 &'a self,
212 input: &'a BlockOrPayload<T>,
213 ) -> Result<impl ExecutableTxIterator<Evm>, NewPayloadError>
214 where
215 V: PayloadValidator<T, Block = N::Block>,
216 Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
217 {
218 match input {
219 BlockOrPayload::Payload(payload) => {
220 let (iter, convert) = self
221 .evm_config
222 .tx_iterator_for_payload(payload)
223 .map_err(NewPayloadError::other)?
224 .into();
225
226 let iter = Either::Left(iter.into_par_iter().map(Either::Left));
227 let convert = move |tx| {
228 let Either::Left(tx) = tx else { unreachable!() };
229 convert(tx).map(Either::Left).map_err(Either::Left)
230 };
231
232 Ok((iter, Box::new(convert) as Box<dyn Fn(_) -> _ + Send + Sync + 'static>))
234 }
235 BlockOrPayload::Block(block) => {
236 let iter = Either::Right(
237 block.body().clone_transactions().into_par_iter().map(Either::Right),
238 );
239 let convert = move |tx: Either<_, N::SignedTx>| {
240 let Either::Right(tx) = tx else { unreachable!() };
241 tx.try_into_recovered().map(Either::Right).map_err(Either::Right)
242 };
243
244 Ok((iter, Box::new(convert)))
245 }
246 }
247 }
248
249 pub fn execution_ctx_for<'a, T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
251 &self,
252 input: &'a BlockOrPayload<T>,
253 ) -> Result<ExecutionCtxFor<'a, Evm>, Evm::Error>
254 where
255 V: PayloadValidator<T, Block = N::Block>,
256 Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
257 {
258 match input {
259 BlockOrPayload::Payload(payload) => Ok(self.evm_config.context_for_payload(payload)?),
260 BlockOrPayload::Block(block) => Ok(self.evm_config.context_for_block(block)?),
261 }
262 }
263
264 fn handle_execution_error<T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
269 &self,
270 input: BlockOrPayload<T>,
271 execution_err: InsertBlockErrorKind,
272 parent_block: &SealedHeader<N::BlockHeader>,
273 ) -> Result<ExecutedBlock<N>, InsertPayloadError<N::Block>>
274 where
275 V: PayloadValidator<T, Block = N::Block>,
276 {
277 debug!(
278 target: "engine::tree::payload_validator",
279 ?execution_err,
280 block = ?input.num_hash(),
281 "Block execution failed, checking for header validation errors"
282 );
283
284 let block = self.convert_to_block(input)?;
287
288 if let Err(consensus_err) = self.validate_block_inner(&block) {
290 return Err(InsertBlockError::new(block, consensus_err.into()).into())
292 }
293
294 if let Err(consensus_err) =
296 self.consensus.validate_header_against_parent(block.sealed_header(), parent_block)
297 {
298 return Err(InsertBlockError::new(block, consensus_err.into()).into())
300 }
301
302 Err(InsertBlockError::new(block, execution_err).into())
304 }
305
306 #[instrument(
314 level = "debug",
315 target = "engine::tree::payload_validator",
316 skip_all,
317 fields(
318 parent = ?input.parent_hash(),
319 type_name = ?input.type_name(),
320 )
321 )]
322 pub fn validate_block_with_state<T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
323 &mut self,
324 input: BlockOrPayload<T>,
325 mut ctx: TreeCtx<'_, N>,
326 ) -> ValidationOutcome<N, InsertPayloadError<N::Block>>
327 where
328 V: PayloadValidator<T, Block = N::Block>,
329 Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
330 {
331 macro_rules! ensure_ok {
334 ($expr:expr) => {
335 match $expr {
336 Ok(val) => val,
337 Err(e) => {
338 let block = self.convert_to_block(input)?;
339 return Err(InsertBlockError::new(block, e.into()).into())
340 }
341 }
342 };
343 }
344
345 macro_rules! ensure_ok_post_block {
347 ($expr:expr, $block:expr) => {
348 match $expr {
349 Ok(val) => val,
350 Err(e) => {
351 return Err(
352 InsertBlockError::new($block.into_sealed_block(), e.into()).into()
353 )
354 }
355 }
356 };
357 }
358
359 let parent_hash = input.parent_hash();
360 let block_num_hash = input.num_hash();
361
362 trace!(target: "engine::tree::payload_validator", "Fetching block state provider");
363 let _enter =
364 debug_span!(target: "engine::tree::payload_validator", "state provider").entered();
365 let Some(provider_builder) =
366 ensure_ok!(self.state_provider_builder(parent_hash, ctx.state()))
367 else {
368 return Err(InsertBlockError::new(
370 self.convert_to_block(input)?,
371 ProviderError::HeaderNotFound(parent_hash.into()).into(),
372 )
373 .into())
374 };
375 let mut state_provider = ensure_ok!(provider_builder.build());
376 drop(_enter);
377
378 let Some(parent_block) = ensure_ok!(self.sealed_header_by_hash(parent_hash, ctx.state()))
381 else {
382 return Err(InsertBlockError::new(
383 self.convert_to_block(input)?,
384 ProviderError::HeaderNotFound(parent_hash.into()).into(),
385 )
386 .into())
387 };
388
389 let evm_env = debug_span!(target: "engine::tree::payload_validator", "evm env")
390 .in_scope(|| self.evm_env_for(&input))
391 .map_err(NewPayloadError::other)?;
392
393 let env = ExecutionEnv { evm_env, hash: input.hash(), parent_hash: input.parent_hash() };
394
395 let strategy = self.plan_state_root_computation();
397
398 debug!(
399 target: "engine::tree::payload_validator",
400 ?strategy,
401 "Decided which state root algorithm to run"
402 );
403
404 let txs = self.tx_iterator_for(&input)?;
406
407 let block_access_list = ensure_ok!(input
409 .block_access_list()
410 .transpose()
411 .map_err(Box::<dyn std::error::Error + Send + Sync>::from))
413 .map(Arc::new);
414
415 let mut handle = ensure_ok!(self.spawn_payload_processor(
417 env.clone(),
418 txs,
419 provider_builder,
420 parent_hash,
421 ctx.state(),
422 strategy,
423 block_access_list,
424 ));
425
426 if let Some((caches, cache_metrics)) = handle.caches().zip(handle.cache_metrics()) {
429 state_provider =
430 Box::new(CachedStateProvider::new(state_provider, caches, cache_metrics));
431 };
432
433 if self.config.state_provider_metrics() {
434 state_provider = Box::new(InstrumentedStateProvider::new(state_provider, "engine"));
435 }
436
437 let (output, senders) = match self.execute_block(state_provider, env, &input, &mut handle) {
439 Ok(output) => output,
440 Err(err) => return self.handle_execution_error(input, err, &parent_block),
441 };
442
443 handle.stop_prewarming_execution();
445
446 let block = self.convert_to_block(input)?.with_senders(senders);
447
448 let hashed_state = ensure_ok_post_block!(
449 self.validate_post_execution(&block, &parent_block, &output, &mut ctx),
450 block
451 );
452
453 let root_time = Instant::now();
454 let mut maybe_state_root = None;
455
456 match strategy {
457 StateRootStrategy::StateRootTask => {
458 debug!(target: "engine::tree::payload_validator", "Using sparse trie state root algorithm");
459 match handle.state_root() {
460 Ok(StateRootComputeOutcome { state_root, trie_updates }) => {
461 let elapsed = root_time.elapsed();
462 info!(target: "engine::tree::payload_validator", ?state_root, ?elapsed, "State root task finished");
463 if state_root == block.header().state_root() {
465 maybe_state_root = Some((state_root, trie_updates, elapsed))
466 } else {
467 warn!(
468 target: "engine::tree::payload_validator",
469 ?state_root,
470 block_state_root = ?block.header().state_root(),
471 "State root task returned incorrect state root"
472 );
473 }
474 }
475 Err(error) => {
476 debug!(target: "engine::tree::payload_validator", %error, "State root task failed");
477 }
478 }
479 }
480 StateRootStrategy::Parallel => {
481 debug!(target: "engine::tree::payload_validator", "Using parallel state root algorithm");
482 match self.compute_state_root_parallel(
483 block.parent_hash(),
484 &hashed_state,
485 ctx.state(),
486 ) {
487 Ok(result) => {
488 let elapsed = root_time.elapsed();
489 info!(
490 target: "engine::tree::payload_validator",
491 regular_state_root = ?result.0,
492 ?elapsed,
493 "Regular root task finished"
494 );
495 maybe_state_root = Some((result.0, result.1, elapsed));
496 }
497 Err(error) => {
498 debug!(target: "engine::tree::payload_validator", %error, "Parallel state root computation failed");
499 }
500 }
501 }
502 StateRootStrategy::Synchronous => {}
503 }
504
505 let (state_root, trie_output, root_elapsed) = if let Some(maybe_state_root) =
509 maybe_state_root
510 {
511 maybe_state_root
512 } else {
513 if self.config.state_root_fallback() {
515 debug!(target: "engine::tree::payload_validator", "Using state root fallback for testing");
516 } else {
517 warn!(target: "engine::tree::payload_validator", "Failed to compute state root in parallel");
518 self.metrics.block_validation.state_root_parallel_fallback_total.increment(1);
519 }
520
521 let (root, updates) = ensure_ok_post_block!(
522 self.compute_state_root_serial(block.parent_hash(), &hashed_state, ctx.state()),
523 block
524 );
525 (root, updates, root_time.elapsed())
526 };
527
528 self.metrics.block_validation.record_state_root(&trie_output, root_elapsed.as_secs_f64());
529 debug!(target: "engine::tree::payload_validator", ?root_elapsed, "Calculated state root");
530
531 if state_root != block.header().state_root() {
533 self.on_invalid_block(
535 &parent_block,
536 &block,
537 &output,
538 Some((&trie_output, state_root)),
539 ctx.state_mut(),
540 );
541 let block_state_root = block.header().state_root();
542 return Err(InsertBlockError::new(
543 block.into_sealed_block(),
544 ConsensusError::BodyStateRootDiff(
545 GotExpected { got: state_root, expected: block_state_root }.into(),
546 )
547 .into(),
548 )
549 .into())
550 }
551
552 let execution_outcome = Arc::new(ExecutionOutcome::from((output, block_num_hash.number)));
555
556 handle.terminate_caching(Some(Arc::clone(&execution_outcome)));
558
559 Ok(self.spawn_deferred_trie_task(block, execution_outcome, &ctx, hashed_state, trie_output))
560 }
561
562 fn sealed_header_by_hash(
564 &self,
565 hash: B256,
566 state: &EngineApiTreeState<N>,
567 ) -> ProviderResult<Option<SealedHeader<N::BlockHeader>>> {
568 let header = state.tree_state.sealed_header_by_hash(&hash);
570
571 if header.is_some() {
572 Ok(header)
573 } else {
574 self.provider.sealed_header_by_hash(hash)
575 }
576 }
577
578 #[instrument(level = "debug", target = "engine::tree::payload_validator", skip_all)]
581 fn validate_block_inner(&self, block: &SealedBlock<N::Block>) -> Result<(), ConsensusError> {
582 if let Err(e) = self.consensus.validate_header(block.sealed_header()) {
583 error!(target: "engine::tree::payload_validator", ?block, "Failed to validate header {}: {e}", block.hash());
584 return Err(e)
585 }
586
587 if let Err(e) = self.consensus.validate_block_pre_execution(block) {
588 error!(target: "engine::tree::payload_validator", ?block, "Failed to validate block {}: {e}", block.hash());
589 return Err(e)
590 }
591
592 Ok(())
593 }
594
595 #[instrument(level = "debug", target = "engine::tree::payload_validator", skip_all)]
597 fn execute_block<S, Err, T>(
598 &mut self,
599 state_provider: S,
600 env: ExecutionEnv<Evm>,
601 input: &BlockOrPayload<T>,
602 handle: &mut PayloadHandle<impl ExecutableTxFor<Evm>, Err, N::Receipt>,
603 ) -> Result<(BlockExecutionOutput<N::Receipt>, Vec<Address>), InsertBlockErrorKind>
604 where
605 S: StateProvider + Send,
606 Err: core::error::Error + Send + Sync + 'static,
607 V: PayloadValidator<T, Block = N::Block>,
608 T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>,
609 Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
610 {
611 debug!(target: "engine::tree::payload_validator", "Executing block");
612
613 let mut db = State::builder()
614 .with_database(StateProviderDatabase::new(state_provider))
615 .with_bundle_update()
616 .without_state_clear()
617 .build();
618
619 let evm = self.evm_config.evm_with_env(&mut db, env.evm_env.clone());
620 let ctx =
621 self.execution_ctx_for(input).map_err(|e| InsertBlockErrorKind::Other(Box::new(e)))?;
622 let mut executor = self.evm_config.create_executor(evm, ctx);
623
624 if !self.config.precompile_cache_disabled() {
625 executor.evm_mut().precompiles_mut().map_pure_precompiles(|address, precompile| {
627 let metrics = self
628 .precompile_cache_metrics
629 .entry(*address)
630 .or_insert_with(|| CachedPrecompileMetrics::new_with_address(*address))
631 .clone();
632 CachedPrecompile::wrap(
633 precompile,
634 self.precompile_cache_map.cache_for_address(*address),
635 *env.evm_env.spec_id(),
636 Some(metrics),
637 )
638 });
639 }
640
641 let execution_start = Instant::now();
642 let state_hook = Box::new(handle.state_hook());
643 let (output, senders) = self.metrics.execute_metered(
644 executor,
645 handle.iter_transactions().map(|res| res.map_err(BlockExecutionError::other)),
646 input.transaction_count(),
647 state_hook,
648 )?;
649 let execution_finish = Instant::now();
650 let execution_time = execution_finish.duration_since(execution_start);
651 debug!(target: "engine::tree::payload_validator", elapsed = ?execution_time, "Executed block");
652 Ok((output, senders))
653 }
654
655 #[instrument(level = "debug", target = "engine::tree::payload_validator", skip_all)]
662 fn compute_state_root_parallel(
663 &self,
664 parent_hash: B256,
665 hashed_state: &HashedPostState,
666 state: &EngineApiTreeState<N>,
667 ) -> Result<(B256, TrieUpdates), ParallelStateRootError> {
668 let (mut input, block_hash) = self.compute_trie_input(parent_hash, state)?;
669
670 input.prefix_sets.extend(hashed_state.construct_prefix_sets());
672 let sorted_hashed_state = hashed_state.clone_into_sorted();
673 Arc::make_mut(&mut input.state).extend_ref(&sorted_hashed_state);
674
675 let TrieInputSorted { nodes, state, prefix_sets: prefix_sets_mut } = input;
676
677 let factory = OverlayStateProviderFactory::new(self.provider.clone())
678 .with_block_hash(Some(block_hash))
679 .with_trie_overlay(Some(nodes))
680 .with_hashed_state_overlay(Some(state));
681
682 let prefix_sets = prefix_sets_mut.freeze();
686
687 ParallelStateRoot::new(factory, prefix_sets).incremental_root_with_updates()
688 }
689
690 fn compute_state_root_serial(
692 &self,
693 parent_hash: B256,
694 hashed_state: &HashedPostState,
695 state: &EngineApiTreeState<N>,
696 ) -> ProviderResult<(B256, TrieUpdates)> {
697 let (mut input, block_hash) = self.compute_trie_input(parent_hash, state)?;
698
699 input.prefix_sets.extend(hashed_state.construct_prefix_sets());
701 let sorted_hashed_state = hashed_state.clone_into_sorted();
702 Arc::make_mut(&mut input.state).extend_ref(&sorted_hashed_state);
703
704 let TrieInputSorted { nodes, state, .. } = input;
705 let prefix_sets = hashed_state.construct_prefix_sets();
706
707 let factory = OverlayStateProviderFactory::new(self.provider.clone())
708 .with_block_hash(Some(block_hash))
709 .with_trie_overlay(Some(nodes))
710 .with_hashed_state_overlay(Some(state));
711
712 let provider = factory.database_provider_ro()?;
713
714 Ok(StateRoot::new(&provider, &provider)
715 .with_prefix_sets(prefix_sets.freeze())
716 .root_with_updates()
717 .map_err(Into::<DatabaseError>::into)?)
718 }
719
720 #[instrument(level = "debug", target = "engine::tree::payload_validator", skip_all)]
727 fn validate_post_execution<T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
728 &self,
729 block: &RecoveredBlock<N::Block>,
730 parent_block: &SealedHeader<N::BlockHeader>,
731 output: &BlockExecutionOutput<N::Receipt>,
732 ctx: &mut TreeCtx<'_, N>,
733 ) -> Result<HashedPostState, InsertBlockErrorKind>
734 where
735 V: PayloadValidator<T, Block = N::Block>,
736 {
737 let start = Instant::now();
738
739 trace!(target: "engine::tree::payload_validator", block=?block.num_hash(), "Validating block consensus");
740 if let Err(e) = self.validate_block_inner(block) {
742 return Err(e.into())
743 }
744
745 let _enter = debug_span!(target: "engine::tree::payload_validator", "validate_header_against_parent").entered();
747 if let Err(e) =
748 self.consensus.validate_header_against_parent(block.sealed_header(), parent_block)
749 {
750 warn!(target: "engine::tree::payload_validator", ?block, "Failed to validate header {} against parent: {e}", block.hash());
751 return Err(e.into())
752 }
753 drop(_enter);
754
755 let _enter =
757 debug_span!(target: "engine::tree::payload_validator", "validate_block_post_execution")
758 .entered();
759 if let Err(err) = self.consensus.validate_block_post_execution(block, output) {
760 self.on_invalid_block(parent_block, block, output, None, ctx.state_mut());
762 return Err(err.into())
763 }
764 drop(_enter);
765
766 let _enter =
767 debug_span!(target: "engine::tree::payload_validator", "hashed_post_state").entered();
768 let hashed_state = self.provider.hashed_post_state(&output.state);
769 drop(_enter);
770
771 let _enter = debug_span!(target: "engine::tree::payload_validator", "validate_block_post_execution_with_hashed_state").entered();
772 if let Err(err) =
773 self.validator.validate_block_post_execution_with_hashed_state(&hashed_state, block)
774 {
775 self.on_invalid_block(parent_block, block, output, None, ctx.state_mut());
777 return Err(err.into())
778 }
779
780 self.metrics
782 .block_validation
783 .post_execution_validation_duration
784 .record(start.elapsed().as_secs_f64());
785
786 Ok(hashed_state)
787 }
788
789 #[allow(clippy::too_many_arguments)]
800 #[instrument(
801 level = "debug",
802 target = "engine::tree::payload_validator",
803 skip_all,
804 fields(strategy)
805 )]
806 fn spawn_payload_processor<T: ExecutableTxIterator<Evm>>(
807 &mut self,
808 env: ExecutionEnv<Evm>,
809 txs: T,
810 provider_builder: StateProviderBuilder<N, P>,
811 parent_hash: B256,
812 state: &EngineApiTreeState<N>,
813 strategy: StateRootStrategy,
814 block_access_list: Option<Arc<BlockAccessList>>,
815 ) -> Result<
816 PayloadHandle<
817 impl ExecutableTxFor<Evm> + use<N, P, Evm, V, T>,
818 impl core::error::Error + Send + Sync + 'static + use<N, P, Evm, V, T>,
819 N::Receipt,
820 >,
821 InsertBlockErrorKind,
822 > {
823 match strategy {
824 StateRootStrategy::StateRootTask => {
825 let trie_input_start = Instant::now();
827 let (trie_input, block_hash) = self.compute_trie_input(parent_hash, state)?;
828
829 let TrieInputSorted { nodes, state, .. } = trie_input;
831
832 let multiproof_provider_factory =
833 OverlayStateProviderFactory::new(self.provider.clone())
834 .with_block_hash(Some(block_hash))
835 .with_trie_overlay(Some(nodes))
836 .with_hashed_state_overlay(Some(state));
837
838 self.metrics
840 .block_validation
841 .trie_input_duration
842 .record(trie_input_start.elapsed().as_secs_f64());
843
844 let spawn_start = Instant::now();
845
846 let handle = self.payload_processor.spawn(
847 env,
848 txs,
849 provider_builder,
850 multiproof_provider_factory,
851 &self.config,
852 block_access_list,
853 );
854
855 self.metrics
857 .block_validation
858 .spawn_payload_processor
859 .record(spawn_start.elapsed().as_secs_f64());
860
861 Ok(handle)
862 }
863 StateRootStrategy::Parallel | StateRootStrategy::Synchronous => {
864 let start = Instant::now();
865 let handle = self.payload_processor.spawn_cache_exclusive(
866 env,
867 txs,
868 provider_builder,
869 block_access_list,
870 );
871
872 self.metrics
874 .block_validation
875 .spawn_payload_processor
876 .record(start.elapsed().as_secs_f64());
877
878 Ok(handle)
879 }
880 }
881 }
882
883 fn state_provider_builder(
888 &self,
889 hash: B256,
890 state: &EngineApiTreeState<N>,
891 ) -> ProviderResult<Option<StateProviderBuilder<N, P>>> {
892 if let Some((historical, blocks)) = state.tree_state.blocks_by_hash(hash) {
893 debug!(target: "engine::tree::payload_validator", %hash, %historical, "found canonical state for block in memory, creating provider builder");
894 return Ok(Some(StateProviderBuilder::new(
896 self.provider.clone(),
897 historical,
898 Some(blocks),
899 )))
900 }
901
902 if let Some(header) = self.provider.header(hash)? {
904 debug!(target: "engine::tree::payload_validator", %hash, number = %header.number(), "found canonical state for block in database, creating provider builder");
905 return Ok(Some(StateProviderBuilder::new(self.provider.clone(), hash, None)))
908 }
909
910 debug!(target: "engine::tree::payload_validator", %hash, "no canonical state found for block");
911 Ok(None)
912 }
913
914 const fn plan_state_root_computation(&self) -> StateRootStrategy {
919 if self.config.state_root_fallback() {
920 StateRootStrategy::Synchronous
921 } else if self.config.use_state_root_task() {
922 StateRootStrategy::StateRootTask
923 } else {
924 StateRootStrategy::Parallel
925 }
926 }
927
928 fn on_invalid_block(
930 &self,
931 parent_header: &SealedHeader<N::BlockHeader>,
932 block: &RecoveredBlock<N::Block>,
933 output: &BlockExecutionOutput<N::Receipt>,
934 trie_updates: Option<(&TrieUpdates, B256)>,
935 state: &mut EngineApiTreeState<N>,
936 ) {
937 if state.invalid_headers.get(&block.hash()).is_some() {
938 return
940 }
941 self.invalid_block_hook.on_invalid_block(parent_header, block, output, trie_updates);
942 }
943
944 #[instrument(
961 level = "debug",
962 target = "engine::tree::payload_validator",
963 skip_all,
964 fields(parent_hash)
965 )]
966 fn compute_trie_input(
967 &self,
968 parent_hash: B256,
969 state: &EngineApiTreeState<N>,
970 ) -> ProviderResult<(TrieInputSorted, B256)> {
971 let wait_start = Instant::now();
972 let (block_hash, blocks) =
973 state.tree_state.blocks_by_hash(parent_hash).unwrap_or_else(|| (parent_hash, vec![]));
974
975 if let Some(tip_block) = blocks.first() {
979 let data = tip_block.trie_data();
980 if let (Some(anchor_hash), Some(trie_input)) =
981 (data.anchor_hash(), data.trie_input().cloned()) &&
982 anchor_hash == block_hash
983 {
984 trace!(target: "engine::tree::payload_validator", %block_hash,"Reusing trie input with matching anchor hash");
985 self.metrics
986 .block_validation
987 .deferred_trie_wait_duration
988 .record(wait_start.elapsed().as_secs_f64());
989 return Ok(((*trie_input).clone(), block_hash));
990 }
991 }
992
993 if blocks.is_empty() {
994 debug!(target: "engine::tree::payload_validator", "Parent found on disk");
995 } else {
996 debug!(target: "engine::tree::payload_validator", historical = ?block_hash, blocks = blocks.len(), "Parent found in memory");
997 }
998
999 let input = Self::merge_overlay_trie_input(&blocks);
1001
1002 self.metrics
1003 .block_validation
1004 .deferred_trie_wait_duration
1005 .record(wait_start.elapsed().as_secs_f64());
1006 Ok((input, block_hash))
1007 }
1008
1009 fn merge_overlay_trie_input(blocks: &[ExecutedBlock<N>]) -> TrieInputSorted {
1016 let mut input = TrieInputSorted::default();
1017 let mut blocks_iter = blocks.iter().rev().peekable();
1018
1019 if let Some(first) = blocks_iter.next() {
1020 let data = first.trie_data();
1021 input.state = data.hashed_state;
1022 input.nodes = data.trie_updates;
1023
1024 if blocks_iter.peek().is_some() {
1026 let state_mut = Arc::make_mut(&mut input.state);
1027 let nodes_mut = Arc::make_mut(&mut input.nodes);
1028 for block in blocks_iter {
1029 let data = block.trie_data();
1030 state_mut.extend_ref(data.hashed_state.as_ref());
1031 nodes_mut.extend_ref(data.trie_updates.as_ref());
1032 }
1033 }
1034 }
1035
1036 input
1037 }
1038
1039 fn spawn_deferred_trie_task(
1056 &self,
1057 block: RecoveredBlock<N::Block>,
1058 execution_outcome: Arc<ExecutionOutcome<N::Receipt>>,
1059 ctx: &TreeCtx<'_, N>,
1060 hashed_state: HashedPostState,
1061 trie_output: TrieUpdates,
1062 ) -> ExecutedBlock<N> {
1063 let (anchor_hash, overlay_blocks) = ctx
1065 .state()
1066 .tree_state
1067 .blocks_by_hash(block.parent_hash())
1068 .unwrap_or_else(|| (block.parent_hash(), Vec::new()));
1069
1070 let ancestors: Vec<DeferredTrieData> =
1073 overlay_blocks.iter().rev().map(|b| b.trie_data_handle()).collect();
1074
1075 let deferred_trie_data = DeferredTrieData::pending(
1077 Arc::new(hashed_state),
1078 Arc::new(trie_output),
1079 anchor_hash,
1080 ancestors,
1081 );
1082 let deferred_handle_task = deferred_trie_data.clone();
1083 let deferred_compute_duration =
1084 self.metrics.block_validation.deferred_trie_compute_duration.clone();
1085
1086 let compute_trie_input_task = move || {
1089 let result = panic::catch_unwind(AssertUnwindSafe(|| {
1090 let compute_start = Instant::now();
1091 let _ = deferred_handle_task.wait_cloned();
1092 deferred_compute_duration.record(compute_start.elapsed().as_secs_f64());
1093 }));
1094
1095 if result.is_err() {
1096 error!(
1097 target: "engine::tree::payload_validator",
1098 "Deferred trie task panicked; fallback computation will be used when trie data is accessed"
1099 );
1100 }
1101 };
1102
1103 self.payload_processor.executor().spawn_blocking(compute_trie_input_task);
1105
1106 ExecutedBlock::with_deferred_trie_data(
1107 Arc::new(block),
1108 execution_outcome,
1109 deferred_trie_data,
1110 )
1111 }
1112}
1113
1114pub type ValidationOutcome<N, E = InsertPayloadError<BlockTy<N>>> = Result<ExecutedBlock<N>, E>;
1116
1117#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1119enum StateRootStrategy {
1120 StateRootTask,
1122 Parallel,
1124 Synchronous,
1126}
1127
1128pub trait EngineValidator<
1132 Types: PayloadTypes,
1133 N: NodePrimitives = <<Types as PayloadTypes>::BuiltPayload as BuiltPayload>::Primitives,
1134>: Send + Sync + 'static
1135{
1136 fn validate_payload_attributes_against_header(
1146 &self,
1147 attr: &Types::PayloadAttributes,
1148 header: &N::BlockHeader,
1149 ) -> Result<(), InvalidPayloadAttributesError>;
1150
1151 fn convert_payload_to_block(
1160 &self,
1161 payload: Types::ExecutionData,
1162 ) -> Result<SealedBlock<N::Block>, NewPayloadError>;
1163
1164 fn validate_payload(
1166 &mut self,
1167 payload: Types::ExecutionData,
1168 ctx: TreeCtx<'_, N>,
1169 ) -> ValidationOutcome<N>;
1170
1171 fn validate_block(
1173 &mut self,
1174 block: SealedBlock<N::Block>,
1175 ctx: TreeCtx<'_, N>,
1176 ) -> ValidationOutcome<N>;
1177
1178 fn on_inserted_executed_block(&self, block: ExecutedBlock<N>);
1183}
1184
1185impl<N, Types, P, Evm, V> EngineValidator<Types> for BasicEngineValidator<P, Evm, V>
1186where
1187 P: DatabaseProviderFactory<
1188 Provider: BlockReader + TrieReader + StageCheckpointReader + PruneCheckpointReader,
1189 > + BlockReader<Header = N::BlockHeader>
1190 + StateProviderFactory
1191 + StateReader
1192 + HashedPostStateProvider
1193 + Clone
1194 + 'static,
1195 N: NodePrimitives,
1196 V: PayloadValidator<Types, Block = N::Block>,
1197 Evm: ConfigureEngineEvm<Types::ExecutionData, Primitives = N> + 'static,
1198 Types: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>,
1199{
1200 fn validate_payload_attributes_against_header(
1201 &self,
1202 attr: &Types::PayloadAttributes,
1203 header: &N::BlockHeader,
1204 ) -> Result<(), InvalidPayloadAttributesError> {
1205 self.validator.validate_payload_attributes_against_header(attr, header)
1206 }
1207
1208 fn convert_payload_to_block(
1209 &self,
1210 payload: Types::ExecutionData,
1211 ) -> Result<SealedBlock<N::Block>, NewPayloadError> {
1212 let block = self.validator.convert_payload_to_block(payload)?;
1213 Ok(block)
1214 }
1215
1216 fn validate_payload(
1217 &mut self,
1218 payload: Types::ExecutionData,
1219 ctx: TreeCtx<'_, N>,
1220 ) -> ValidationOutcome<N> {
1221 self.validate_block_with_state(BlockOrPayload::Payload(payload), ctx)
1222 }
1223
1224 fn validate_block(
1225 &mut self,
1226 block: SealedBlock<N::Block>,
1227 ctx: TreeCtx<'_, N>,
1228 ) -> ValidationOutcome<N> {
1229 self.validate_block_with_state(BlockOrPayload::Block(block), ctx)
1230 }
1231
1232 fn on_inserted_executed_block(&self, block: ExecutedBlock<N>) {
1233 self.payload_processor.on_inserted_executed_block(
1234 block.recovered_block.block_with_parent(),
1235 block.execution_output.state(),
1236 );
1237 }
1238}
1239
1240#[derive(Debug)]
1242pub enum BlockOrPayload<T: PayloadTypes> {
1243 Payload(T::ExecutionData),
1245 Block(SealedBlock<BlockTy<<T::BuiltPayload as BuiltPayload>::Primitives>>),
1247}
1248
1249impl<T: PayloadTypes> BlockOrPayload<T> {
1250 pub fn hash(&self) -> B256 {
1252 match self {
1253 Self::Payload(payload) => payload.block_hash(),
1254 Self::Block(block) => block.hash(),
1255 }
1256 }
1257
1258 pub fn num_hash(&self) -> NumHash {
1260 match self {
1261 Self::Payload(payload) => payload.num_hash(),
1262 Self::Block(block) => block.num_hash(),
1263 }
1264 }
1265
1266 pub fn parent_hash(&self) -> B256 {
1268 match self {
1269 Self::Payload(payload) => payload.parent_hash(),
1270 Self::Block(block) => block.parent_hash(),
1271 }
1272 }
1273
1274 pub fn block_with_parent(&self) -> BlockWithParent {
1276 match self {
1277 Self::Payload(payload) => payload.block_with_parent(),
1278 Self::Block(block) => block.block_with_parent(),
1279 }
1280 }
1281
1282 pub const fn type_name(&self) -> &'static str {
1284 match self {
1285 Self::Payload(_) => "payload",
1286 Self::Block(_) => "block",
1287 }
1288 }
1289
1290 pub const fn block_access_list(&self) -> Option<Result<BlockAccessList, alloy_rlp::Error>> {
1292 None
1294 }
1295
1296 pub fn transaction_count(&self) -> usize
1298 where
1299 T::ExecutionData: ExecutionPayload,
1300 {
1301 match self {
1302 Self::Payload(payload) => payload.transaction_count(),
1303 Self::Block(block) => block.transaction_count(),
1304 }
1305 }
1306}