1use crate::tree::{
99 error::{
100 BlockAccessListDecodeError, InsertBlockError, InsertBlockErrorKind, InsertPayloadError,
101 },
102 instrumented_state::{InstrumentedStateProvider, StateProviderMetrics, StateProviderStats},
103 metrics::PrewarmReuseStats,
104 payload_processor::{
105 reuse::{PrewarmResults, ReuseMiss},
106 PayloadProcessor,
107 },
108 txpool_prewarm,
109 types::{InsertPayloadResult, ValidationOutput},
110 CacheWaitDurations, CachedStateProvider, EngineApiMetrics, EngineApiTreeState, ExecutionEnv,
111 PayloadHandle, TreeConfig, WaitForCaches,
112};
113use alloy_consensus::{transaction::Either, Transaction as _};
114use alloy_eip7928::{
115 bal::{Bal, DecodedBal},
116 BlockAccessList,
117};
118use alloy_eips::{eip1898::BlockWithParent, eip4895::Withdrawal, NumHash};
119use alloy_primitives::{map::B256Set, B256};
120use reth_tasks::LazyHandle;
121
122use crate::tree::{
123 payload_processor::receipt_root_task::{IndexedReceipt, ReceiptRootTaskHandle},
124 state_root_strategy::{
125 DefaultStateRootStrategy, LazyHashedPostState, PayloadStateRootHandle,
126 PayloadStateRootJobContext, StateRootHintStream, StateRootJobContext, StateRootStrategy,
127 StateRootUpdateHook, StateRootUpdateStream,
128 },
129};
130use alloy_primitives::Address;
131use evm2::evm::{AccountInfo, Bal as EvmBal};
132use reth_chain_state::{CanonicalInMemoryState, ExecutedBlock, ExecutionTimingStats};
133use reth_consensus::{ConsensusError, FullConsensus, ReceiptRootBloom};
134use reth_engine_primitives::{
135 ConfigureEngineEvm, ExecutableTxIterator, ExecutionPayload, InvalidBlockHook, PayloadValidator,
136};
137use reth_errors::{BlockExecutionError, BlockValidationError, ProviderResult};
138use reth_evm::{
139 database::StateProviderDatabase, precompile_cache::PrecompileCacheMap, BlockExecutor,
140 BlockExecutorFactory, BlockExecutorFor, BlockTransactionResult, ConfigureEvm, Evm as _,
141 EvmEnvFor, EvmTypesFor, ExecutableTxFor, ExecutionCtxFor, OnStateHook, SpecFor,
142};
143use reth_execution_cache::{CacheFillMode, CacheStats};
144use reth_execution_types::DecodedEvmBal;
145use reth_network_p2p::full_block::SealedBlockWithAccessList;
146use reth_payload_builder::{PayloadBuilderLease, PayloadBuilderResources};
147use reth_payload_primitives::{
148 BuiltPayload, BuiltPayloadExecutedBlock, InvalidPayloadAttributesError, NewPayloadError,
149 PayloadTypes,
150};
151use reth_primitives_traits::{
152 AlloyBlockHeader, BlockBody, BlockTy, FastInstant as Instant, GotExpected, NodePrimitives,
153 RecoveredBlock, SealedBlock, SealedHeader, SignerRecoverable, TxTy,
154};
155use reth_provider::{
156 BlockExecutionOutput, BlockReader, ChangeSetReader, DatabaseProviderFactory,
157 DatabaseProviderROFactory, EvmStateProvider, EvmStateProviderBox, HistoryReader, ProviderError,
158 PruneCheckpointReader, StageCheckpointReader, StateProvider, StateProviderFactory, StateReader,
159 StorageChangeSetReader, StorageSettingsCache,
160};
161use reth_storage_overlay::{OverlayManager, OverlayStateProviderFactory};
162use reth_trie::{updates::TrieUpdates, HashedPostState, KeccakKeyHasher, LazyTrieData};
163use reth_trie_common::bundle_state::BundleAccount;
164use std::{
165 sync::{
166 atomic::{AtomicUsize, Ordering},
167 Arc,
168 },
169 time::Duration,
170};
171use tracing::{debug, debug_span, error, info, instrument, trace, warn, Level, Span};
172
173pub use crate::tree::types::ValidationOutcome;
174
175const MAX_EXPECTED_GAS_LIMIT_MULTIPLIER: u64 = 2;
178
179const DEFERRED_TRIE_WORKER_NAME: &str = "deferred-trie";
181
182type ReceiptRootSender<N> =
183 crossbeam_channel::Sender<IndexedReceipt<<N as NodePrimitives>::Receipt>>;
184type ReceiptRootReceiver = tokio::sync::oneshot::Receiver<(B256, alloy_primitives::Bloom)>;
185
186pub struct TreeCtx<'a, N: NodePrimitives> {
191 state: &'a mut EngineApiTreeState<N>,
193 canonical_in_memory_state: &'a CanonicalInMemoryState<N>,
195}
196
197impl<'a, N: NodePrimitives> std::fmt::Debug for TreeCtx<'a, N> {
198 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
199 f.debug_struct("TreeCtx")
200 .field("state", &"EngineApiTreeState")
201 .field("canonical_in_memory_state", &self.canonical_in_memory_state)
202 .finish()
203 }
204}
205
206impl<'a, N: NodePrimitives> TreeCtx<'a, N> {
207 pub const fn new(
209 state: &'a mut EngineApiTreeState<N>,
210 canonical_in_memory_state: &'a CanonicalInMemoryState<N>,
211 ) -> Self {
212 Self { state, canonical_in_memory_state }
213 }
214}
215
216impl<'a, N: NodePrimitives> TreeCtx<'a, N> {
217 pub const fn state(&self) -> &EngineApiTreeState<N> {
219 &*self.state
220 }
221
222 pub const fn state_mut(&mut self) -> &mut EngineApiTreeState<N> {
224 self.state
225 }
226
227 pub const fn canonical_in_memory_state(&self) -> &'a CanonicalInMemoryState<N> {
229 self.canonical_in_memory_state
230 }
231}
232
233struct JitPauseGuard<Evm: ConfigureEvm>(Evm);
239
240impl<Evm: ConfigureEvm> JitPauseGuard<Evm> {
241 fn new(evm_config: &Evm) -> Self {
242 if let Some(jit_backend) = evm_config.jit_backend() {
243 jit_backend.pause();
244 }
245 Self(evm_config.clone())
246 }
247}
248
249impl<Evm: ConfigureEvm> Drop for JitPauseGuard<Evm> {
250 fn drop(&mut self) {
251 if let Some(jit_backend) = self.0.jit_backend() {
252 jit_backend.resume();
253 }
254 }
255}
256
257#[derive(derive_more::Debug)]
265pub struct BasicEngineValidator<P, Evm, V>
266where
267 Evm: ConfigureEvm,
268{
269 provider: P,
271 consensus: Arc<dyn FullConsensus<Evm::Primitives>>,
273 evm_config: Evm,
275 config: TreeConfig,
277 payload_processor: PayloadProcessor<Evm>,
279 precompile_cache_map: PrecompileCacheMap<SpecFor<Evm>>,
281 #[debug(skip)]
283 invalid_block_hook: Box<dyn InvalidBlockHook<Evm::Primitives>>,
284 metrics: EngineApiMetrics,
286 validator: V,
288 runtime: reth_tasks::Runtime,
290 overlay_manager: OverlayManager<Evm::Primitives>,
292 #[debug(skip)]
294 state_root_strategy: Arc<dyn StateRootStrategy<Evm::Primitives, P, Evm>>,
295 #[debug(skip)]
299 txpool_prewarm: Option<txpool_prewarm::Handle<Evm::Primitives, P, Evm>>,
300 bal_hash_buf: Vec<u8>,
302}
303
304impl<N, P, Evm, V> BasicEngineValidator<P, Evm, V>
305where
306 N: NodePrimitives,
307 P: DatabaseProviderFactory<
308 Provider: BlockReader
309 + StageCheckpointReader
310 + PruneCheckpointReader
311 + ChangeSetReader
312 + StorageChangeSetReader
313 + StorageSettingsCache
314 + HistoryReader
315 + 'static,
316 > + BlockReader<Header = N::BlockHeader>
317 + ChangeSetReader
318 + StateProviderFactory
319 + StateReader
320 + Clone
321 + 'static,
322 Evm: ConfigureEvm<Primitives = N> + 'static,
323{
324 #[expect(clippy::too_many_arguments)]
326 pub fn new(
327 provider: P,
328 consensus: Arc<dyn FullConsensus<N>>,
329 evm_config: Evm,
330 validator: V,
331 config: TreeConfig,
332 invalid_block_hook: Box<dyn InvalidBlockHook<N>>,
333 overlay_manager: OverlayManager<N>,
334 runtime: reth_tasks::Runtime,
335 ) -> Self {
336 let precompile_cache_map = PrecompileCacheMap::default();
337 let payload_processor = PayloadProcessor::new(
338 runtime.clone(),
339 evm_config.clone(),
340 &config,
341 precompile_cache_map.clone(),
342 );
343 Self {
344 provider,
345 consensus,
346 evm_config,
347 payload_processor,
348 precompile_cache_map,
349 config,
350 invalid_block_hook,
351 metrics: EngineApiMetrics::default(),
352 validator,
353 runtime,
354 overlay_manager,
355 state_root_strategy: Arc::new(DefaultStateRootStrategy::default()),
356 txpool_prewarm: None,
357 bal_hash_buf: Vec::new(),
358 }
359 }
360
361 pub fn with_state_root_strategy(
363 mut self,
364 state_root_strategy: Arc<dyn StateRootStrategy<N, P, Evm>>,
365 ) -> Self {
366 self.state_root_strategy = state_root_strategy;
367 self
368 }
369
370 pub fn with_txpool_prewarming(
372 mut self,
373 source: impl crate::tree::TxPoolPrewarmSource<N> + 'static,
374 ) -> Self {
375 self.txpool_prewarm = Some(txpool_prewarm::Handle::spawn(
376 &self.runtime,
377 Arc::new(source),
378 self.evm_config.clone(),
379 self.evm_config.reuses_prewarm_results() && self.config.prewarm_reuse(),
380 ));
381 self
382 }
383
384 #[instrument(level = "debug", target = "engine::tree::payload_validator", skip_all)]
386 pub fn convert_to_block<T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
387 &self,
388 input: BlockOrPayload<T>,
389 ) -> Result<SealedBlock<N::Block>, NewPayloadError>
390 where
391 V: PayloadValidator<T, Block = N::Block>,
392 {
393 match input {
394 BlockOrPayload::Payload(payload) => self.validator.convert_payload_to_block(payload),
395 BlockOrPayload::Block(block) => Ok(block.split().0),
396 }
397 }
398
399 pub fn evm_env_for<T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
401 &self,
402 input: &BlockOrPayload<T>,
403 ) -> Result<EvmEnvFor<Evm>, Evm::Error>
404 where
405 V: PayloadValidator<T, Block = N::Block>,
406 Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
407 {
408 match input {
409 BlockOrPayload::Payload(payload) => Ok(self.evm_config.evm_env_for_payload(payload)?),
410 BlockOrPayload::Block(block) => Ok(self.evm_config.evm_env(block.header())?),
411 }
412 }
413
414 pub fn tx_iterator_for<'a, T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
416 &'a self,
417 input: &'a BlockOrPayload<T>,
418 ) -> Result<impl ExecutableTxIterator<Evm>, NewPayloadError>
419 where
420 V: PayloadValidator<T, Block = N::Block>,
421 Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
422 {
423 Ok(match input {
424 BlockOrPayload::Payload(payload) => {
425 let iter = self
426 .evm_config
427 .tx_iterator_for_payload(payload)
428 .map_err(NewPayloadError::other)?;
429 Either::Left(iter)
430 }
431 BlockOrPayload::Block(block) => {
432 let txs = block.body().clone_transactions();
433 let convert = |tx: N::SignedTx| tx.try_into_recovered();
434 Either::Right((txs, convert))
435 }
436 })
437 }
438
439 pub fn execution_ctx_for<'a, T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
441 &self,
442 input: &'a BlockOrPayload<T>,
443 ) -> Result<ExecutionCtxFor<'a, Evm>, Evm::Error>
444 where
445 V: PayloadValidator<T, Block = N::Block>,
446 Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
447 {
448 match input {
449 BlockOrPayload::Payload(payload) => Ok(self.evm_config.context_for_payload(payload)?),
450 BlockOrPayload::Block(block) => Ok(self.evm_config.context_for_block(block)?),
451 }
452 }
453
454 #[instrument(
462 level = "debug",
463 target = "engine::tree::payload_validator",
464 skip_all,
465 fields(
466 parent = ?input.parent_hash(),
467 type_name = ?input.type_name(),
468 )
469 )]
470 pub fn validate_block_with_state<T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
471 &mut self,
472 input: BlockOrPayload<T>,
473 mut ctx: TreeCtx<'_, N>,
474 ) -> InsertPayloadResult<N>
475 where
476 V: PayloadValidator<T, Block = N::Block> + Clone,
477 Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
478 {
479 let parent_hash = input.parent_hash();
480 let _txpool_pause = self.txpool_prewarm.as_ref().map(txpool_prewarm::Handle::pause);
481 let txpool_snapshot =
482 self.txpool_prewarm.as_ref().and_then(|prewarmer| prewarmer.snapshot(parent_hash));
483 let _jit_pause = JitPauseGuard::new(&self.evm_config);
484
485 let parent_block = match self.sealed_header_by_hash(parent_hash, ctx.state()) {
488 Ok(Some(parent_block)) => parent_block,
489 Ok(None) => {
490 return Err(InsertBlockError::new(
491 self.convert_to_block(input)?,
492 ProviderError::HeaderNotFound(parent_hash.into()).into(),
493 )
494 .into())
495 }
496 Err(e) => {
497 return Err(InsertBlockError::new(self.convert_to_block(input)?, e.into()).into())
498 }
499 };
500
501 let validated_block = self.spawn_convert_and_validate(&input, parent_block.clone());
505
506 macro_rules! ensure_ok {
509 ($expr:expr) => {
510 match $expr {
511 Ok(val) => val,
512 Err(e) => {
513 let block = validated_block.try_into_inner().expect("sole handle")?;
514 return Err(InsertBlockError::new(block, e.into()).into())
515 }
516 }
517 };
518 }
519
520 macro_rules! ensure_ok_post_block {
522 ($expr:expr, $block:expr) => {
523 match $expr {
524 Ok(val) => val,
525 Err(e) => {
526 return Err(
527 InsertBlockError::new($block.into_sealed_block(), e.into()).into()
528 )
529 }
530 }
531 };
532 }
533
534 if input.gas_limit() >
537 parent_block.gas_limit().saturating_mul(MAX_EXPECTED_GAS_LIMIT_MULTIPLIER)
538 {
539 if validated_block.get().is_err() {
541 return Err(validated_block
542 .try_into_inner()
543 .expect("sole handle")
544 .expect_err("Err result checked"))
545 }
546 }
547
548 trace!(target: "engine::tree::payload_validator", "Fetching block state provider");
549 let _enter =
550 debug_span!(target: "engine::tree::payload_validator", "state_provider").entered();
551 let Some(state_provider_factory) =
552 ensure_ok!(self.overlay_state_provider_factory(parent_hash, ctx.state()))
553 else {
554 return Err(InsertBlockError::new(
556 validated_block.try_into_inner().expect("sole handle")?,
557 ProviderError::HeaderNotFound(parent_hash.into()).into(),
558 )
559 .into())
560 };
561 drop(_enter);
562
563 let evm_env = debug_span!(target: "engine::tree::payload_validator", "evm_env")
564 .in_scope(|| self.evm_env_for(&input))
565 .map_err(NewPayloadError::other)?;
566 let pool_seeds = self
567 .txpool_prewarm
568 .as_ref()
569 .and_then(|prewarmer| prewarmer.seeds(parent_hash, &evm_env));
570
571 let decoded_bal =
574 ensure_ok!(input.try_decoded_access_list().map_err(BlockAccessListDecodeError::new))
575 .map(Arc::new);
576
577 if let Some(decoded_bal) = decoded_bal.as_deref() {
578 ensure_ok!(decoded_bal
580 .as_bal()
581 .validate_gas_limit(input.gas_limit())
582 .map_err(ConsensusError::from));
583 }
584
585 let env = ExecutionEnv {
586 evm_env,
587 hash: input.hash(),
588 parent_hash: input.parent_hash(),
589 parent_state_root: parent_block.state_root(),
590 transaction_count: input.transaction_count(),
591 gas_used: input.gas_used(),
592 withdrawals: input.withdrawals().map(|w| w.to_vec()),
593 decoded_bal: decoded_bal.as_ref().map(Arc::clone),
594 txpool_snapshot: txpool_snapshot.clone(),
595 pool_seeds,
596 };
597
598 let txs = self.tx_iterator_for(&input)?;
600
601 let parallel_bal_execution = ensure_ok!(self.bal_path_eligible(env.decoded_bal.as_deref()));
602
603 let mut state_root_job =
605 ensure_ok!(self.state_root_strategy.prepare(StateRootJobContext::new(
606 &self.runtime,
607 &self.overlay_manager,
608 &env,
609 &parent_block,
610 state_provider_factory.clone(),
611 &self.config,
612 parallel_bal_execution,
613 ctx.state_mut(),
614 )));
615 let state_root_job_name = state_root_job.name();
616
617 debug!(
618 target: "engine::tree::payload_validator",
619 strategy = state_root_job_name,
620 "Prepared state root job"
621 );
622
623 let execution_state_hook = state_root_job.take_execution_hook();
626 let hint_stream = state_root_job.take_hint_stream();
629 let hashed_update_stream = state_root_job.take_hashed_update_stream();
630
631 let mut handle = ensure_ok!(self.spawn_payload_processor(
633 env.clone(),
634 txs,
635 state_provider_factory.clone(),
636 hint_stream,
637 hashed_update_stream,
638 parallel_bal_execution,
639 ));
640
641 let slow_block_enabled = self.config.slow_block_threshold().is_some();
643 let cache_stats = slow_block_enabled.then(|| Arc::new(CacheStats::default()));
644 let instrument_state_provider = slow_block_enabled || self.config.state_provider_metrics();
645 let state_provider_metrics =
646 instrument_state_provider.then(|| StateProviderMetrics::with_source("engine"));
647 let state_provider_stats =
648 instrument_state_provider.then(|| Arc::new(StateProviderStats::default()));
649 let execution_cache = handle.caches().map(|caches| (caches, handle.cache_metrics()));
650
651 let make_state_provider = |fill_on_miss: bool| -> ProviderResult<EvmStateProviderBox> {
671 let provider = state_provider_factory.database_provider_ro()?.into_evm_state_provider();
672 let provider: EvmStateProviderBox =
673 if let Some((caches, cache_metrics)) = &execution_cache {
674 let fill_mode = if fill_on_miss {
675 CacheFillMode::FillOnMiss
676 } else {
677 CacheFillMode::LookupOnly
678 };
679 Box::new(
680 CachedStateProvider::new_with_mode(
681 provider,
682 caches.clone(),
683 fill_mode,
684 cache_metrics.clone(),
685 cache_stats.clone(),
686 )
687 .with_txpool_snapshot(txpool_snapshot.clone()),
688 )
689 } else {
690 Box::new(provider)
691 };
692
693 let provider: EvmStateProviderBox = if instrument_state_provider {
694 let stats = state_provider_stats
695 .as_ref()
696 .expect("instrumented state provider requires shared stats");
697 let metrics = state_provider_metrics
698 .as_ref()
699 .expect("instrumented state provider requires metrics");
700 Box::new(InstrumentedStateProvider::with_stats(
701 provider,
702 metrics.clone(),
703 Arc::clone(stats),
704 ))
705 } else {
706 provider
707 };
708
709 Ok(provider)
710 };
711
712 let execute_block_start = Instant::now();
716 let execution_result = if parallel_bal_execution {
717 self.execute_block_bal(env, &input, &handle, &make_state_provider)
718 } else {
719 let state_provider = make_state_provider(false);
720 match state_provider {
721 Ok(state_provider) => self.execute_block(
722 state_provider,
723 env,
724 &input,
725 &mut handle,
726 execution_state_hook,
727 ),
728 Err(err) => Err(err.into()),
729 }
730 };
731 let execution_duration = execute_block_start.elapsed();
732 if let (Some(metrics), Some(stats)) = (&state_provider_metrics, &state_provider_stats) {
733 metrics.record_totals(stats);
734 }
735 let (output, senders, receipt_root_rx, executed_bal) = ensure_ok!(execution_result);
736 let (built_bal, evm_bal) =
737 executed_bal.map(|ExecutedBal { alloy, evm }| (alloy, evm)).unzip();
738
739 handle.stop_prewarming_execution();
741
742 let output = Arc::new(output);
746
747 let valid_block_tx = handle.terminate_caching(Some(output.clone()));
750
751 let hashed_state_output = output.clone();
755 let mut hashed_state_rx = state_root_job.take_hashed_state_rx();
756 let parent_span = Span::current();
757 let mut hashed_state: LazyHashedPostState =
758 self.runtime.spawn_blocking_named("hash-post-state", move || {
759 let _span = debug_span!(
760 target: "engine::tree::payload_validator",
761 parent: &parent_span,
762 "hashed_post_state",
763 )
764 .entered();
765 if let Some(Ok(state)) = hashed_state_rx.as_mut().map(|rx| rx.recv()) {
766 state
767 } else {
768 Arc::new(HashedPostState::from_bundle_state::<KeccakKeyHasher>(
769 hashed_state_output.state.state(),
770 ))
771 }
772 });
773
774 let block = validated_block.try_into_inner().expect("sole handle")?;
775 let block = block.with_senders(senders);
776
777 let receipt_root_bloom = {
779 let _enter = debug_span!(
780 target: "engine::tree::payload_validator",
781 "wait_receipt_root",
782 )
783 .entered();
784
785 receipt_root_rx
786 .blocking_recv()
787 .inspect_err(|_| {
788 tracing::error!(
789 target: "engine::tree::payload_validator",
790 "Receipt root task dropped sender without result, receipt root calculation likely aborted"
791 );
792 })
793 .ok()
794 };
795
796 ensure_ok_post_block!(
797 self.validate_post_execution(
798 &block,
799 &parent_block,
800 &output,
801 &mut ctx,
802 receipt_root_bloom,
803 built_bal
804 ),
805 block
806 );
807
808 let mut hashed_state_validate_result = debug_span!(
809 target: "engine::tree::payload_validator",
810 "validate_block_post_execution_with_hashed_state"
811 )
812 .in_scope(|| {
813 self.validator.validate_block_post_execution_with_hashed_state(
814 || hashed_state.get(),
815 &block,
816 &parent_block,
817 || {
818 state_provider_factory
819 .database_provider_ro()
820 .map(|provider| Box::new(provider) as _)
821 },
822 )
823 });
824
825 let root_start = Instant::now();
826 let root_outcome = ensure_ok_post_block!(
827 state_root_job.finish(&block, output.clone(), &hashed_state),
828 block
829 );
830 let root_elapsed = root_start.elapsed();
831
832 info!(
833 target: "engine::tree::payload_validator",
834 strategy = state_root_job_name,
835 state_root = ?root_outcome.state_root,
836 elapsed = ?root_elapsed,
837 "State root job finished"
838 );
839
840 let state_root = root_outcome.state_root;
841 let trie_output = root_outcome.trie_updates;
842
843 if let Some(refreshed) = root_outcome.hashed_state {
847 hashed_state = LazyHandle::ready(refreshed);
848 hashed_state_validate_result = debug_span!(
849 target: "engine::tree::payload_validator",
850 "validate_block_post_execution_with_hashed_state"
851 )
852 .in_scope(|| {
853 self.validator.validate_block_post_execution_with_hashed_state(
854 || hashed_state.get(),
855 &block,
856 &parent_block,
857 || {
858 state_provider_factory
859 .database_provider_ro()
860 .map(|provider| Box::new(provider) as _)
861 },
862 )
863 });
864 }
865
866 if let Err(err) = hashed_state_validate_result {
867 if err.is_validation_error() {
868 self.on_invalid_block(&parent_block, &block, &output, None, ctx.state_mut());
869 }
870 return Err(InsertBlockError::new(block.into_sealed_block(), err).into())
871 }
872
873 self.metrics.block_validation.record_state_root(&trie_output, root_elapsed.as_secs_f64());
874 self.metrics
875 .record_state_root_gas_bucket(block.header().gas_used(), root_elapsed.as_secs_f64());
876 debug!(target: "engine::tree::payload_validator", ?root_elapsed, "Calculated state root");
877
878 if state_root != block.header().state_root() {
880 self.on_invalid_block(
882 &parent_block,
883 &block,
884 &output,
885 Some((&trie_output, state_root)),
886 ctx.state_mut(),
887 );
888 let block_state_root = block.header().state_root();
889 return Err(InsertBlockError::new(
890 block.into_sealed_block(),
891 ConsensusError::BodyStateRootDiff(
892 GotExpected { got: state_root, expected: block_state_root }.into(),
893 )
894 .into(),
895 )
896 .into())
897 }
898
899 let timing_stats = state_provider_stats.filter(|_| slow_block_enabled).map(|stats| {
900 self.calculate_timing_stats(
901 &block,
902 stats,
903 cache_stats,
904 &output,
905 execution_duration,
906 root_elapsed,
907 )
908 });
909
910 if let Some(valid_block_tx) = valid_block_tx {
911 let _ = valid_block_tx.send(());
912 }
913
914 let bal = evm_bal.zip(decoded_bal).map(|(evm_bal, decoded_bal)| {
919 Arc::new(DecodedEvmBal::with_raw_bal(evm_bal, decoded_bal.as_raw_bal().clone()))
920 });
921 let executed_block = self
922 .spawn_deferred_trie_task(Arc::new(block), output, hashed_state, trie_output)
923 .with_bal(bal);
924 Ok(ValidationOutput::new(executed_block, timing_stats))
925 }
926
927 #[expect(clippy::type_complexity)]
930 pub fn spawn_convert_and_validate<T>(
931 &self,
932 input: &BlockOrPayload<T>,
933 parent: SealedHeader<N::BlockHeader>,
934 ) -> LazyHandle<Result<SealedBlock<N::Block>, InsertPayloadError<N::Block>>>
935 where
936 T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>,
937 V: PayloadValidator<T, Block = N::Block> + Clone,
938 {
939 let input = input.clone();
940 let validator = self.validator.clone();
941 let consensus = self.consensus.clone();
942 let parent_span = Span::current();
943 self.runtime.spawn_blocking_named("payload-convert", move || {
944 let _span = debug_span!(
945 target: "engine::tree::payload_validator",
946 parent: &parent_span,
947 "convert_and_validate",
948 )
949 .entered();
950 let block = match input {
951 BlockOrPayload::Block(block) => block.split().0,
952 BlockOrPayload::Payload(payload) => {
953 validator.convert_payload_to_block(payload)?
954 }
955 };
956
957 if let Err(e) = consensus.validate_header(block.sealed_header()) {
958 error!(target: "engine::tree::payload_validator", block = ?block.block_with_parent(), %e, "Failed to validate header");
959 return Err(InsertBlockError::consensus_error(e, block).into())
960 }
961
962 let _enter = debug_span!(target: "engine::tree::payload_validator", "validate_header_against_parent").entered();
964 if let Err(e) = consensus.validate_header_against_parent(block.sealed_header(), &parent)
965 {
966 warn!(target: "engine::tree::payload_validator", block = ?block.block_with_parent(), %e, "Failed to validate header against parent");
967 return Err(InsertBlockError::consensus_error(e, block).into())
968 }
969 drop(_enter);
970
971 if let Err(e) =
972 consensus.validate_block_pre_execution_with_tx_root(&block, None)
973 {
974 error!(target: "engine::tree::payload_validator", block = ?block.block_with_parent(), %e, "Failed to validate block");
975 return Err(InsertBlockError::consensus_error(e, block).into())
976 }
977
978 Ok(block)
979 })
980 }
981
982 fn sealed_header_by_hash(
984 &self,
985 hash: B256,
986 state: &EngineApiTreeState<N>,
987 ) -> ProviderResult<Option<SealedHeader<N::BlockHeader>>> {
988 let header = state.tree_state.sealed_header_by_hash(&hash);
990
991 if header.is_some() {
992 Ok(header)
993 } else {
994 self.provider.sealed_header_by_hash(hash)
995 }
996 }
997
998 #[instrument(level = "debug", target = "engine::tree::payload_validator", skip_all)]
1006 #[expect(clippy::type_complexity)]
1007 fn execute_block<S, Err, T>(
1008 &self,
1009 state_provider: S,
1010 env: ExecutionEnv<Evm>,
1011 input: &BlockOrPayload<T>,
1012 handle: &mut PayloadHandle<impl ExecutableTxFor<Evm>, Err, N::Receipt, EvmTypesFor<Evm>>,
1013 state_hook: Option<StateRootUpdateHook>,
1014 ) -> Result<
1015 (BlockExecutionOutput<N::Receipt>, Vec<Address>, ReceiptRootReceiver, Option<ExecutedBal>),
1016 InsertBlockErrorKind,
1017 >
1018 where
1019 S: EvmStateProvider + Send,
1020 Err: core::error::Error + Send + Sync + 'static,
1021 V: PayloadValidator<T, Block = N::Block>,
1022 T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>,
1023 Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
1024 {
1025 debug!(target: "engine::tree::payload_validator", "Executing block");
1026
1027 let transaction_count = input.transaction_count();
1028 let (receipt_tx, result_rx) = self.spawn_receipt_root_task(transaction_count);
1029 let executed_tx_index = Arc::clone(handle.executed_tx_index());
1030 let prewarm_results = handle.prewarm_results().filter(|_| !input.has_block_access_list());
1032 let execution_start = Instant::now();
1033 let evm_config = self.evm_config.clone().with_jit_support();
1034 let execution_ctx = self.execution_ctx_for(input).map_err(BlockExecutionError::other)?;
1035
1036 let (output, senders, built_bal) = debug_span!(target: "engine::tree", "execute_block")
1037 .in_scope(|| {
1038 let db = StateProviderDatabase::new(state_provider);
1039 let evm = if self.config.precompile_cache_disabled() {
1040 evm_config.evm_with_env(db, env.evm_env)
1041 } else {
1042 evm_config.evm_with_precompile_cache(
1043 db,
1044 env.evm_env,
1045 self.precompile_cache_map.clone(),
1046 true,
1047 )
1048 };
1049 let mut executor =
1050 evm_config.block_executor_factory().create_executor(evm, execution_ctx);
1051 if input.has_block_access_list() {
1052 executor.enable_block_access_list_builder();
1053 }
1054 if let Some(mut state_hook) = state_hook {
1055 executor.set_state_hook(move |state| state_hook.on_state(state));
1056 }
1057 let senders = self.execute_transactions(
1058 &mut executor,
1059 transaction_count,
1060 handle.iter_transactions(),
1061 &receipt_tx,
1062 &executed_tx_index,
1063 prewarm_results.as_deref(),
1064 )?;
1065
1066 let post_exec_start = Instant::now();
1067 let (output, built_bal) = executor.finish_with_prepared_block_access_list()?;
1068 self.metrics.record_post_execution(post_exec_start.elapsed());
1069 Ok::<_, BlockExecutionError>((output, senders, built_bal))
1070 })?;
1071 drop(receipt_tx);
1072 if let Some(results) = prewarm_results {
1074 self.runtime.spawn_drop(results);
1075 }
1076 let built_bal =
1077 built_bal.map(|bal| ExecutedBal { alloy: (&bal).into(), evm: Arc::new(bal) });
1078
1079 let execution_duration = execution_start.elapsed();
1080 self.metrics.record_block_execution(&output, execution_duration);
1081 self.metrics.record_block_execution_gas_bucket(output.result.gas_used, execution_duration);
1082 debug!(target: "engine::tree::payload_validator", elapsed = ?execution_duration, "Executed block");
1083
1084 Ok((output, senders, result_rx, built_bal))
1085 }
1086
1087 fn bal_path_eligible(&self, bal: Option<&DecodedBal>) -> Result<bool, InsertBlockErrorKind> {
1096 let has_bal = bal.is_some();
1097 let parallel_execution = has_bal && !self.config.disable_bal_parallel_execution();
1098 if parallel_execution && self.config.disable_bal_parallel_state_root() {
1099 return Err(InsertBlockErrorKind::Other(
1100 "disabling parallel state root is impossible when parallel execution is enabled"
1101 .into(),
1102 ));
1103 }
1104
1105 Ok(parallel_execution)
1106 }
1107
1108 #[instrument(level = "debug", target = "engine::tree::payload_validator", skip_all)]
1119 #[expect(clippy::type_complexity)]
1120 fn execute_block_bal<Tx, Err, MakeStateProvider, T>(
1121 &self,
1122 env: ExecutionEnv<Evm>,
1123 input: &BlockOrPayload<T>,
1124 handle: &PayloadHandle<Tx, Err, N::Receipt, EvmTypesFor<Evm>>,
1125 make_state_provider: &MakeStateProvider,
1126 ) -> Result<
1127 (BlockExecutionOutput<N::Receipt>, Vec<Address>, ReceiptRootReceiver, Option<ExecutedBal>),
1128 InsertBlockErrorKind,
1129 >
1130 where
1131 Tx: ExecutableTxFor<Evm> + Send,
1132 Err: core::error::Error + Send + Sync + 'static,
1133 MakeStateProvider: Fn(bool) -> ProviderResult<EvmStateProviderBox> + Sync,
1134 Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
1135 T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>,
1136 V: PayloadValidator<T, Block = N::Block>,
1137 {
1138 debug!(target: "engine::tree::payload_validator", "Executing block via BAL path");
1139
1140 let (receipt_tx, result_rx) = self.spawn_receipt_root_task(env.transaction_count);
1141 let input_bal = env.decoded_bal.ok_or_else(|| {
1142 InsertBlockErrorKind::Other("BAL execute path: no decoded BAL available".into())
1143 })?;
1144
1145 let make_db = |fill_on_miss| {
1146 let provider = make_state_provider(fill_on_miss)
1147 .map_err(crate::tree::payload_processor::bal::BalExecutionError::Provider)?;
1148 Ok(StateProviderDatabase::new(provider))
1149 };
1150 let execution_start = Instant::now();
1151 let ctx =
1152 self.execution_ctx_for(input).map_err(|e| InsertBlockErrorKind::Other(Box::new(e)))?;
1153 let (output, senders, built_bal, received_bal_evm) =
1154 crate::tree::payload_processor::bal::execute_block(
1155 &self.runtime,
1156 &self.evm_config,
1157 &make_db,
1158 input_bal,
1159 env.evm_env,
1160 ctx,
1161 env.transaction_count,
1162 handle.clone_transaction_receiver(),
1163 receipt_tx,
1164 )?;
1165 let execution_duration = execution_start.elapsed();
1166
1167 self.metrics.record_block_execution(&output, execution_duration);
1168 self.metrics.record_block_execution_gas_bucket(output.result.gas_used, execution_duration);
1169 debug!(
1170 target: "engine::tree::payload_validator",
1171 elapsed = ?execution_duration,
1172 "Executed block via BAL path",
1173 );
1174
1175 Ok((
1179 output,
1180 senders,
1181 result_rx,
1182 Some(ExecutedBal { alloy: built_bal, evm: received_bal_evm }),
1183 ))
1184 }
1185
1186 fn spawn_receipt_root_task(
1187 &self,
1188 receipts_len: usize,
1189 ) -> (ReceiptRootSender<N>, ReceiptRootReceiver) {
1190 let (receipt_tx, receipt_rx) = crossbeam_channel::unbounded();
1192 let (result_tx, result_rx) = tokio::sync::oneshot::channel();
1193 let task_handle = ReceiptRootTaskHandle::new(receipt_rx, result_tx);
1194 self.runtime.spawn_blocking_named("receipt-root", move || task_handle.run(receipts_len));
1195
1196 (receipt_tx, result_rx)
1197 }
1198
1199 fn execute_transactions<Tx, Err>(
1207 &self,
1208 executor: &mut BlockExecutorFor<'_, Evm>,
1209 transaction_count: usize,
1210 transactions: impl Iterator<Item = Result<Tx, Err>>,
1211 receipt_tx: &ReceiptRootSender<N>,
1212 executed_tx_index: &AtomicUsize,
1213 prewarm_results: Option<&PrewarmResults<EvmTypesFor<Evm>>>,
1214 ) -> Result<Vec<Address>, BlockExecutionError>
1215 where
1216 Tx: ExecutableTxFor<Evm>,
1217 Err: core::error::Error + Send + Sync + 'static,
1218 {
1219 let pre_exec_start = Instant::now();
1221 debug_span!(target: "engine::tree", "pre_execution")
1222 .in_scope(|| executor.apply_pre_execution_changes())?;
1223 self.metrics.record_pre_execution(pre_exec_start.elapsed());
1224
1225 let exec_span = debug_span!(target: "engine::tree", "execution").entered();
1227 let mut senders = Vec::with_capacity(transaction_count);
1228 let mut reuse_stats = PrewarmReuseStats::default();
1229 let mut transactions = transactions.into_iter();
1230 let mut last_sent_len = 0usize;
1235 loop {
1236 let wait_start = Instant::now();
1239 let Some(tx_result) = transactions.next() else { break };
1240 self.metrics.record_transaction_wait(wait_start.elapsed());
1241 let tx = tx_result.map_err(BlockValidationError::other)?;
1242 senders.push(*tx.signer());
1243
1244 let _enter = tracing::enabled!(target: "engine::tree", Level::TRACE).then(|| {
1245 tracing::trace_span!(
1246 target: "engine::tree",
1247 "execute tx",
1248 tx_index = senders.len() - 1,
1249 )
1250 .entered()
1251 });
1252 if tracing::enabled!(target: "engine::tree", Level::TRACE) {
1253 trace!(target: "engine::tree", "Executing transaction");
1254 }
1255
1256 let tx_start = Instant::now();
1257 let index = senders.len() - 1;
1258 match prewarm_results {
1259 Some(results) => {
1260 let outcome = self.commit_prewarm_result(executor, results, index, tx.tx())?;
1261 if outcome.is_err() {
1262 let result = executor.execute_transaction_without_commit(tx)?;
1264 results.publish_executed(index, &result.result().pending_state);
1265 executor.commit_transaction(result)?;
1266 }
1267 reuse_stats.record(outcome);
1268 }
1269 None => {
1270 executor.execute_transaction(tx)?;
1271 }
1272 }
1273 self.metrics.record_transaction_execution(tx_start.elapsed());
1274
1275 executed_tx_index.store(senders.len(), Ordering::Relaxed);
1277
1278 let current_len = executor.receipts().len();
1279 if current_len > last_sent_len {
1280 last_sent_len = current_len;
1281 if let Some(receipt) = executor.receipts().last() {
1283 let tx_index = current_len - 1;
1284 receipt_tx
1285 .send(IndexedReceipt::new(tx_index, receipt.clone()))
1286 .map_err(|_| BlockExecutionError::msg("receipt root task closed"))?;
1287 }
1288 }
1289 }
1290
1291 drop(exec_span);
1292 if prewarm_results.is_some() {
1293 self.metrics.prewarm_reuse.record(reuse_stats);
1294 }
1295 Ok(senders)
1296 }
1297
1298 fn commit_prewarm_result(
1301 &self,
1302 executor: &mut BlockExecutorFor<'_, Evm>,
1303 results: &PrewarmResults<EvmTypesFor<Evm>>,
1304 index: usize,
1305 tx: &TxTy<N>,
1306 ) -> Result<Result<(), ReuseMiss>, BlockExecutionError> {
1307 executor.validate_transaction_gas_limit(tx.gas_limit())?;
1308 let result = match results
1309 .take_valid(index, executor.evm_mut().state_db_mut())
1310 .map_err(BlockExecutionError::other)?
1311 {
1312 Ok(result) => result,
1313 Err(miss) => return Ok(Err(miss)),
1314 };
1315 let Some(result) = self.evm_config.prewarm_tx_result(tx, result) else {
1316 return Ok(Err(ReuseMiss::Stale))
1317 };
1318 executor.commit_transaction(result)?;
1319 Ok(Ok(()))
1320 }
1321
1322 #[instrument(level = "debug", target = "engine::tree::payload_validator", skip_all)]
1334 fn validate_post_execution<T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
1335 &mut self,
1336 block: &RecoveredBlock<N::Block>,
1337 parent_block: &SealedHeader<N::BlockHeader>,
1338 output: &BlockExecutionOutput<N::Receipt>,
1339 ctx: &mut TreeCtx<'_, N>,
1340 receipt_root_bloom: Option<ReceiptRootBloom>,
1341 built_bal: Option<BlockAccessList>,
1342 ) -> Result<(), InsertBlockErrorKind>
1343 where
1344 V: PayloadValidator<T, Block = N::Block>,
1345 {
1346 let start = Instant::now();
1347
1348 trace!(target: "engine::tree::payload_validator", block=?block.num_hash(), "Validating block consensus");
1349
1350 let _enter =
1352 debug_span!(target: "engine::tree::payload_validator", "validate_block_post_execution")
1353 .entered();
1354 let built_bal = built_bal.map(Bal::from);
1355 let block_access_list_hash =
1356 built_bal.as_ref().map(|bal| bal.compute_hash_with_buf(&mut self.bal_hash_buf));
1357
1358 let validation_result = built_bal
1359 .as_ref()
1360 .map(|bal| bal.validate_gas_limit(block.gas_limit()).map_err(ConsensusError::from))
1361 .transpose()
1362 .and_then(|_| {
1363 self.consensus.validate_block_post_execution(
1364 block,
1365 output,
1366 receipt_root_bloom,
1367 block_access_list_hash,
1368 )
1369 });
1370
1371 if let Err(err) = validation_result {
1372 self.on_invalid_block(parent_block, block, output, None, ctx.state_mut());
1374 return Err(err.into())
1375 }
1376 drop(_enter);
1377
1378 self.metrics
1380 .block_validation
1381 .post_execution_validation_duration
1382 .record(start.elapsed().as_secs_f64());
1383
1384 Ok(())
1385 }
1386
1387 #[instrument(
1392 level = "debug",
1393 target = "engine::tree::payload_validator",
1394 skip_all,
1395 fields(
1396 has_hint_stream = hint_stream.is_some(),
1397 has_hashed_update_stream = hashed_update_stream.is_some(),
1398 parallel_bal_execution
1399 )
1400 )]
1401 fn spawn_payload_processor<T: ExecutableTxIterator<Evm>>(
1402 &self,
1403 env: ExecutionEnv<Evm>,
1404 txs: T,
1405 state_provider_factory: OverlayStateProviderFactory<P, N>,
1406 hint_stream: Option<StateRootHintStream>,
1407 hashed_update_stream: Option<StateRootUpdateStream>,
1408 parallel_bal_execution: bool,
1409 ) -> Result<
1410 PayloadHandle<
1411 impl ExecutableTxFor<Evm> + use<N, P, Evm, V, T>,
1412 impl core::error::Error + Send + Sync + 'static + use<N, P, Evm, V, T>,
1413 N::Receipt,
1414 EvmTypesFor<Evm>,
1415 >,
1416 InsertBlockErrorKind,
1417 > {
1418 let start = Instant::now();
1419 let handle = self.payload_processor.spawn_with_state_root_streams(
1420 env,
1421 txs,
1422 state_provider_factory,
1423 hint_stream,
1424 hashed_update_stream,
1425 parallel_bal_execution,
1426 );
1427
1428 self.metrics.block_validation.spawn_payload_processor.record(start.elapsed().as_secs_f64());
1429
1430 Ok(handle)
1431 }
1432
1433 fn overlay_state_provider_factory(
1437 &self,
1438 hash: B256,
1439 state: &EngineApiTreeState<N>,
1440 ) -> ProviderResult<Option<OverlayStateProviderFactory<P, N>>> {
1441 if !state.tree_state.contains_hash(&hash) && self.provider.header(hash)?.is_none() {
1442 debug!(target: "engine::tree::payload_validator", %hash, "no canonical state found for block");
1443 return Ok(None)
1444 }
1445
1446 Ok(Some(OverlayStateProviderFactory::new(
1447 self.provider.clone(),
1448 state.tree_state.overlay_manager.overlay_builder(hash),
1449 )))
1450 }
1451
1452 fn on_invalid_block(
1454 &self,
1455 parent_header: &SealedHeader<N::BlockHeader>,
1456 block: &RecoveredBlock<N::Block>,
1457 output: &BlockExecutionOutput<N::Receipt>,
1458 trie_updates: Option<(&TrieUpdates, B256)>,
1459 state: &mut EngineApiTreeState<N>,
1460 ) {
1461 if state.invalid_headers.get(&block.hash()).is_some() {
1462 return
1464 }
1465 self.invalid_block_hook.on_invalid_block(parent_header, block, output, trie_updates);
1466 }
1467
1468 fn payload_state_root_handle_for(
1471 &self,
1472 parent_hash: B256,
1473 parent_header: &N::BlockHeader,
1474 timestamp: u64,
1475 state: &mut EngineApiTreeState<N>,
1476 ) -> Option<PayloadStateRootHandle> {
1477 let state_provider_factory = match self.overlay_state_provider_factory(parent_hash, state) {
1478 Ok(Some(state_provider_factory)) => state_provider_factory,
1479 Ok(None) => return None,
1480 Err(err) => {
1481 warn!(
1482 target: "engine::tree::payload_validator",
1483 %err,
1484 %parent_hash,
1485 "failed to prepare payload-builder state-root provider"
1486 );
1487 return None
1488 }
1489 };
1490 match self.state_root_strategy.prepare_payload_builder(PayloadStateRootJobContext::new(
1491 &self.runtime,
1492 &self.overlay_manager,
1493 parent_hash,
1494 parent_header,
1495 timestamp,
1496 state,
1497 state_provider_factory,
1498 &self.config,
1499 )) {
1500 Ok(handle) => handle,
1501 Err(err) => {
1502 warn!(
1503 target: "engine::tree::payload_validator",
1504 %err,
1505 %parent_hash,
1506 "failed to prepare payload-builder state-root job"
1507 );
1508 None
1509 }
1510 }
1511 }
1512
1513 fn spawn_deferred_trie_task(
1526 &self,
1527 block: Arc<RecoveredBlock<N::Block>>,
1528 execution_outcome: Arc<BlockExecutionOutput<N::Receipt>>,
1529 hashed_state: LazyHashedPostState,
1530 trie_output: Arc<TrieUpdates>,
1531 ) -> ExecutedBlock<N> {
1532 let hashed_state = match hashed_state.try_into_inner() {
1536 Ok(state) => state,
1537 Err(handle) => handle.get().clone(),
1538 };
1539 let (deferred_trie_data, deferred_trie_task) =
1540 LazyTrieData::pending(hashed_state, trie_output);
1541 let block_validation_metrics = self.metrics.block_validation.clone();
1542
1543 let block_number = block.number();
1545
1546 let compute_trie_input_task = move || {
1548 let _span = debug_span!(
1549 target: "engine::tree::payload_validator",
1550 "compute_trie_input_task",
1551 block_number
1552 )
1553 .entered();
1554
1555 let compute_start = Instant::now();
1556 let computed = deferred_trie_task.compute_and_publish();
1557 block_validation_metrics
1558 .deferred_trie_compute_duration
1559 .record(compute_start.elapsed().as_secs_f64());
1560
1561 block_validation_metrics
1563 .hashed_post_state_size
1564 .record(computed.hashed_state.total_len() as f64);
1565 block_validation_metrics
1566 .trie_updates_sorted_size
1567 .record(computed.trie_updates.total_len() as f64);
1568 };
1569
1570 self.runtime.spawn_blocking_named(DEFERRED_TRIE_WORKER_NAME, compute_trie_input_task);
1572
1573 ExecutedBlock::with_deferred_trie_data(block, execution_outcome, deferred_trie_data)
1574 }
1575
1576 fn calculate_timing_stats(
1577 &self,
1578 block: &RecoveredBlock<N::Block>,
1579 provider_stats: Arc<StateProviderStats>,
1580 cache_stats: Option<Arc<CacheStats>>,
1581 output: &BlockExecutionOutput<N::Receipt>,
1582 execution_duration: Duration,
1583 state_hash_duration: Duration,
1584 ) -> Box<ExecutionTimingStats> {
1585 let accounts_read = provider_stats.total_account_fetches();
1586 let storage_read = provider_stats.total_storage_fetches();
1587 let code_read = provider_stats.total_code_fetches();
1588 let code_bytes_read = provider_stats.total_code_fetched_bytes();
1589
1590 let accounts_changed = output.state.len();
1592 let accounts_deleted =
1593 output.state.state.values().filter(|acc| acc.was_destroyed()).count();
1594 let storage_slots_changed =
1595 output.state.state.values().map(|account| account.storage.len()).sum::<usize>();
1596 let storage_slots_deleted = output
1597 .state
1598 .state
1599 .values()
1600 .flat_map(|account| account.storage.values())
1601 .filter(|slot| {
1602 slot.present_value.is_zero() && !slot.previous_or_original_value.is_zero()
1603 })
1604 .count();
1605
1606 let is_new_deployment = |acc: &BundleAccount| -> bool {
1608 let has_code_now = acc.info.as_ref().is_some_and(|info| !info.is_empty_code_hash());
1609 let had_no_code_before =
1610 acc.original_info.as_ref().is_none_or(AccountInfo::is_empty_code_hash);
1611 has_code_now && had_no_code_before
1612 };
1613
1614 let bytecodes_changed =
1615 output.state.state.values().filter(|acc| is_new_deployment(acc)).count();
1616
1617 let unique_new_code_hashes: B256Set = output
1619 .state
1620 .state
1621 .values()
1622 .filter(|acc| is_new_deployment(acc))
1623 .filter_map(|acc| acc.info.as_ref().map(|info| info.code_hash))
1624 .collect();
1625 let code_bytes_written: usize = unique_new_code_hashes
1626 .iter()
1627 .filter_map(|hash| output.state.contracts.get(hash).map(|bytecode| bytecode.len()))
1628 .sum();
1629
1630 let state_read_duration = provider_stats.total_account_fetch_latency() +
1632 provider_stats.total_storage_fetch_latency() +
1633 provider_stats.total_code_fetch_latency();
1634
1635 let eip7702_delegations_set =
1638 output.state.contracts.values().filter(|bytecode| bytecode.is_eip7702()).count();
1639 let eip7702_delegations_cleared = output
1644 .state
1645 .state
1646 .values()
1647 .filter(|acc| {
1648 let original_was_eip7702 = acc
1650 .original_info
1651 .as_ref()
1652 .and_then(|info| info.code.as_ref())
1653 .is_some_and(|bytecode| bytecode.is_eip7702());
1654
1655 let code_now_empty = acc.info.as_ref().is_some_and(AccountInfo::is_empty_code_hash);
1657
1658 original_was_eip7702 && code_now_empty
1659 })
1660 .count();
1661
1662 let (account_cache_hits, account_cache_misses) = cache_stats
1664 .as_ref()
1665 .map(|s| (s.account_hits(), s.account_misses()))
1666 .unwrap_or_default();
1667 let (storage_cache_hits, storage_cache_misses) = cache_stats
1668 .as_ref()
1669 .map(|s| (s.storage_hits(), s.storage_misses()))
1670 .unwrap_or_default();
1671 let (code_cache_hits, code_cache_misses) =
1672 cache_stats.as_ref().map(|s| (s.code_hits(), s.code_misses())).unwrap_or_default();
1673 let (txpool_snapshot_account_hits, txpool_snapshot_account_misses) = cache_stats
1674 .as_ref()
1675 .map(|s| (s.txpool_snapshot_account_hits(), s.txpool_snapshot_account_misses()))
1676 .unwrap_or_default();
1677 let (txpool_snapshot_storage_hits, txpool_snapshot_storage_misses) = cache_stats
1678 .as_ref()
1679 .map(|s| (s.txpool_snapshot_storage_hits(), s.txpool_snapshot_storage_misses()))
1680 .unwrap_or_default();
1681 let (txpool_snapshot_code_hits, txpool_snapshot_code_misses) = cache_stats
1682 .as_ref()
1683 .map(|s| (s.txpool_snapshot_code_hits(), s.txpool_snapshot_code_misses()))
1684 .unwrap_or_default();
1685
1686 Box::new(ExecutionTimingStats {
1688 block_number: block.number(),
1689 block_hash: block.hash(),
1690 gas_used: output.result.gas_used,
1691 tx_count: block.transaction_count(),
1692 execution_duration,
1693 state_read_duration,
1694 state_hash_duration,
1695 accounts_read,
1696 storage_read,
1697 code_read,
1698 code_bytes_read,
1699 accounts_changed,
1700 accounts_deleted,
1701 storage_slots_changed,
1702 storage_slots_deleted,
1703 bytecodes_changed,
1704 code_bytes_written,
1705 eip7702_delegations_set,
1706 eip7702_delegations_cleared,
1707 account_cache_hits,
1708 account_cache_misses,
1709 storage_cache_hits,
1710 storage_cache_misses,
1711 code_cache_hits,
1712 code_cache_misses,
1713 txpool_snapshot_account_hits,
1714 txpool_snapshot_account_misses,
1715 txpool_snapshot_storage_hits,
1716 txpool_snapshot_storage_misses,
1717 txpool_snapshot_code_hits,
1718 txpool_snapshot_code_misses,
1719 })
1720 }
1721}
1722
1723pub trait EngineValidator<
1727 Types: PayloadTypes,
1728 N: NodePrimitives = <<Types as PayloadTypes>::BuiltPayload as BuiltPayload>::Primitives,
1729>: Send + Sync + 'static
1730{
1731 fn validate_payload_attributes_against_header(
1741 &self,
1742 attr: &Types::PayloadAttributes,
1743 header: &N::BlockHeader,
1744 ) -> Result<(), InvalidPayloadAttributesError>;
1745
1746 fn convert_payload_to_block(
1755 &self,
1756 payload: Types::ExecutionData,
1757 ) -> Result<SealedBlock<N::Block>, NewPayloadError>;
1758
1759 fn validate_payload(
1761 &mut self,
1762 payload: Types::ExecutionData,
1763 ctx: TreeCtx<'_, N>,
1764 ) -> ValidationOutcome<N>;
1765
1766 fn validate_block(
1768 &mut self,
1769 block: SealedBlockWithAccessList<N::Block>,
1770 ctx: TreeCtx<'_, N>,
1771 ) -> ValidationOutcome<N>;
1772
1773 fn on_inserted_executed_block(
1778 &self,
1779 block: BuiltPayloadExecutedBlock<N>,
1780 ) -> ProviderResult<ExecutedBlock<N>>;
1781
1782 fn on_canonical_head_changed(&self, _hash: B256, _state: &EngineApiTreeState<N>) {}
1786
1787 fn payload_builder_resources(
1791 &self,
1792 parent_hash: B256,
1793 parent_header: &N::BlockHeader,
1794 timestamp: u64,
1795 state: &mut EngineApiTreeState<N>,
1796 ) -> PayloadBuilderResources;
1797}
1798
1799impl<N, Types, P, Evm, V> EngineValidator<Types> for BasicEngineValidator<P, Evm, V>
1800where
1801 P: DatabaseProviderFactory<
1802 Provider: BlockReader
1803 + StageCheckpointReader
1804 + PruneCheckpointReader
1805 + ChangeSetReader
1806 + StorageChangeSetReader
1807 + StorageSettingsCache
1808 + HistoryReader
1809 + 'static,
1810 > + BlockReader<Header = N::BlockHeader>
1811 + StateProviderFactory
1812 + StateReader
1813 + ChangeSetReader
1814 + Clone
1815 + 'static,
1816 N: NodePrimitives,
1817 V: PayloadValidator<Types, Block = N::Block> + Clone,
1818 Evm: ConfigureEngineEvm<Types::ExecutionData, Primitives = N> + 'static,
1819 Types: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>,
1820{
1821 fn validate_payload_attributes_against_header(
1822 &self,
1823 attr: &Types::PayloadAttributes,
1824 header: &N::BlockHeader,
1825 ) -> Result<(), InvalidPayloadAttributesError> {
1826 self.validator.validate_payload_attributes_against_header(attr, header)
1827 }
1828
1829 fn convert_payload_to_block(
1830 &self,
1831 payload: Types::ExecutionData,
1832 ) -> Result<SealedBlock<N::Block>, NewPayloadError> {
1833 let block = self.validator.convert_payload_to_block(payload)?;
1834 Ok(block)
1835 }
1836
1837 fn validate_payload(
1838 &mut self,
1839 payload: Types::ExecutionData,
1840 ctx: TreeCtx<'_, N>,
1841 ) -> ValidationOutcome<N> {
1842 self.validate_block_with_state(BlockOrPayload::Payload(payload), ctx)
1843 }
1844
1845 fn validate_block(
1846 &mut self,
1847 block: SealedBlockWithAccessList<N::Block>,
1848 ctx: TreeCtx<'_, N>,
1849 ) -> ValidationOutcome<N> {
1850 self.validate_block_with_state(BlockOrPayload::Block(block), ctx)
1851 }
1852
1853 fn on_inserted_executed_block(
1854 &self,
1855 block: BuiltPayloadExecutedBlock<N>,
1856 ) -> ProviderResult<ExecutedBlock<N>> {
1857 self.payload_processor.on_inserted_executed_block(
1858 block.recovered_block.block_with_parent(),
1859 &block.execution_output.state,
1860 );
1861
1862 Ok(self.spawn_deferred_trie_task(
1863 block.recovered_block,
1864 block.execution_output,
1865 LazyHashedPostState::ready(block.hashed_state),
1866 block.trie_updates,
1867 ))
1868 }
1869
1870 fn on_canonical_head_changed(&self, hash: B256, state: &EngineApiTreeState<N>) {
1871 let Some(txpool_prewarm) = self.txpool_prewarm.as_ref() else { return };
1872
1873 let parent = match self.sealed_header_by_hash(hash, state) {
1876 Ok(Some(header)) => header,
1877 Ok(None) => return,
1878 Err(err) => {
1879 trace!(
1880 target: "engine::tree::txpool_prewarm",
1881 %err,
1882 block_hash = ?hash,
1883 "failed to fetch canonical header for txpool prewarming"
1884 );
1885 return
1886 }
1887 };
1888 let evm_env = match self.evm_config.evm_env(parent.header()) {
1893 Ok(evm_env) => evm_env,
1894 Err(err) => {
1895 trace!(
1896 target: "engine::tree::txpool_prewarm",
1897 %err,
1898 block_hash = ?parent.hash(),
1899 "failed to derive canonical txpool prewarming environment"
1900 );
1901 return
1902 }
1903 };
1904
1905 let state_provider_factory = match self.overlay_state_provider_factory(parent.hash(), state)
1906 {
1907 Ok(Some(state_provider_factory)) => state_provider_factory,
1908 Ok(None) => return,
1909 Err(err) => {
1910 trace!(
1911 target: "engine::tree::txpool_prewarm",
1912 %err,
1913 block_hash = ?parent.hash(),
1914 "failed to derive canonical txpool prewarming provider"
1915 );
1916 return
1917 }
1918 };
1919 txpool_prewarm.start(&parent, evm_env, state_provider_factory)
1920 }
1921
1922 fn payload_builder_resources(
1923 &self,
1924 parent_hash: B256,
1925 parent_header: &N::BlockHeader,
1926 timestamp: u64,
1927 state: &mut EngineApiTreeState<N>,
1928 ) -> PayloadBuilderResources {
1929 let execution_cache = self
1930 .config
1931 .share_execution_cache_with_payload_builder()
1932 .then(|| self.payload_processor.cache_for(parent_hash))
1933 .flatten();
1934 let state_root_handle =
1935 self.payload_state_root_handle_for(parent_hash, parent_header, timestamp, state);
1936 let mut resources = PayloadBuilderResources::new(execution_cache, state_root_handle)
1937 .with_lease(PayloadBuilderLease::new(JitPauseGuard::new(&self.evm_config)));
1938 if let Some(txpool_prewarm) = self.txpool_prewarm.as_ref() {
1942 let txpool_lease = PayloadBuilderLease::new(txpool_prewarm.pause());
1943 resources = resources.with_lease(txpool_lease);
1944 }
1945 resources
1946 }
1947}
1948
1949impl<P, Evm, V> WaitForCaches for BasicEngineValidator<P, Evm, V>
1950where
1951 Evm: ConfigureEvm,
1952{
1953 fn wait_for_caches(&self) -> CacheWaitDurations {
1954 debug!(target: "engine::tree::payload_validator", "Waiting for execution cache and sparse trie locks");
1955
1956 let execution_cache = self.payload_processor.execution_cache();
1957 let overlay_manager = self.overlay_manager.clone();
1958 let (execution_tx, execution_rx) = std::sync::mpsc::channel();
1959 let (sparse_trie_tx, sparse_trie_rx) = std::sync::mpsc::channel();
1960
1961 self.runtime.spawn_blocking_named("wait-exec-cache", move || {
1962 let _ = execution_tx.send(execution_cache.wait_for_availability());
1963 });
1964 self.runtime.spawn_blocking_named("wait-sparse-tri", move || {
1965 let _ = sparse_trie_tx.send(overlay_manager.wait_for_sparse_trie_availability());
1966 });
1967
1968 let execution_cache =
1969 execution_rx.recv().expect("execution cache wait task failed to send result");
1970 let sparse_trie =
1971 sparse_trie_rx.recv().expect("sparse trie wait task failed to send result");
1972 debug!(
1973 target: "engine::tree::payload_validator",
1974 ?execution_cache,
1975 ?sparse_trie,
1976 "Execution cache and sparse trie locks acquired"
1977 );
1978 CacheWaitDurations { execution_cache, sparse_trie }
1979 }
1980}
1981
1982#[derive(Debug, Clone)]
1984pub enum BlockOrPayload<T: PayloadTypes> {
1985 Payload(T::ExecutionData),
1987 Block(SealedBlockWithAccessList<BlockTy<<T::BuiltPayload as BuiltPayload>::Primitives>>),
1989}
1990
1991impl<T: PayloadTypes> BlockOrPayload<T> {
1992 pub fn hash(&self) -> B256 {
1994 match self {
1995 Self::Payload(payload) => payload.block_hash(),
1996 Self::Block(block) => block.hash(),
1997 }
1998 }
1999
2000 pub fn num_hash(&self) -> NumHash {
2002 match self {
2003 Self::Payload(payload) => payload.num_hash(),
2004 Self::Block(block) => block.num_hash(),
2005 }
2006 }
2007
2008 pub fn parent_hash(&self) -> B256 {
2010 match self {
2011 Self::Payload(payload) => payload.parent_hash(),
2012 Self::Block(block) => block.parent_hash(),
2013 }
2014 }
2015
2016 pub fn block_with_parent(&self) -> BlockWithParent {
2018 match self {
2019 Self::Payload(payload) => payload.block_with_parent(),
2020 Self::Block(block) => block.block_with_parent(),
2021 }
2022 }
2023
2024 pub const fn type_name(&self) -> &'static str {
2026 match self {
2027 Self::Payload(_) => "payload",
2028 Self::Block(_) => "block",
2029 }
2030 }
2031
2032 pub const fn is_payload(&self) -> bool {
2034 matches!(self, Self::Payload(_))
2035 }
2036
2037 pub const fn is_block(&self) -> bool {
2039 matches!(self, Self::Block(_))
2040 }
2041
2042 pub fn try_decoded_access_list(&self) -> Result<Option<DecodedBal>, alloy_rlp::Error> {
2044 match self {
2045 Self::Payload(payload) => payload
2046 .block_access_list()
2047 .map(|block_access_list| DecodedBal::from_rlp_bytes(block_access_list.clone()))
2048 .transpose(),
2049 Self::Block(block) => block.data().clone().map(DecodedBal::from_raw_bal).transpose(),
2050 }
2051 }
2052
2053 pub fn has_block_access_list(&self) -> bool {
2055 match self {
2056 Self::Payload(payload) => payload.block_access_list().is_some(),
2057 Self::Block(block) => block.block_access_list_hash().is_some(),
2058 }
2059 }
2060
2061 pub fn transaction_count(&self) -> usize
2063 where
2064 T::ExecutionData: ExecutionPayload,
2065 {
2066 match self {
2067 Self::Payload(payload) => payload.transaction_count(),
2068 Self::Block(block) => block.transaction_count(),
2069 }
2070 }
2071
2072 pub fn withdrawals(&self) -> Option<&[Withdrawal]>
2074 where
2075 T::ExecutionData: ExecutionPayload,
2076 {
2077 match self {
2078 Self::Payload(payload) => payload.withdrawals().map(|w| w.as_slice()),
2079 Self::Block(block) => block.body().withdrawals().map(|w| w.as_slice()),
2080 }
2081 }
2082
2083 pub fn gas_used(&self) -> u64
2085 where
2086 T::ExecutionData: ExecutionPayload,
2087 {
2088 match self {
2089 Self::Payload(payload) => payload.gas_used(),
2090 Self::Block(block) => block.gas_used(),
2091 }
2092 }
2093
2094 pub fn gas_limit(&self) -> u64
2096 where
2097 T::ExecutionData: ExecutionPayload,
2098 {
2099 match self {
2100 Self::Payload(payload) => payload.gas_limit(),
2101 Self::Block(block) => block.gas_limit(),
2102 }
2103 }
2104}
2105
2106struct ExecutedBal {
2108 alloy: BlockAccessList,
2110 evm: Arc<EvmBal>,
2112}