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 reth_chain_state::{CanonicalInMemoryState, DeferredTrieData, ExecutedBlock};
19use reth_consensus::{ConsensusError, FullConsensus};
20use reth_engine_primitives::{
21 ConfigureEngineEvm, ExecutableTxIterator, ExecutionPayload, InvalidBlockHook, PayloadValidator,
22};
23use reth_errors::{BlockExecutionError, ProviderResult};
24use reth_evm::{
25 block::BlockExecutor, execute::ExecutableTxFor, ConfigureEvm, EvmEnvFor, ExecutionCtxFor,
26 SpecFor,
27};
28use reth_payload_primitives::{
29 BuiltPayload, InvalidPayloadAttributesError, NewPayloadError, PayloadTypes,
30};
31use reth_primitives_traits::{
32 AlloyBlockHeader, BlockBody, BlockTy, GotExpected, NodePrimitives, RecoveredBlock, SealedBlock,
33 SealedHeader, SignerRecoverable,
34};
35use reth_provider::{
36 providers::OverlayStateProviderFactory, BlockExecutionOutput, BlockReader,
37 DatabaseProviderFactory, ExecutionOutcome, HashedPostStateProvider, ProviderError,
38 PruneCheckpointReader, StageCheckpointReader, StateProvider, StateProviderFactory, StateReader,
39 StateRootProvider, TrieReader,
40};
41use reth_revm::db::State;
42use reth_trie::{updates::TrieUpdates, HashedPostState, TrieInputSorted};
43use reth_trie_parallel::root::{ParallelStateRoot, ParallelStateRootError};
44use revm_primitives::Address;
45use std::{
46 collections::HashMap,
47 panic::{self, AssertUnwindSafe},
48 sync::Arc,
49 time::Instant,
50};
51use tracing::{debug, debug_span, error, info, instrument, trace, warn};
52
53pub struct TreeCtx<'a, N: NodePrimitives> {
58 state: &'a mut EngineApiTreeState<N>,
60 canonical_in_memory_state: &'a CanonicalInMemoryState<N>,
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("canonical_in_memory_state", &self.canonical_in_memory_state)
69 .finish()
70 }
71}
72
73impl<'a, N: NodePrimitives> TreeCtx<'a, N> {
74 pub const fn new(
76 state: &'a mut EngineApiTreeState<N>,
77 canonical_in_memory_state: &'a CanonicalInMemoryState<N>,
78 ) -> Self {
79 Self { state, canonical_in_memory_state }
80 }
81
82 pub const fn state(&self) -> &EngineApiTreeState<N> {
84 &*self.state
85 }
86
87 pub const fn state_mut(&mut self) -> &mut EngineApiTreeState<N> {
89 self.state
90 }
91
92 pub const fn canonical_in_memory_state(&self) -> &'a CanonicalInMemoryState<N> {
94 self.canonical_in_memory_state
95 }
96}
97
98#[derive(derive_more::Debug)]
106pub struct BasicEngineValidator<P, Evm, V>
107where
108 Evm: ConfigureEvm,
109{
110 provider: P,
112 consensus: Arc<dyn FullConsensus<Evm::Primitives, Error = ConsensusError>>,
114 evm_config: Evm,
116 config: TreeConfig,
118 payload_processor: PayloadProcessor<Evm>,
120 precompile_cache_map: PrecompileCacheMap<SpecFor<Evm>>,
122 precompile_cache_metrics: HashMap<alloy_primitives::Address, CachedPrecompileMetrics>,
124 #[debug(skip)]
126 invalid_block_hook: Box<dyn InvalidBlockHook<Evm::Primitives>>,
127 metrics: EngineApiMetrics,
129 validator: V,
131}
132
133impl<N, P, Evm, V> BasicEngineValidator<P, Evm, V>
134where
135 N: NodePrimitives,
136 P: DatabaseProviderFactory<
137 Provider: BlockReader + TrieReader + StageCheckpointReader + PruneCheckpointReader,
138 > + BlockReader<Header = N::BlockHeader>
139 + StateProviderFactory
140 + StateReader
141 + HashedPostStateProvider
142 + Clone
143 + 'static,
144 Evm: ConfigureEvm<Primitives = N> + 'static,
145{
146 #[allow(clippy::too_many_arguments)]
148 pub fn new(
149 provider: P,
150 consensus: Arc<dyn FullConsensus<N, Error = ConsensusError>>,
151 evm_config: Evm,
152 validator: V,
153 config: TreeConfig,
154 invalid_block_hook: Box<dyn InvalidBlockHook<N>>,
155 ) -> Self {
156 let precompile_cache_map = PrecompileCacheMap::default();
157 let payload_processor = PayloadProcessor::new(
158 WorkloadExecutor::default(),
159 evm_config.clone(),
160 &config,
161 precompile_cache_map.clone(),
162 );
163 Self {
164 provider,
165 consensus,
166 evm_config,
167 payload_processor,
168 precompile_cache_map,
169 precompile_cache_metrics: HashMap::new(),
170 config,
171 invalid_block_hook,
172 metrics: EngineApiMetrics::default(),
173 validator,
174 }
175 }
176
177 #[instrument(level = "debug", target = "engine::tree::payload_validator", skip_all)]
179 pub fn convert_to_block<T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
180 &self,
181 input: BlockOrPayload<T>,
182 ) -> Result<SealedBlock<N::Block>, NewPayloadError>
183 where
184 V: PayloadValidator<T, Block = N::Block>,
185 {
186 match input {
187 BlockOrPayload::Payload(payload) => self.validator.convert_payload_to_block(payload),
188 BlockOrPayload::Block(block) => Ok(block),
189 }
190 }
191
192 pub fn evm_env_for<T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
194 &self,
195 input: &BlockOrPayload<T>,
196 ) -> Result<EvmEnvFor<Evm>, Evm::Error>
197 where
198 V: PayloadValidator<T, Block = N::Block>,
199 Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
200 {
201 match input {
202 BlockOrPayload::Payload(payload) => Ok(self.evm_config.evm_env_for_payload(payload)?),
203 BlockOrPayload::Block(block) => Ok(self.evm_config.evm_env(block.header())?),
204 }
205 }
206
207 pub fn tx_iterator_for<'a, T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
209 &'a self,
210 input: &'a BlockOrPayload<T>,
211 ) -> Result<impl ExecutableTxIterator<Evm>, NewPayloadError>
212 where
213 V: PayloadValidator<T, Block = N::Block>,
214 Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
215 {
216 match input {
217 BlockOrPayload::Payload(payload) => {
218 let (iter, convert) = self
219 .evm_config
220 .tx_iterator_for_payload(payload)
221 .map_err(NewPayloadError::other)?
222 .into();
223
224 let iter = Either::Left(iter.into_iter().map(Either::Left));
225 let convert = move |tx| {
226 let Either::Left(tx) = tx else { unreachable!() };
227 convert(tx).map(Either::Left).map_err(Either::Left)
228 };
229
230 Ok((iter, Box::new(convert) as Box<dyn Fn(_) -> _ + Send + Sync + 'static>))
232 }
233 BlockOrPayload::Block(block) => {
234 let iter =
235 Either::Right(block.body().clone_transactions().into_iter().map(Either::Right));
236 let convert = move |tx: Either<_, N::SignedTx>| {
237 let Either::Right(tx) = tx else { unreachable!() };
238 tx.try_into_recovered().map(Either::Right).map_err(Either::Right)
239 };
240
241 Ok((iter, Box::new(convert)))
242 }
243 }
244 }
245
246 pub fn execution_ctx_for<'a, T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
248 &self,
249 input: &'a BlockOrPayload<T>,
250 ) -> Result<ExecutionCtxFor<'a, Evm>, Evm::Error>
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(self.evm_config.context_for_payload(payload)?),
257 BlockOrPayload::Block(block) => Ok(self.evm_config.context_for_block(block)?),
258 }
259 }
260
261 fn handle_execution_error<T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
266 &self,
267 input: BlockOrPayload<T>,
268 execution_err: InsertBlockErrorKind,
269 parent_block: &SealedHeader<N::BlockHeader>,
270 ) -> Result<ExecutedBlock<N>, InsertPayloadError<N::Block>>
271 where
272 V: PayloadValidator<T, Block = N::Block>,
273 {
274 debug!(
275 target: "engine::tree::payload_validator",
276 ?execution_err,
277 block = ?input.num_hash(),
278 "Block execution failed, checking for header validation errors"
279 );
280
281 let block = self.convert_to_block(input)?;
284
285 if let Err(consensus_err) = self.validate_block_inner(&block) {
287 return Err(InsertBlockError::new(block, consensus_err.into()).into())
289 }
290
291 if let Err(consensus_err) =
293 self.consensus.validate_header_against_parent(block.sealed_header(), parent_block)
294 {
295 return Err(InsertBlockError::new(block, consensus_err.into()).into())
297 }
298
299 Err(InsertBlockError::new(block, execution_err).into())
301 }
302
303 #[instrument(
311 level = "debug",
312 target = "engine::tree::payload_validator",
313 skip_all,
314 fields(
315 parent = ?input.parent_hash(),
316 type_name = ?input.type_name(),
317 )
318 )]
319 pub fn validate_block_with_state<T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
320 &mut self,
321 input: BlockOrPayload<T>,
322 mut ctx: TreeCtx<'_, N>,
323 ) -> ValidationOutcome<N, InsertPayloadError<N::Block>>
324 where
325 V: PayloadValidator<T, Block = N::Block>,
326 Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
327 {
328 macro_rules! ensure_ok {
331 ($expr:expr) => {
332 match $expr {
333 Ok(val) => val,
334 Err(e) => {
335 let block = self.convert_to_block(input)?;
336 return Err(InsertBlockError::new(block, e.into()).into())
337 }
338 }
339 };
340 }
341
342 macro_rules! ensure_ok_post_block {
344 ($expr:expr, $block:expr) => {
345 match $expr {
346 Ok(val) => val,
347 Err(e) => {
348 return Err(
349 InsertBlockError::new($block.into_sealed_block(), e.into()).into()
350 )
351 }
352 }
353 };
354 }
355
356 let parent_hash = input.parent_hash();
357 let block_num_hash = input.num_hash();
358
359 trace!(target: "engine::tree::payload_validator", "Fetching block state provider");
360 let _enter =
361 debug_span!(target: "engine::tree::payload_validator", "state provider").entered();
362 let Some(provider_builder) =
363 ensure_ok!(self.state_provider_builder(parent_hash, ctx.state()))
364 else {
365 return Err(InsertBlockError::new(
367 self.convert_to_block(input)?,
368 ProviderError::HeaderNotFound(parent_hash.into()).into(),
369 )
370 .into())
371 };
372 let mut state_provider = ensure_ok!(provider_builder.build());
373 drop(_enter);
374
375 let Some(parent_block) = ensure_ok!(self.sealed_header_by_hash(parent_hash, ctx.state()))
377 else {
378 return Err(InsertBlockError::new(
379 self.convert_to_block(input)?,
380 ProviderError::HeaderNotFound(parent_hash.into()).into(),
381 )
382 .into())
383 };
384
385 let evm_env = debug_span!(target: "engine::tree::payload_validator", "evm env")
386 .in_scope(|| self.evm_env_for(&input))
387 .map_err(NewPayloadError::other)?;
388
389 let env = ExecutionEnv { evm_env, hash: input.hash(), parent_hash: input.parent_hash() };
390
391 let strategy = self.plan_state_root_computation();
393
394 debug!(
395 target: "engine::tree::payload_validator",
396 ?strategy,
397 "Decided which state root algorithm to run"
398 );
399
400 let txs = self.tx_iterator_for(&input)?;
402
403 let mut handle = ensure_ok!(self.spawn_payload_processor(
405 env.clone(),
406 txs,
407 provider_builder,
408 parent_hash,
409 ctx.state(),
410 strategy,
411 ));
412
413 if let Some((caches, cache_metrics)) = handle.caches().zip(handle.cache_metrics()) {
416 state_provider = Box::new(CachedStateProvider::new_with_caches(
417 state_provider,
418 caches,
419 cache_metrics,
420 ));
421 };
422
423 let (output, senders) = match if self.config.state_provider_metrics() {
425 let state_provider =
426 InstrumentedStateProvider::from_state_provider(&state_provider, "engine");
427 self.execute_block(&state_provider, env, &input, &mut handle)
428 } else {
429 self.execute_block(&state_provider, env, &input, &mut handle)
430 } {
431 Ok(output) => output,
432 Err(err) => return self.handle_execution_error(input, err, &parent_block),
433 };
434
435 handle.stop_prewarming_execution();
437
438 let block = self.convert_to_block(input)?.with_senders(senders);
439
440 let hashed_state = ensure_ok_post_block!(
441 self.validate_post_execution(&block, &parent_block, &output, &mut ctx),
442 block
443 );
444
445 let root_time = Instant::now();
446 let mut maybe_state_root = None;
447
448 match strategy {
449 StateRootStrategy::StateRootTask => {
450 debug!(target: "engine::tree::payload_validator", "Using sparse trie state root algorithm");
451 match handle.state_root() {
452 Ok(StateRootComputeOutcome { state_root, trie_updates }) => {
453 let elapsed = root_time.elapsed();
454 info!(target: "engine::tree::payload_validator", ?state_root, ?elapsed, "State root task finished");
455 if state_root == block.header().state_root() {
457 maybe_state_root = Some((state_root, trie_updates, elapsed))
458 } else {
459 warn!(
460 target: "engine::tree::payload_validator",
461 ?state_root,
462 block_state_root = ?block.header().state_root(),
463 "State root task returned incorrect state root"
464 );
465 }
466 }
467 Err(error) => {
468 debug!(target: "engine::tree::payload_validator", %error, "State root task failed");
469 }
470 }
471 }
472 StateRootStrategy::Parallel => {
473 debug!(target: "engine::tree::payload_validator", "Using parallel state root algorithm");
474 match self.compute_state_root_parallel(
475 block.parent_hash(),
476 &hashed_state,
477 ctx.state(),
478 ) {
479 Ok(result) => {
480 let elapsed = root_time.elapsed();
481 info!(
482 target: "engine::tree::payload_validator",
483 regular_state_root = ?result.0,
484 ?elapsed,
485 "Regular root task finished"
486 );
487 maybe_state_root = Some((result.0, result.1, elapsed));
488 }
489 Err(error) => {
490 debug!(target: "engine::tree::payload_validator", %error, "Parallel state root computation failed");
491 }
492 }
493 }
494 StateRootStrategy::Synchronous => {}
495 }
496
497 let (state_root, trie_output, root_elapsed) = if let Some(maybe_state_root) =
501 maybe_state_root
502 {
503 maybe_state_root
504 } else {
505 if self.config.state_root_fallback() {
507 debug!(target: "engine::tree::payload_validator", "Using state root fallback for testing");
508 } else {
509 warn!(target: "engine::tree::payload_validator", "Failed to compute state root in parallel");
510 self.metrics.block_validation.state_root_parallel_fallback_total.increment(1);
511 }
512
513 let (root, updates) = ensure_ok_post_block!(
514 state_provider.state_root_with_updates(hashed_state.clone()),
515 block
516 );
517 (root, updates, root_time.elapsed())
518 };
519
520 self.metrics.block_validation.record_state_root(&trie_output, root_elapsed.as_secs_f64());
521 debug!(target: "engine::tree::payload_validator", ?root_elapsed, "Calculated state root");
522
523 if state_root != block.header().state_root() {
525 self.on_invalid_block(
527 &parent_block,
528 &block,
529 &output,
530 Some((&trie_output, state_root)),
531 ctx.state_mut(),
532 );
533 let block_state_root = block.header().state_root();
534 return Err(InsertBlockError::new(
535 block.into_sealed_block(),
536 ConsensusError::BodyStateRootDiff(
537 GotExpected { got: state_root, expected: block_state_root }.into(),
538 )
539 .into(),
540 )
541 .into())
542 }
543
544 handle.terminate_caching(Some(&output.state));
546
547 Ok(self.spawn_deferred_trie_task(
548 block,
549 output,
550 block_num_hash.number,
551 &ctx,
552 hashed_state,
553 trie_output,
554 ))
555 }
556
557 fn sealed_header_by_hash(
559 &self,
560 hash: B256,
561 state: &EngineApiTreeState<N>,
562 ) -> ProviderResult<Option<SealedHeader<N::BlockHeader>>> {
563 let header = state.tree_state.sealed_header_by_hash(&hash);
565
566 if header.is_some() {
567 Ok(header)
568 } else {
569 self.provider.sealed_header_by_hash(hash)
570 }
571 }
572
573 #[instrument(level = "debug", target = "engine::tree::payload_validator", skip_all)]
576 fn validate_block_inner(&self, block: &SealedBlock<N::Block>) -> Result<(), ConsensusError> {
577 if let Err(e) = self.consensus.validate_header(block.sealed_header()) {
578 error!(target: "engine::tree::payload_validator", ?block, "Failed to validate header {}: {e}", block.hash());
579 return Err(e)
580 }
581
582 if let Err(e) = self.consensus.validate_block_pre_execution(block) {
583 error!(target: "engine::tree::payload_validator", ?block, "Failed to validate block {}: {e}", block.hash());
584 return Err(e)
585 }
586
587 Ok(())
588 }
589
590 #[instrument(level = "debug", target = "engine::tree::payload_validator", skip_all)]
592 fn execute_block<S, Err, T>(
593 &mut self,
594 state_provider: S,
595 env: ExecutionEnv<Evm>,
596 input: &BlockOrPayload<T>,
597 handle: &mut PayloadHandle<impl ExecutableTxFor<Evm>, Err>,
598 ) -> Result<(BlockExecutionOutput<N::Receipt>, Vec<Address>), InsertBlockErrorKind>
599 where
600 S: StateProvider,
601 Err: core::error::Error + Send + Sync + 'static,
602 V: PayloadValidator<T, Block = N::Block>,
603 T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>,
604 Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
605 {
606 debug!(target: "engine::tree::payload_validator", "Executing block");
607
608 let mut db = State::builder()
609 .with_database(StateProviderDatabase::new(&state_provider))
610 .with_bundle_update()
611 .without_state_clear()
612 .build();
613
614 let evm = self.evm_config.evm_with_env(&mut db, env.evm_env.clone());
615 let ctx =
616 self.execution_ctx_for(input).map_err(|e| InsertBlockErrorKind::Other(Box::new(e)))?;
617 let mut executor = self.evm_config.create_executor(evm, ctx);
618
619 if !self.config.precompile_cache_disabled() {
620 executor.evm_mut().precompiles_mut().map_pure_precompiles(|address, precompile| {
622 let metrics = self
623 .precompile_cache_metrics
624 .entry(*address)
625 .or_insert_with(|| CachedPrecompileMetrics::new_with_address(*address))
626 .clone();
627 CachedPrecompile::wrap(
628 precompile,
629 self.precompile_cache_map.cache_for_address(*address),
630 *env.evm_env.spec_id(),
631 Some(metrics),
632 )
633 });
634 }
635
636 let execution_start = Instant::now();
637 let state_hook = Box::new(handle.state_hook());
638 let (output, senders) = self.metrics.execute_metered(
639 executor,
640 handle.iter_transactions().map(|res| res.map_err(BlockExecutionError::other)),
641 state_hook,
642 )?;
643 let execution_finish = Instant::now();
644 let execution_time = execution_finish.duration_since(execution_start);
645 debug!(target: "engine::tree::payload_validator", elapsed = ?execution_time, "Executed block");
646 Ok((output, senders))
647 }
648
649 #[instrument(level = "debug", target = "engine::tree::payload_validator", skip_all)]
658 fn compute_state_root_parallel(
659 &self,
660 parent_hash: B256,
661 hashed_state: &HashedPostState,
662 state: &EngineApiTreeState<N>,
663 ) -> Result<(B256, TrieUpdates), ParallelStateRootError> {
664 let (mut input, block_hash) = self.compute_trie_input(parent_hash, state)?;
665
666 input.prefix_sets.extend(hashed_state.construct_prefix_sets());
668 let sorted_hashed_state = hashed_state.clone_into_sorted();
669 Arc::make_mut(&mut input.state).extend_ref(&sorted_hashed_state);
670
671 let TrieInputSorted { nodes, state, prefix_sets: prefix_sets_mut } = input;
672
673 let factory = OverlayStateProviderFactory::new(self.provider.clone())
674 .with_block_hash(Some(block_hash))
675 .with_trie_overlay(Some(nodes))
676 .with_hashed_state_overlay(Some(state));
677
678 let prefix_sets = prefix_sets_mut.freeze();
682
683 ParallelStateRoot::new(factory, prefix_sets).incremental_root_with_updates()
684 }
685
686 #[instrument(level = "debug", target = "engine::tree::payload_validator", skip_all)]
693 fn validate_post_execution<T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
694 &self,
695 block: &RecoveredBlock<N::Block>,
696 parent_block: &SealedHeader<N::BlockHeader>,
697 output: &BlockExecutionOutput<N::Receipt>,
698 ctx: &mut TreeCtx<'_, N>,
699 ) -> Result<HashedPostState, InsertBlockErrorKind>
700 where
701 V: PayloadValidator<T, Block = N::Block>,
702 {
703 let start = Instant::now();
704
705 trace!(target: "engine::tree::payload_validator", block=?block.num_hash(), "Validating block consensus");
706 if let Err(e) = self.validate_block_inner(block) {
708 return Err(e.into())
709 }
710
711 let _enter = debug_span!(target: "engine::tree::payload_validator", "validate_header_against_parent").entered();
713 if let Err(e) =
714 self.consensus.validate_header_against_parent(block.sealed_header(), parent_block)
715 {
716 warn!(target: "engine::tree::payload_validator", ?block, "Failed to validate header {} against parent: {e}", block.hash());
717 return Err(e.into())
718 }
719 drop(_enter);
720
721 let _enter =
723 debug_span!(target: "engine::tree::payload_validator", "validate_block_post_execution")
724 .entered();
725 if let Err(err) = self.consensus.validate_block_post_execution(block, output) {
726 self.on_invalid_block(parent_block, block, output, None, ctx.state_mut());
728 return Err(err.into())
729 }
730 drop(_enter);
731
732 let _enter =
733 debug_span!(target: "engine::tree::payload_validator", "hashed_post_state").entered();
734 let hashed_state = self.provider.hashed_post_state(&output.state);
735 drop(_enter);
736
737 let _enter = debug_span!(target: "engine::tree::payload_validator", "validate_block_post_execution_with_hashed_state").entered();
738 if let Err(err) =
739 self.validator.validate_block_post_execution_with_hashed_state(&hashed_state, block)
740 {
741 self.on_invalid_block(parent_block, block, output, None, ctx.state_mut());
743 return Err(err.into())
744 }
745
746 self.metrics
748 .block_validation
749 .post_execution_validation_duration
750 .record(start.elapsed().as_secs_f64());
751
752 Ok(hashed_state)
753 }
754
755 #[allow(clippy::too_many_arguments)]
766 #[instrument(
767 level = "debug",
768 target = "engine::tree::payload_validator",
769 skip_all,
770 fields(strategy)
771 )]
772 fn spawn_payload_processor<T: ExecutableTxIterator<Evm>>(
773 &mut self,
774 env: ExecutionEnv<Evm>,
775 txs: T,
776 provider_builder: StateProviderBuilder<N, P>,
777 parent_hash: B256,
778 state: &EngineApiTreeState<N>,
779 strategy: StateRootStrategy,
780 ) -> Result<
781 PayloadHandle<
782 impl ExecutableTxFor<Evm> + use<N, P, Evm, V, T>,
783 impl core::error::Error + Send + Sync + 'static + use<N, P, Evm, V, T>,
784 >,
785 InsertBlockErrorKind,
786 > {
787 match strategy {
788 StateRootStrategy::StateRootTask => {
789 let trie_input_start = Instant::now();
791 let (trie_input, block_hash) = self.compute_trie_input(parent_hash, state)?;
792
793 let TrieInputSorted { nodes, state, .. } = trie_input;
795
796 let multiproof_provider_factory =
797 OverlayStateProviderFactory::new(self.provider.clone())
798 .with_block_hash(Some(block_hash))
799 .with_trie_overlay(Some(nodes))
800 .with_hashed_state_overlay(Some(state));
801
802 self.metrics
804 .block_validation
805 .trie_input_duration
806 .record(trie_input_start.elapsed().as_secs_f64());
807
808 let spawn_start = Instant::now();
809 let handle = self.payload_processor.spawn(
810 env,
811 txs,
812 provider_builder,
813 multiproof_provider_factory,
814 &self.config,
815 );
816
817 self.metrics
819 .block_validation
820 .spawn_payload_processor
821 .record(spawn_start.elapsed().as_secs_f64());
822
823 Ok(handle)
824 }
825 StateRootStrategy::Parallel | StateRootStrategy::Synchronous => {
826 let start = Instant::now();
827 let handle =
828 self.payload_processor.spawn_cache_exclusive(env, txs, provider_builder);
829
830 self.metrics
832 .block_validation
833 .spawn_payload_processor
834 .record(start.elapsed().as_secs_f64());
835
836 Ok(handle)
837 }
838 }
839 }
840
841 fn state_provider_builder(
846 &self,
847 hash: B256,
848 state: &EngineApiTreeState<N>,
849 ) -> ProviderResult<Option<StateProviderBuilder<N, P>>> {
850 if let Some((historical, blocks)) = state.tree_state.blocks_by_hash(hash) {
851 debug!(target: "engine::tree::payload_validator", %hash, %historical, "found canonical state for block in memory, creating provider builder");
852 return Ok(Some(StateProviderBuilder::new(
854 self.provider.clone(),
855 historical,
856 Some(blocks),
857 )))
858 }
859
860 if let Some(header) = self.provider.header(hash)? {
862 debug!(target: "engine::tree::payload_validator", %hash, number = %header.number(), "found canonical state for block in database, creating provider builder");
863 return Ok(Some(StateProviderBuilder::new(self.provider.clone(), hash, None)))
866 }
867
868 debug!(target: "engine::tree::payload_validator", %hash, "no canonical state found for block");
869 Ok(None)
870 }
871
872 const fn plan_state_root_computation(&self) -> StateRootStrategy {
877 if self.config.state_root_fallback() || !self.config.has_enough_parallelism() {
878 StateRootStrategy::Synchronous
879 } else if self.config.use_state_root_task() {
880 StateRootStrategy::StateRootTask
881 } else {
882 StateRootStrategy::Parallel
883 }
884 }
885
886 fn on_invalid_block(
888 &self,
889 parent_header: &SealedHeader<N::BlockHeader>,
890 block: &RecoveredBlock<N::Block>,
891 output: &BlockExecutionOutput<N::Receipt>,
892 trie_updates: Option<(&TrieUpdates, B256)>,
893 state: &mut EngineApiTreeState<N>,
894 ) {
895 if state.invalid_headers.get(&block.hash()).is_some() {
896 return
898 }
899 self.invalid_block_hook.on_invalid_block(parent_header, block, output, trie_updates);
900 }
901
902 #[instrument(
919 level = "debug",
920 target = "engine::tree::payload_validator",
921 skip_all,
922 fields(parent_hash)
923 )]
924 fn compute_trie_input(
925 &self,
926 parent_hash: B256,
927 state: &EngineApiTreeState<N>,
928 ) -> ProviderResult<(TrieInputSorted, B256)> {
929 let wait_start = Instant::now();
930 let (block_hash, blocks) =
931 state.tree_state.blocks_by_hash(parent_hash).unwrap_or_else(|| (parent_hash, vec![]));
932
933 if let Some(tip_block) = blocks.first() {
937 let data = tip_block.trie_data();
938 if let (Some(anchor_hash), Some(trie_input)) =
939 (data.anchor_hash(), data.trie_input().cloned()) &&
940 anchor_hash == block_hash
941 {
942 trace!(target: "engine::tree::payload_validator", %block_hash,"Reusing trie input with matching anchor hash");
943 self.metrics
944 .block_validation
945 .deferred_trie_wait_duration
946 .record(wait_start.elapsed().as_secs_f64());
947 return Ok(((*trie_input).clone(), block_hash));
948 }
949 }
950
951 if blocks.is_empty() {
952 debug!(target: "engine::tree::payload_validator", "Parent found on disk");
953 } else {
954 debug!(target: "engine::tree::payload_validator", historical = ?block_hash, blocks = blocks.len(), "Parent found in memory");
955 }
956
957 let input = Self::merge_overlay_trie_input(&blocks);
959
960 self.metrics
961 .block_validation
962 .deferred_trie_wait_duration
963 .record(wait_start.elapsed().as_secs_f64());
964 Ok((input, block_hash))
965 }
966
967 fn merge_overlay_trie_input(blocks: &[ExecutedBlock<N>]) -> TrieInputSorted {
974 let mut input = TrieInputSorted::default();
975 let mut blocks_iter = blocks.iter().rev().peekable();
976
977 if let Some(first) = blocks_iter.next() {
978 let data = first.trie_data();
979 input.state = data.hashed_state;
980 input.nodes = data.trie_updates;
981
982 if blocks_iter.peek().is_some() {
984 let state_mut = Arc::make_mut(&mut input.state);
985 let nodes_mut = Arc::make_mut(&mut input.nodes);
986 for block in blocks_iter {
987 let data = block.trie_data();
988 state_mut.extend_ref(data.hashed_state.as_ref());
989 nodes_mut.extend_ref(data.trie_updates.as_ref());
990 }
991 }
992 }
993
994 input
995 }
996
997 fn spawn_deferred_trie_task(
1014 &self,
1015 block: RecoveredBlock<N::Block>,
1016 output: BlockExecutionOutput<N::Receipt>,
1017 block_number: u64,
1018 ctx: &TreeCtx<'_, N>,
1019 hashed_state: HashedPostState,
1020 trie_output: TrieUpdates,
1021 ) -> ExecutedBlock<N> {
1022 let (anchor_hash, overlay_blocks) = ctx
1024 .state()
1025 .tree_state
1026 .blocks_by_hash(block.parent_hash())
1027 .unwrap_or_else(|| (block.parent_hash(), Vec::new()));
1028
1029 let ancestors: Vec<DeferredTrieData> =
1032 overlay_blocks.iter().rev().map(|b| b.trie_data_handle()).collect();
1033
1034 let deferred_trie_data = DeferredTrieData::pending(
1036 Arc::new(hashed_state),
1037 Arc::new(trie_output),
1038 anchor_hash,
1039 ancestors,
1040 );
1041 let deferred_handle_task = deferred_trie_data.clone();
1042 let deferred_compute_duration =
1043 self.metrics.block_validation.deferred_trie_compute_duration.clone();
1044
1045 let compute_trie_input_task = move || {
1048 let result = panic::catch_unwind(AssertUnwindSafe(|| {
1049 let compute_start = Instant::now();
1050 let _ = deferred_handle_task.wait_cloned();
1051 deferred_compute_duration.record(compute_start.elapsed().as_secs_f64());
1052 }));
1053
1054 if result.is_err() {
1055 error!(
1056 target: "engine::tree::payload_validator",
1057 "Deferred trie task panicked; fallback computation will be used when trie data is accessed"
1058 );
1059 }
1060 };
1061
1062 self.payload_processor.executor().spawn_blocking(compute_trie_input_task);
1064
1065 ExecutedBlock::with_deferred_trie_data(
1066 Arc::new(block),
1067 Arc::new(ExecutionOutcome::from((output, block_number))),
1068 deferred_trie_data,
1069 )
1070 }
1071}
1072
1073pub type ValidationOutcome<N, E = InsertPayloadError<BlockTy<N>>> = Result<ExecutedBlock<N>, E>;
1075
1076#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1078enum StateRootStrategy {
1079 StateRootTask,
1081 Parallel,
1083 Synchronous,
1085}
1086
1087pub trait EngineValidator<
1091 Types: PayloadTypes,
1092 N: NodePrimitives = <<Types as PayloadTypes>::BuiltPayload as BuiltPayload>::Primitives,
1093>: Send + Sync + 'static
1094{
1095 fn validate_payload_attributes_against_header(
1105 &self,
1106 attr: &Types::PayloadAttributes,
1107 header: &N::BlockHeader,
1108 ) -> Result<(), InvalidPayloadAttributesError>;
1109
1110 fn convert_payload_to_block(
1119 &self,
1120 payload: Types::ExecutionData,
1121 ) -> Result<SealedBlock<N::Block>, NewPayloadError>;
1122
1123 fn validate_payload(
1125 &mut self,
1126 payload: Types::ExecutionData,
1127 ctx: TreeCtx<'_, N>,
1128 ) -> ValidationOutcome<N>;
1129
1130 fn validate_block(
1132 &mut self,
1133 block: SealedBlock<N::Block>,
1134 ctx: TreeCtx<'_, N>,
1135 ) -> ValidationOutcome<N>;
1136
1137 fn on_inserted_executed_block(&self, block: ExecutedBlock<N>);
1142}
1143
1144impl<N, Types, P, Evm, V> EngineValidator<Types> for BasicEngineValidator<P, Evm, V>
1145where
1146 P: DatabaseProviderFactory<
1147 Provider: BlockReader + TrieReader + StageCheckpointReader + PruneCheckpointReader,
1148 > + BlockReader<Header = N::BlockHeader>
1149 + StateProviderFactory
1150 + StateReader
1151 + HashedPostStateProvider
1152 + Clone
1153 + 'static,
1154 N: NodePrimitives,
1155 V: PayloadValidator<Types, Block = N::Block>,
1156 Evm: ConfigureEngineEvm<Types::ExecutionData, Primitives = N> + 'static,
1157 Types: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>,
1158{
1159 fn validate_payload_attributes_against_header(
1160 &self,
1161 attr: &Types::PayloadAttributes,
1162 header: &N::BlockHeader,
1163 ) -> Result<(), InvalidPayloadAttributesError> {
1164 self.validator.validate_payload_attributes_against_header(attr, header)
1165 }
1166
1167 fn convert_payload_to_block(
1168 &self,
1169 payload: Types::ExecutionData,
1170 ) -> Result<SealedBlock<N::Block>, NewPayloadError> {
1171 let block = self.validator.convert_payload_to_block(payload)?;
1172 Ok(block)
1173 }
1174
1175 fn validate_payload(
1176 &mut self,
1177 payload: Types::ExecutionData,
1178 ctx: TreeCtx<'_, N>,
1179 ) -> ValidationOutcome<N> {
1180 self.validate_block_with_state(BlockOrPayload::Payload(payload), ctx)
1181 }
1182
1183 fn validate_block(
1184 &mut self,
1185 block: SealedBlock<N::Block>,
1186 ctx: TreeCtx<'_, N>,
1187 ) -> ValidationOutcome<N> {
1188 self.validate_block_with_state(BlockOrPayload::Block(block), ctx)
1189 }
1190
1191 fn on_inserted_executed_block(&self, block: ExecutedBlock<N>) {
1192 self.payload_processor.on_inserted_executed_block(
1193 block.recovered_block.block_with_parent(),
1194 block.execution_output.state(),
1195 );
1196 }
1197}
1198
1199#[derive(Debug)]
1201pub enum BlockOrPayload<T: PayloadTypes> {
1202 Payload(T::ExecutionData),
1204 Block(SealedBlock<BlockTy<<T::BuiltPayload as BuiltPayload>::Primitives>>),
1206}
1207
1208impl<T: PayloadTypes> BlockOrPayload<T> {
1209 pub fn hash(&self) -> B256 {
1211 match self {
1212 Self::Payload(payload) => payload.block_hash(),
1213 Self::Block(block) => block.hash(),
1214 }
1215 }
1216
1217 pub fn num_hash(&self) -> NumHash {
1219 match self {
1220 Self::Payload(payload) => payload.num_hash(),
1221 Self::Block(block) => block.num_hash(),
1222 }
1223 }
1224
1225 pub fn parent_hash(&self) -> B256 {
1227 match self {
1228 Self::Payload(payload) => payload.parent_hash(),
1229 Self::Block(block) => block.parent_hash(),
1230 }
1231 }
1232
1233 pub fn block_with_parent(&self) -> BlockWithParent {
1235 match self {
1236 Self::Payload(payload) => payload.block_with_parent(),
1237 Self::Block(block) => block.block_with_parent(),
1238 }
1239 }
1240
1241 pub const fn type_name(&self) -> &'static str {
1243 match self {
1244 Self::Payload(_) => "payload",
1245 Self::Block(_) => "block",
1246 }
1247 }
1248
1249 pub const fn block_access_list(&self) -> Option<Result<BlockAccessList, alloy_rlp::Error>> {
1251 None
1253 }
1254}