1use crate::tree::{
99 error::{InsertBlockError, InsertBlockErrorKind, InsertPayloadError},
100 instrumented_state::{InstrumentedStateProvider, StateProviderMetrics, StateProviderStats},
101 payload_processor::PayloadProcessor,
102 precompile_cache::{CachedPrecompile, CachedPrecompileMetrics, PrecompileCacheMap},
103 txpool_prewarm,
104 types::{InsertPayloadResult, ValidationOutput},
105 CacheWaitDurations, CachedStateProvider, EngineApiMetrics, EngineApiTreeState, ExecutionEnv,
106 PayloadHandle, StateProviderBuilder, StateProviderDatabase, TreeConfig, WaitForCaches,
107};
108use alloy_consensus::transaction::{Either, TxHashRef};
109use alloy_eip7928::{bal::DecodedBal, compute_block_access_list_hash_with_buf, BlockAccessList};
110use alloy_eips::{eip1898::BlockWithParent, eip4895::Withdrawal, NumHash};
111use alloy_evm::Evm;
112use alloy_primitives::{
113 map::{AddressMap, B256Set},
114 B256,
115};
116use reth_tasks::LazyHandle;
117
118use crate::tree::{
119 payload_processor::receipt_root_task::{IndexedReceipt, ReceiptRootTaskHandle},
120 state_root_strategy::{
121 DefaultStateRootStrategy, LazyHashedPostState, PayloadStateRootHandle,
122 PayloadStateRootJobContext, StateRootHintStream, StateRootJobContext, StateRootStrategy,
123 StateRootUpdateStream,
124 },
125};
126use alloy_consensus::constants::KECCAK_EMPTY;
127use alloy_primitives::Address;
128use reth_chain_state::{CanonicalInMemoryState, ExecutedBlock, ExecutionTimingStats};
129use reth_consensus::{ConsensusError, FullConsensus, ReceiptRootBloom};
130use reth_engine_primitives::{
131 ConfigureEngineEvm, ExecutableTxIterator, ExecutionPayload, InvalidBlockHook, PayloadValidator,
132};
133use reth_errors::{BlockExecutionError, ProviderResult};
134use reth_evm::{
135 block::BlockExecutor, execute::ExecutableTxFor, ConfigureEvm, EvmEnvFor, ExecutionCtxFor,
136 OnStateHook, SpecFor,
137};
138use reth_execution_cache::{CacheFillMode, CacheStats};
139use reth_payload_builder::{PayloadBuilderLease, PayloadBuilderResources};
140use reth_payload_primitives::{
141 BuiltPayload, BuiltPayloadExecutedBlock, InvalidPayloadAttributesError, NewPayloadError,
142 PayloadTypes,
143};
144use reth_primitives_traits::{
145 AlloyBlockHeader, BlockBody, BlockTy, FastInstant as Instant, GotExpected, NodePrimitives,
146 RecoveredBlock, SealedBlock, SealedHeader, SignerRecoverable,
147};
148use reth_provider::{
149 BlockExecutionOutput, BlockReader, ChangeSetReader, DatabaseProviderFactory,
150 DatabaseProviderROFactory, ProviderError, PruneCheckpointReader, StageCheckpointReader,
151 StateProvider, StateProviderBox, StateProviderFactory, StateReader, StorageChangeSetReader,
152 StorageSettingsCache, TryIntoHistoricalStateProvider,
153};
154use reth_revm::db::{states::bundle_state::BundleRetention, BundleAccount, State};
155use reth_storage_overlay::{OverlayManager, OverlayStateProviderFactory};
156use reth_trie::{
157 hashed_cursor::HashedCursorFactory, trie_cursor::TrieCursorFactory, updates::TrieUpdates,
158 HashedPostState, KeccakKeyHasher, LazyTrieData,
159};
160use std::{
161 sync::{
162 atomic::{AtomicUsize, Ordering},
163 Arc,
164 },
165 time::Duration,
166};
167use tracing::{debug, debug_span, error, info, instrument, trace, warn, Level, Span};
168
169pub use crate::tree::types::ValidationOutcome;
170
171const MAX_EXPECTED_GAS_LIMIT_MULTIPLIER: u64 = 2;
174
175const DEFERRED_TRIE_WORKER_NAME: &str = "deferred-trie";
177
178type ReceiptRootSender<N> =
179 crossbeam_channel::Sender<IndexedReceipt<<N as NodePrimitives>::Receipt>>;
180type ReceiptRootReceiver = tokio::sync::oneshot::Receiver<(B256, alloy_primitives::Bloom)>;
181
182pub struct TreeCtx<'a, N: NodePrimitives> {
187 state: &'a mut EngineApiTreeState<N>,
189 canonical_in_memory_state: &'a CanonicalInMemoryState<N>,
191}
192
193impl<'a, N: NodePrimitives> std::fmt::Debug for TreeCtx<'a, N> {
194 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
195 f.debug_struct("TreeCtx")
196 .field("state", &"EngineApiTreeState")
197 .field("canonical_in_memory_state", &self.canonical_in_memory_state)
198 .finish()
199 }
200}
201
202impl<'a, N: NodePrimitives> TreeCtx<'a, N> {
203 pub const fn new(
205 state: &'a mut EngineApiTreeState<N>,
206 canonical_in_memory_state: &'a CanonicalInMemoryState<N>,
207 ) -> Self {
208 Self { state, canonical_in_memory_state }
209 }
210}
211
212impl<'a, N: NodePrimitives> TreeCtx<'a, N> {
213 pub const fn state(&self) -> &EngineApiTreeState<N> {
215 &*self.state
216 }
217
218 pub const fn state_mut(&mut self) -> &mut EngineApiTreeState<N> {
220 self.state
221 }
222
223 pub const fn canonical_in_memory_state(&self) -> &'a CanonicalInMemoryState<N> {
225 self.canonical_in_memory_state
226 }
227}
228
229struct JitPauseGuard<Evm: ConfigureEvm>(Evm);
235
236impl<Evm: ConfigureEvm> JitPauseGuard<Evm> {
237 fn new(evm_config: &Evm) -> Self {
238 if let Some(jit_backend) = evm_config.jit_backend() {
239 jit_backend.pause();
240 }
241 Self(evm_config.clone())
242 }
243}
244
245impl<Evm: ConfigureEvm> Drop for JitPauseGuard<Evm> {
246 fn drop(&mut self) {
247 if let Some(jit_backend) = self.0.jit_backend() {
248 jit_backend.resume();
249 }
250 }
251}
252
253#[derive(derive_more::Debug)]
261pub struct BasicEngineValidator<P, Evm, V>
262where
263 Evm: ConfigureEvm,
264{
265 provider: P,
267 consensus: Arc<dyn FullConsensus<Evm::Primitives>>,
269 evm_config: Evm,
271 config: TreeConfig,
273 payload_processor: PayloadProcessor<Evm>,
275 precompile_cache_map: PrecompileCacheMap<SpecFor<Evm>>,
277 precompile_cache_metrics: AddressMap<CachedPrecompileMetrics>,
279 #[debug(skip)]
281 invalid_block_hook: Box<dyn InvalidBlockHook<Evm::Primitives>>,
282 metrics: EngineApiMetrics,
284 validator: V,
286 runtime: reth_tasks::Runtime,
288 overlay_manager: OverlayManager<Evm::Primitives>,
290 #[debug(skip)]
292 state_root_strategy: Arc<dyn StateRootStrategy<Evm::Primitives, P, Evm>>,
293 #[debug(skip)]
297 txpool_prewarm: Option<txpool_prewarm::Handle<Evm::Primitives, P, Evm>>,
298 bal_hash_buf: Vec<u8>,
300}
301
302impl<N, P, Evm, V> BasicEngineValidator<P, Evm, V>
303where
304 N: NodePrimitives,
305 P: DatabaseProviderFactory<
306 Provider: BlockReader
307 + StageCheckpointReader
308 + PruneCheckpointReader
309 + ChangeSetReader
310 + StorageChangeSetReader
311 + StorageSettingsCache
312 + TryIntoHistoricalStateProvider
313 + 'static,
314 > + BlockReader<Header = N::BlockHeader>
315 + ChangeSetReader
316 + StateProviderFactory
317 + StateReader
318 + Clone
319 + 'static,
320 OverlayStateProviderFactory<P, N>: DatabaseProviderROFactory<Provider: TrieCursorFactory + HashedCursorFactory>
321 + Clone
322 + 'static,
323 Evm: ConfigureEvm<Primitives = N> + 'static,
324{
325 #[expect(clippy::too_many_arguments)]
327 pub fn new(
328 provider: P,
329 consensus: Arc<dyn FullConsensus<N>>,
330 evm_config: Evm,
331 validator: V,
332 config: TreeConfig,
333 invalid_block_hook: Box<dyn InvalidBlockHook<N>>,
334 overlay_manager: OverlayManager<N>,
335 runtime: reth_tasks::Runtime,
336 ) -> Self {
337 let precompile_cache_map = PrecompileCacheMap::default();
338 let payload_processor = PayloadProcessor::new(
339 runtime.clone(),
340 evm_config.clone(),
341 &config,
342 precompile_cache_map.clone(),
343 );
344 Self {
345 provider,
346 consensus,
347 evm_config,
348 payload_processor,
349 precompile_cache_map,
350 precompile_cache_metrics: AddressMap::default(),
351 config,
352 invalid_block_hook,
353 metrics: EngineApiMetrics::default(),
354 validator,
355 runtime,
356 overlay_manager,
357 state_root_strategy: Arc::new(DefaultStateRootStrategy::default()),
358 txpool_prewarm: None,
359 bal_hash_buf: Vec::new(),
360 }
361 }
362
363 pub fn with_state_root_strategy(
365 mut self,
366 state_root_strategy: Arc<dyn StateRootStrategy<N, P, Evm>>,
367 ) -> Self {
368 self.state_root_strategy = state_root_strategy;
369 self
370 }
371
372 pub fn with_txpool_prewarming(
374 mut self,
375 source: impl crate::tree::TxPoolPrewarmSource<N> + 'static,
376 ) -> Self {
377 self.txpool_prewarm = Some(txpool_prewarm::Handle::spawn(
378 &self.runtime,
379 Arc::new(source),
380 self.evm_config.clone(),
381 ));
382 self
383 }
384
385 #[instrument(level = "debug", target = "engine::tree::payload_validator", skip_all)]
387 pub fn convert_to_block<T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
388 &self,
389 input: BlockOrPayload<T>,
390 ) -> Result<SealedBlock<N::Block>, NewPayloadError>
391 where
392 V: PayloadValidator<T, Block = N::Block>,
393 {
394 match input {
395 BlockOrPayload::Payload(payload) => self.validator.convert_payload_to_block(payload),
396 BlockOrPayload::Block(block) => Ok(block),
397 }
398 }
399
400 pub fn evm_env_for<T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
402 &self,
403 input: &BlockOrPayload<T>,
404 ) -> Result<EvmEnvFor<Evm>, Evm::Error>
405 where
406 V: PayloadValidator<T, Block = N::Block>,
407 Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
408 {
409 match input {
410 BlockOrPayload::Payload(payload) => Ok(self.evm_config.evm_env_for_payload(payload)?),
411 BlockOrPayload::Block(block) => Ok(self.evm_config.evm_env(block.header())?),
412 }
413 }
414
415 pub fn tx_iterator_for<'a, T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
417 &'a self,
418 input: &'a BlockOrPayload<T>,
419 ) -> Result<impl ExecutableTxIterator<Evm>, NewPayloadError>
420 where
421 V: PayloadValidator<T, Block = N::Block>,
422 Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
423 {
424 Ok(match input {
425 BlockOrPayload::Payload(payload) => {
426 let iter = self
427 .evm_config
428 .tx_iterator_for_payload(payload)
429 .map_err(NewPayloadError::other)?;
430 Either::Left(iter)
431 }
432 BlockOrPayload::Block(block) => {
433 let txs = block.body().clone_transactions();
434 let convert = |tx: N::SignedTx| tx.try_into_recovered();
435 Either::Right((txs, convert))
436 }
437 })
438 }
439
440 pub fn execution_ctx_for<'a, T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
442 &self,
443 input: &'a BlockOrPayload<T>,
444 ) -> Result<ExecutionCtxFor<'a, Evm>, Evm::Error>
445 where
446 V: PayloadValidator<T, Block = N::Block>,
447 Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
448 {
449 match input {
450 BlockOrPayload::Payload(payload) => Ok(self.evm_config.context_for_payload(payload)?),
451 BlockOrPayload::Block(block) => Ok(self.evm_config.context_for_block(block)?),
452 }
453 }
454
455 #[instrument(
463 level = "debug",
464 target = "engine::tree::payload_validator",
465 skip_all,
466 fields(
467 parent = ?input.parent_hash(),
468 type_name = ?input.type_name(),
469 )
470 )]
471 pub fn validate_block_with_state<T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
472 &mut self,
473 input: BlockOrPayload<T>,
474 mut ctx: TreeCtx<'_, N>,
475 ) -> InsertPayloadResult<N>
476 where
477 V: PayloadValidator<T, Block = N::Block> + Clone,
478 Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
479 {
480 let parent_hash = input.parent_hash();
481 let _txpool_pause = self.txpool_prewarm.as_ref().map(txpool_prewarm::Handle::pause);
482 let txpool_snapshot =
483 self.txpool_prewarm.as_ref().and_then(|prewarmer| prewarmer.snapshot(parent_hash));
484 let _jit_pause = JitPauseGuard::new(&self.evm_config);
485
486 let parent_block = match self.sealed_header_by_hash(parent_hash, ctx.state()) {
489 Ok(Some(parent_block)) => parent_block,
490 Ok(None) => {
491 return Err(InsertBlockError::new(
492 self.convert_to_block(input)?,
493 ProviderError::HeaderNotFound(parent_hash.into()).into(),
494 )
495 .into())
496 }
497 Err(e) => {
498 return Err(InsertBlockError::new(self.convert_to_block(input)?, e.into()).into())
499 }
500 };
501
502 let validated_block = self.spawn_convert_and_validate(&input, parent_block.clone());
506
507 macro_rules! ensure_ok {
510 ($expr:expr) => {
511 match $expr {
512 Ok(val) => val,
513 Err(e) => {
514 let block = validated_block.try_into_inner().expect("sole handle")?;
515 return Err(InsertBlockError::new(block, e.into()).into())
516 }
517 }
518 };
519 }
520
521 macro_rules! ensure_ok_post_block {
523 ($expr:expr, $block:expr) => {
524 match $expr {
525 Ok(val) => val,
526 Err(e) => {
527 return Err(
528 InsertBlockError::new($block.into_sealed_block(), e.into()).into()
529 )
530 }
531 }
532 };
533 }
534
535 if input.gas_limit() >
538 parent_block.gas_limit().saturating_mul(MAX_EXPECTED_GAS_LIMIT_MULTIPLIER)
539 {
540 if validated_block.get().is_err() {
542 return Err(validated_block
543 .try_into_inner()
544 .expect("sole handle")
545 .expect_err("Err result checked"))
546 }
547 }
548
549 trace!(target: "engine::tree::payload_validator", "Fetching block state provider");
550 let _enter =
551 debug_span!(target: "engine::tree::payload_validator", "state_provider").entered();
552 let Some(provider_builder) =
553 ensure_ok!(self.state_provider_builder(parent_hash, ctx.state()))
554 else {
555 return Err(InsertBlockError::new(
557 validated_block.try_into_inner().expect("sole handle")?,
558 ProviderError::HeaderNotFound(parent_hash.into()).into(),
559 )
560 .into())
561 };
562 drop(_enter);
563
564 let evm_env = debug_span!(target: "engine::tree::payload_validator", "evm_env")
565 .in_scope(|| self.evm_env_for(&input))
566 .map_err(NewPayloadError::other)?;
567
568 let decoded_bal = ensure_ok!(input
571 .try_decoded_access_list()
572 .map_err(ConsensusError::BlockAccessListDecode))
573 .map(Arc::new);
574
575 if let Some(decoded_bal) = decoded_bal.as_deref() {
576 ensure_ok!(decoded_bal
578 .as_bal()
579 .validate_gas_limit(input.gas_limit())
580 .map_err(ConsensusError::from));
581 }
582
583 let env = ExecutionEnv {
584 evm_env,
585 hash: input.hash(),
586 parent_hash: input.parent_hash(),
587 parent_state_root: parent_block.state_root(),
588 transaction_count: input.transaction_count(),
589 gas_used: input.gas_used(),
590 withdrawals: input.withdrawals().map(|w| w.to_vec()),
591 decoded_bal: decoded_bal.as_ref().map(Arc::clone),
592 txpool_snapshot: txpool_snapshot.clone(),
593 };
594
595 let txs = self.tx_iterator_for(&input)?;
597
598 let provider_factory = self.provider.clone();
600 let overlay_builder = ctx.state().tree_state.overlay_manager.overlay_builder(parent_hash);
601 let overlay_factory = OverlayStateProviderFactory::new(provider_factory, overlay_builder);
602
603 let parallel_bal_execution = ensure_ok!(self.bal_path_eligible(env.decoded_bal.as_deref()));
604
605 let mut state_root_job =
607 ensure_ok!(self.state_root_strategy.prepare(StateRootJobContext::new(
608 &self.runtime,
609 &self.overlay_manager,
610 &env,
611 &parent_block,
612 provider_builder.clone(),
613 overlay_factory,
614 &self.config,
615 parallel_bal_execution,
616 ctx.state_mut(),
617 )));
618 let state_root_job_name = state_root_job.name();
619
620 debug!(
621 target: "engine::tree::payload_validator",
622 strategy = state_root_job_name,
623 "Prepared state root job"
624 );
625
626 let execution_state_hook = state_root_job.take_execution_hook();
629 let hint_stream = state_root_job.take_hint_stream();
632 let hashed_update_stream = state_root_job.take_hashed_update_stream();
633
634 let mut handle = ensure_ok!(self.spawn_payload_processor(
636 env.clone(),
637 txs,
638 provider_builder.clone(),
639 hint_stream,
640 hashed_update_stream,
641 parallel_bal_execution,
642 ));
643
644 let slow_block_enabled = self.config.slow_block_threshold().is_some();
646 let cache_stats = slow_block_enabled.then(|| Arc::new(CacheStats::default()));
647 let instrument_state_provider = slow_block_enabled || self.config.state_provider_metrics();
648 let state_provider_metrics =
649 instrument_state_provider.then(|| StateProviderMetrics::with_source("engine"));
650 let state_provider_stats =
651 instrument_state_provider.then(|| Arc::new(StateProviderStats::default()));
652 let execution_cache = handle.caches().map(|caches| (caches, handle.cache_metrics()));
653
654 let make_state_provider = |fill_on_miss: bool| -> ProviderResult<StateProviderBox> {
674 let provider = provider_builder.build()?;
675 let mut provider = if let Some((caches, cache_metrics)) = &execution_cache {
676 let fill_mode = if fill_on_miss {
677 CacheFillMode::FillOnMiss
678 } else {
679 CacheFillMode::LookupOnly
680 };
681 Box::new(
682 CachedStateProvider::new_with_mode(
683 provider,
684 caches.clone(),
685 fill_mode,
686 cache_metrics.clone(),
687 cache_stats.clone(),
688 )
689 .with_txpool_snapshot(txpool_snapshot.clone()),
690 ) as StateProviderBox
691 } else {
692 provider
693 };
694
695 if instrument_state_provider {
696 let stats = state_provider_stats
697 .as_ref()
698 .expect("instrumented state provider requires shared stats");
699 let metrics = state_provider_metrics
700 .as_ref()
701 .expect("instrumented state provider requires metrics");
702 provider = Box::new(InstrumentedStateProvider::with_stats(
703 provider,
704 metrics.clone(),
705 Arc::clone(stats),
706 ));
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, built_bal) = ensure_ok!(execution_result);
736
737 handle.stop_prewarming_execution();
739
740 let output = Arc::new(output);
744
745 let valid_block_tx = handle.terminate_caching(Some(output.clone()));
748
749 let hashed_state_output = output.clone();
753 let mut hashed_state_rx = state_root_job.take_hashed_state_rx();
754 let mut hashed_state: LazyHashedPostState =
755 self.runtime.spawn_blocking_named("hash-post-state", move || {
756 let _span = debug_span!(
757 target: "engine::tree::payload_validator",
758 "hashed_post_state",
759 )
760 .entered();
761 if let Some(Ok(state)) = hashed_state_rx.as_mut().map(|rx| rx.recv()) {
762 state
763 } else {
764 Arc::new(HashedPostState::from_bundle_state::<KeccakKeyHasher>(
765 hashed_state_output.state.state(),
766 ))
767 }
768 });
769
770 let block = validated_block.try_into_inner().expect("sole handle")?;
771 let block = block.with_senders(senders);
772
773 let receipt_root_bloom = {
775 let _enter = debug_span!(
776 target: "engine::tree::payload_validator",
777 "wait_receipt_root",
778 )
779 .entered();
780
781 receipt_root_rx
782 .blocking_recv()
783 .inspect_err(|_| {
784 tracing::error!(
785 target: "engine::tree::payload_validator",
786 "Receipt root task dropped sender without result, receipt root calculation likely aborted"
787 );
788 })
789 .ok()
790 };
791
792 ensure_ok_post_block!(
793 self.validate_post_execution(
794 &block,
795 &parent_block,
796 &output,
797 &mut ctx,
798 receipt_root_bloom,
799 built_bal
800 ),
801 block
802 );
803
804 let mut hashed_state_validate_result = debug_span!(
805 target: "engine::tree::payload_validator",
806 "validate_block_post_execution_with_hashed_state"
807 )
808 .in_scope(|| {
809 self.validator.validate_block_post_execution_with_hashed_state(
810 || hashed_state.get(),
811 &block,
812 &parent_block,
813 || provider_builder.build(),
814 )
815 });
816
817 let root_start = Instant::now();
818 let root_outcome = ensure_ok_post_block!(
819 state_root_job.finish(&block, output.clone(), &hashed_state),
820 block
821 );
822 let root_elapsed = root_start.elapsed();
823
824 info!(
825 target: "engine::tree::payload_validator",
826 strategy = state_root_job_name,
827 state_root = ?root_outcome.state_root,
828 elapsed = ?root_elapsed,
829 "State root job finished"
830 );
831
832 let state_root = root_outcome.state_root;
833 let trie_output = root_outcome.trie_updates;
834
835 if let Some(refreshed) = root_outcome.hashed_state {
839 hashed_state = LazyHandle::ready(refreshed);
840 hashed_state_validate_result = debug_span!(
841 target: "engine::tree::payload_validator",
842 "validate_block_post_execution_with_hashed_state"
843 )
844 .in_scope(|| {
845 self.validator.validate_block_post_execution_with_hashed_state(
846 || hashed_state.get(),
847 &block,
848 &parent_block,
849 || provider_builder.build(),
850 )
851 });
852 }
853
854 if let Err(err) = hashed_state_validate_result {
855 if err.is_validation_error() {
856 self.on_invalid_block(&parent_block, &block, &output, None, ctx.state_mut());
857 }
858 return Err(InsertBlockError::new(block.into_sealed_block(), err).into())
859 }
860
861 self.metrics.block_validation.record_state_root(&trie_output, root_elapsed.as_secs_f64());
862 self.metrics
863 .record_state_root_gas_bucket(block.header().gas_used(), root_elapsed.as_secs_f64());
864 debug!(target: "engine::tree::payload_validator", ?root_elapsed, "Calculated state root");
865
866 if state_root != block.header().state_root() {
868 self.on_invalid_block(
870 &parent_block,
871 &block,
872 &output,
873 Some((&trie_output, state_root)),
874 ctx.state_mut(),
875 );
876 let block_state_root = block.header().state_root();
877 return Err(InsertBlockError::new(
878 block.into_sealed_block(),
879 ConsensusError::BodyStateRootDiff(
880 GotExpected { got: state_root, expected: block_state_root }.into(),
881 )
882 .into(),
883 )
884 .into())
885 }
886
887 let timing_stats = state_provider_stats.filter(|_| slow_block_enabled).map(|stats| {
888 self.calculate_timing_stats(
889 &block,
890 stats,
891 cache_stats,
892 &output,
893 execution_duration,
894 root_elapsed,
895 )
896 });
897
898 if let Some(valid_block_tx) = valid_block_tx {
899 let _ = valid_block_tx.send(());
900 }
901
902 let executed_block =
903 self.spawn_deferred_trie_task(Arc::new(block), output, hashed_state, trie_output);
904 let raw_bal = decoded_bal.map(|decoded_bal| decoded_bal.as_raw_bal().clone());
905 Ok(ValidationOutput::new(executed_block, timing_stats).with_raw_bal(raw_bal))
906 }
907
908 #[expect(clippy::type_complexity)]
911 pub fn spawn_convert_and_validate<T>(
912 &self,
913 input: &BlockOrPayload<T>,
914 parent: SealedHeader<N::BlockHeader>,
915 ) -> LazyHandle<Result<SealedBlock<N::Block>, InsertPayloadError<N::Block>>>
916 where
917 T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>,
918 V: PayloadValidator<T, Block = N::Block> + Clone,
919 {
920 let input = input.clone();
921 let validator = self.validator.clone();
922 let consensus = self.consensus.clone();
923 let parent_span = Span::current();
924 self.runtime.spawn_blocking_named("payload-convert", move || {
925 let _span = debug_span!(
926 target: "engine::tree::payload_validator",
927 parent: parent_span,
928 "convert_and_validate",
929 )
930 .entered();
931 let block = match input {
932 BlockOrPayload::Block(block) => block,
933 BlockOrPayload::Payload(payload) => {
934 validator.convert_payload_to_block(payload)?
935 }
936 };
937
938 if let Err(e) = consensus.validate_header(block.sealed_header()) {
939 error!(target: "engine::tree::payload_validator", ?block, "Failed to validate header {}: {e}", block.hash());
940 return Err(InsertBlockError::consensus_error(e, block).into())
941 }
942
943 let _enter = debug_span!(target: "engine::tree::payload_validator", "validate_header_against_parent").entered();
945 if let Err(e) = consensus.validate_header_against_parent(block.sealed_header(), &parent)
946 {
947 warn!(target: "engine::tree::payload_validator", ?block, "Failed to validate header {} against parent: {e}", block.hash());
948 return Err(InsertBlockError::consensus_error(e, block).into())
949 }
950 drop(_enter);
951
952 if let Err(e) =
953 consensus.validate_block_pre_execution_with_tx_root(&block, None)
954 {
955 error!(target: "engine::tree::payload_validator", ?block, "Failed to validate block {}: {e}", block.hash());
956 return Err(InsertBlockError::consensus_error(e, block).into())
957 }
958
959 Ok(block)
960 })
961 }
962
963 fn sealed_header_by_hash(
965 &self,
966 hash: B256,
967 state: &EngineApiTreeState<N>,
968 ) -> ProviderResult<Option<SealedHeader<N::BlockHeader>>> {
969 let header = state.tree_state.sealed_header_by_hash(&hash);
971
972 if header.is_some() {
973 Ok(header)
974 } else {
975 self.provider.sealed_header_by_hash(hash)
976 }
977 }
978
979 #[instrument(level = "debug", target = "engine::tree::payload_validator", skip_all)]
987 #[expect(clippy::type_complexity)]
988 fn execute_block<S, Err, T>(
989 &mut self,
990 state_provider: S,
991 env: ExecutionEnv<Evm>,
992 input: &BlockOrPayload<T>,
993 handle: &mut PayloadHandle<impl ExecutableTxFor<Evm>, Err, N::Receipt>,
994 state_hook: Option<Box<dyn OnStateHook + 'static>>,
995 ) -> Result<
996 (
997 BlockExecutionOutput<N::Receipt>,
998 Vec<Address>,
999 ReceiptRootReceiver,
1000 Option<BlockAccessList>,
1001 ),
1002 InsertBlockErrorKind,
1003 >
1004 where
1005 S: StateProvider + Send,
1006 Err: core::error::Error + Send + Sync + 'static,
1007 V: PayloadValidator<T, Block = N::Block>,
1008 T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>,
1009 Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
1010 {
1011 debug!(target: "engine::tree::payload_validator", "Executing block");
1012
1013 let has_bal = env.decoded_bal.is_some();
1014 let mut db = debug_span!(target: "engine::tree", "build_state_db").in_scope(|| {
1015 State::builder()
1016 .with_database(StateProviderDatabase::new(state_provider))
1017 .with_bundle_update()
1018 .with_bal_builder_if(has_bal)
1019 .build()
1020 });
1021
1022 let (spec_id, mut executor) = {
1023 let _span = debug_span!(target: "engine::tree", "create_evm").entered();
1024 let spec_id = *env.evm_env.spec_id();
1025 let evm_config = self.evm_config.clone().with_jit_support();
1026 let evm = evm_config.evm_with_env(&mut db, env.evm_env);
1027 let ctx = self
1028 .execution_ctx_for(input)
1029 .map_err(|e| InsertBlockErrorKind::Other(Box::new(e)))?;
1030 let executor = self.evm_config.create_executor(evm, ctx);
1031 (spec_id, executor)
1032 };
1033
1034 if !self.config.precompile_cache_disabled() {
1035 let _span = debug_span!(target: "engine::tree", "setup_precompile_cache").entered();
1036 executor.evm_mut().precompiles_mut().map_cacheable_precompiles(
1037 |address, precompile| {
1038 let metrics = self
1039 .precompile_cache_metrics
1040 .entry(*address)
1041 .or_insert_with(|| CachedPrecompileMetrics::new_with_address(*address))
1042 .clone();
1043 CachedPrecompile::wrap(
1044 precompile,
1045 self.precompile_cache_map.cache_for_address(*address),
1046 spec_id,
1047 Some(metrics),
1048 )
1049 },
1050 );
1051 }
1052
1053 let transaction_count = input.transaction_count();
1054 let (receipt_tx, result_rx) = self.spawn_receipt_root_task(transaction_count);
1055 let executed_tx_index = Arc::clone(handle.executed_tx_index());
1056 executor.evm_mut().db_mut().set_state_hook(state_hook);
1057
1058 let execution_start = Instant::now();
1059
1060 let (executor, senders) = self.execute_transactions(
1062 executor,
1063 transaction_count,
1064 handle.iter_transactions(),
1065 &receipt_tx,
1066 &executed_tx_index,
1067 has_bal,
1068 )?;
1069 drop(receipt_tx);
1070
1071 let post_exec_start = Instant::now();
1073 let (_evm, result) = debug_span!(target: "engine::tree", "BlockExecutor::finish")
1074 .in_scope(|| executor.finish())
1075 .map(|(evm, result)| (evm.into_db(), result))?;
1076 self.metrics.record_post_execution(post_exec_start.elapsed());
1077
1078 debug_span!(target: "engine::tree", "merge_transitions")
1080 .in_scope(|| db.merge_transitions(BundleRetention::Reverts));
1081
1082 let built_bal = if has_bal { db.take_built_alloy_bal() } else { None };
1083 let output = BlockExecutionOutput { result, state: db.take_bundle() };
1084
1085 let execution_duration = execution_start.elapsed();
1086 self.metrics.record_block_execution(&output, execution_duration);
1087 self.metrics.record_block_execution_gas_bucket(output.result.gas_used, execution_duration);
1088 debug!(target: "engine::tree::payload_validator", elapsed = ?execution_duration, "Executed block");
1089
1090 Ok((output, senders, result_rx, built_bal))
1091 }
1092
1093 fn bal_path_eligible(&self, bal: Option<&DecodedBal>) -> Result<bool, InsertBlockErrorKind> {
1102 let has_bal = bal.is_some();
1103 let parallel_execution = has_bal && !self.config.disable_bal_parallel_execution();
1104 if parallel_execution && self.config.disable_bal_parallel_state_root() {
1105 return Err(InsertBlockErrorKind::Other(
1106 "disabling parallel state root is impossible when parallel execution is enabled"
1107 .into(),
1108 ));
1109 }
1110
1111 Ok(parallel_execution)
1112 }
1113
1114 #[instrument(level = "debug", target = "engine::tree::payload_validator", skip_all)]
1124 #[expect(clippy::type_complexity)]
1125 fn execute_block_bal<Tx, Err, MakeStateProvider, T>(
1126 &self,
1127 env: ExecutionEnv<Evm>,
1128 input: &BlockOrPayload<T>,
1129 handle: &PayloadHandle<Tx, Err, N::Receipt>,
1130 make_state_provider: &MakeStateProvider,
1131 ) -> Result<
1132 (
1133 BlockExecutionOutput<N::Receipt>,
1134 Vec<Address>,
1135 ReceiptRootReceiver,
1136 Option<BlockAccessList>,
1137 ),
1138 InsertBlockErrorKind,
1139 >
1140 where
1141 Tx: ExecutableTxFor<Evm> + Send,
1142 Err: core::error::Error + Send + Sync + 'static,
1143 MakeStateProvider: Fn(bool) -> ProviderResult<StateProviderBox> + Sync,
1144 Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
1145 T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>,
1146 V: PayloadValidator<T, Block = N::Block>,
1147 {
1148 debug!(target: "engine::tree::payload_validator", "Executing block via BAL path");
1149
1150 let (receipt_tx, result_rx) = self.spawn_receipt_root_task(env.transaction_count);
1151 let input_bal = env.decoded_bal.ok_or_else(|| {
1152 InsertBlockErrorKind::Other("BAL execute path: no decoded BAL available".into())
1153 })?;
1154
1155 let make_db = |fill_on_miss| {
1156 let provider = make_state_provider(fill_on_miss)
1157 .map_err(crate::tree::payload_processor::bal::BalExecutionError::Provider)?;
1158 Ok(StateProviderDatabase::new(provider))
1159 };
1160 let execution_start = Instant::now();
1161 let ctx =
1162 self.execution_ctx_for(input).map_err(|e| InsertBlockErrorKind::Other(Box::new(e)))?;
1163 let (output, senders, built_bal) = crate::tree::payload_processor::bal::execute_block(
1164 &self.runtime,
1165 &self.evm_config,
1166 &make_db,
1167 input_bal,
1168 env.evm_env,
1169 ctx,
1170 env.transaction_count,
1171 handle.clone_transaction_receiver(),
1172 receipt_tx,
1173 )?;
1174 let execution_duration = execution_start.elapsed();
1175
1176 self.metrics.record_block_execution(&output, execution_duration);
1177 self.metrics.record_block_execution_gas_bucket(output.result.gas_used, execution_duration);
1178 debug!(
1179 target: "engine::tree::payload_validator",
1180 elapsed = ?execution_duration,
1181 "Executed block via BAL path",
1182 );
1183
1184 Ok((output, senders, result_rx, Some(built_bal)))
1185 }
1186
1187 fn spawn_receipt_root_task(
1188 &self,
1189 receipts_len: usize,
1190 ) -> (ReceiptRootSender<N>, ReceiptRootReceiver) {
1191 let (receipt_tx, receipt_rx) = crossbeam_channel::unbounded();
1193 let (result_tx, result_rx) = tokio::sync::oneshot::channel();
1194 let task_handle = ReceiptRootTaskHandle::new(receipt_rx, result_tx);
1195 self.runtime.spawn_blocking_named("receipt-root", move || task_handle.run(receipts_len));
1196
1197 (receipt_tx, result_rx)
1198 }
1199
1200 fn execute_transactions<'a, E, Tx, InnerTx, Err, DB>(
1210 &self,
1211 mut executor: E,
1212 transaction_count: usize,
1213 transactions: impl Iterator<Item = Result<Tx, Err>>,
1214 receipt_tx: &crossbeam_channel::Sender<IndexedReceipt<N::Receipt>>,
1215 executed_tx_index: &AtomicUsize,
1216 has_bal: bool,
1217 ) -> Result<(E, Vec<Address>), BlockExecutionError>
1218 where
1219 E: BlockExecutor<Receipt = N::Receipt, Evm: alloy_evm::Evm<DB = &'a mut State<DB>>>,
1220 Tx: alloy_evm::block::ExecutableTx<E> + alloy_evm::RecoveredTx<InnerTx>,
1221 InnerTx: TxHashRef,
1222 DB: revm::Database + 'a,
1223 Err: core::error::Error + Send + Sync + 'static,
1224 {
1225 let mut senders = Vec::with_capacity(transaction_count);
1226
1227 let pre_exec_start = Instant::now();
1229 debug_span!(target: "engine::tree", "pre_execution")
1230 .in_scope(|| executor.apply_pre_execution_changes())?;
1231 self.metrics.record_pre_execution(pre_exec_start.elapsed());
1232
1233 if has_bal {
1235 executor.evm_mut().db_mut().bump_bal_index();
1236 }
1237
1238 let exec_span = debug_span!(target: "engine::tree", "execution").entered();
1240 let mut transactions = transactions.into_iter();
1241 let mut last_sent_len = 0usize;
1246 loop {
1247 let wait_start = Instant::now();
1250 let Some(tx_result) = transactions.next() else { break };
1251 self.metrics.record_transaction_wait(wait_start.elapsed());
1252
1253 let tx = tx_result.map_err(BlockExecutionError::other)?;
1254 let tx_signer = *<Tx as alloy_evm::RecoveredTx<InnerTx>>::signer(&tx);
1255
1256 senders.push(tx_signer);
1257
1258 let _enter = tracing::enabled!(target: "engine::tree", Level::TRACE).then(|| {
1259 tracing::trace_span!(
1260 target: "engine::tree",
1261 "execute tx",
1262 tx_index = senders.len() - 1,
1263 )
1264 .entered()
1265 });
1266 if tracing::enabled!(target: "engine::tree", Level::TRACE) {
1267 trace!(target: "engine::tree", "Executing transaction");
1268 }
1269
1270 let tx_start = Instant::now();
1271 executor.execute_transaction(tx)?;
1272 self.metrics.record_transaction_execution(tx_start.elapsed());
1273
1274 executed_tx_index.store(senders.len(), Ordering::Relaxed);
1276
1277 let current_len = executor.receipts().len();
1278 if current_len > last_sent_len {
1279 last_sent_len = current_len;
1280 if let Some(receipt) = executor.receipts().last() {
1282 let tx_index = current_len - 1;
1283 let _ = receipt_tx.send(IndexedReceipt::new(tx_index, receipt.clone()));
1284 }
1285 }
1286 if has_bal {
1288 executor.evm_mut().db_mut().bump_bal_index();
1289 }
1290 }
1291
1292 drop(exec_span);
1293
1294 Ok((executor, senders))
1295 }
1296
1297 #[instrument(level = "debug", target = "engine::tree::payload_validator", skip_all)]
1309 fn validate_post_execution<T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
1310 &mut self,
1311 block: &RecoveredBlock<N::Block>,
1312 parent_block: &SealedHeader<N::BlockHeader>,
1313 output: &BlockExecutionOutput<N::Receipt>,
1314 ctx: &mut TreeCtx<'_, N>,
1315 receipt_root_bloom: Option<ReceiptRootBloom>,
1316 built_bal: Option<BlockAccessList>,
1317 ) -> Result<(), InsertBlockErrorKind>
1318 where
1319 V: PayloadValidator<T, Block = N::Block>,
1320 {
1321 let start = Instant::now();
1322
1323 trace!(target: "engine::tree::payload_validator", block=?block.num_hash(), "Validating block consensus");
1324
1325 let _enter =
1327 debug_span!(target: "engine::tree::payload_validator", "validate_block_post_execution")
1328 .entered();
1329 let block_access_list_hash = built_bal
1330 .as_ref()
1331 .map(|bal| compute_block_access_list_hash_with_buf(bal, &mut self.bal_hash_buf));
1332
1333 if let Err(err) = self.consensus.validate_block_post_execution(
1334 block,
1335 output,
1336 receipt_root_bloom,
1337 block_access_list_hash,
1338 ) {
1339 self.on_invalid_block(parent_block, block, output, None, ctx.state_mut());
1341 return Err(err.into())
1342 }
1343 drop(_enter);
1344
1345 self.metrics
1347 .block_validation
1348 .post_execution_validation_duration
1349 .record(start.elapsed().as_secs_f64());
1350
1351 Ok(())
1352 }
1353
1354 #[instrument(
1359 level = "debug",
1360 target = "engine::tree::payload_validator",
1361 skip_all,
1362 fields(
1363 has_hint_stream = hint_stream.is_some(),
1364 has_hashed_update_stream = hashed_update_stream.is_some(),
1365 parallel_bal_execution
1366 )
1367 )]
1368 fn spawn_payload_processor<T: ExecutableTxIterator<Evm>>(
1369 &self,
1370 env: ExecutionEnv<Evm>,
1371 txs: T,
1372 provider_builder: StateProviderBuilder<N, P>,
1373 hint_stream: Option<StateRootHintStream>,
1374 hashed_update_stream: Option<StateRootUpdateStream>,
1375 parallel_bal_execution: bool,
1376 ) -> Result<
1377 PayloadHandle<
1378 impl ExecutableTxFor<Evm> + use<N, P, Evm, V, T>,
1379 impl core::error::Error + Send + Sync + 'static + use<N, P, Evm, V, T>,
1380 N::Receipt,
1381 >,
1382 InsertBlockErrorKind,
1383 > {
1384 let start = Instant::now();
1385 let handle = self.payload_processor.spawn_with_state_root_streams(
1386 env,
1387 txs,
1388 provider_builder,
1389 hint_stream,
1390 hashed_update_stream,
1391 parallel_bal_execution,
1392 );
1393
1394 self.metrics.block_validation.spawn_payload_processor.record(start.elapsed().as_secs_f64());
1395
1396 Ok(handle)
1397 }
1398
1399 fn state_provider_builder(
1403 &self,
1404 hash: B256,
1405 state: &EngineApiTreeState<N>,
1406 ) -> ProviderResult<Option<StateProviderBuilder<N, P>>> {
1407 if !state.tree_state.contains_hash(&hash) && self.provider.header(hash)?.is_none() {
1408 debug!(target: "engine::tree::payload_validator", %hash, "no canonical state found for block");
1409 return Ok(None)
1410 }
1411
1412 Ok(Some(StateProviderBuilder::new(
1413 self.provider.clone(),
1414 hash,
1415 state.tree_state.overlay_manager.clone(),
1416 )))
1417 }
1418
1419 fn on_invalid_block(
1421 &self,
1422 parent_header: &SealedHeader<N::BlockHeader>,
1423 block: &RecoveredBlock<N::Block>,
1424 output: &BlockExecutionOutput<N::Receipt>,
1425 trie_updates: Option<(&TrieUpdates, B256)>,
1426 state: &mut EngineApiTreeState<N>,
1427 ) {
1428 if state.invalid_headers.get(&block.hash()).is_some() {
1429 return
1431 }
1432 self.invalid_block_hook.on_invalid_block(parent_header, block, output, trie_updates);
1433 }
1434
1435 fn payload_state_root_handle_for(
1438 &self,
1439 parent_hash: B256,
1440 parent_header: &N::BlockHeader,
1441 timestamp: u64,
1442 state: &mut EngineApiTreeState<N>,
1443 ) -> Option<PayloadStateRootHandle> {
1444 let provider_builder = match self.state_provider_builder(parent_hash, state) {
1445 Ok(Some(provider_builder)) => provider_builder,
1446 Ok(None) => return None,
1447 Err(err) => {
1448 warn!(
1449 target: "engine::tree::payload_validator",
1450 %err,
1451 %parent_hash,
1452 "failed to prepare payload-builder state-root provider"
1453 );
1454 return None
1455 }
1456 };
1457 let overlay_factory = OverlayStateProviderFactory::new(
1458 self.provider.clone(),
1459 state.tree_state.overlay_manager.overlay_builder(parent_hash),
1460 );
1461
1462 match self.state_root_strategy.prepare_payload_builder(PayloadStateRootJobContext::new(
1463 &self.runtime,
1464 &self.overlay_manager,
1465 parent_hash,
1466 parent_header,
1467 timestamp,
1468 state,
1469 provider_builder,
1470 overlay_factory,
1471 &self.config,
1472 )) {
1473 Ok(handle) => handle,
1474 Err(err) => {
1475 warn!(
1476 target: "engine::tree::payload_validator",
1477 %err,
1478 %parent_hash,
1479 "failed to prepare payload-builder state-root job"
1480 );
1481 None
1482 }
1483 }
1484 }
1485
1486 fn spawn_deferred_trie_task(
1499 &self,
1500 block: Arc<RecoveredBlock<N::Block>>,
1501 execution_outcome: Arc<BlockExecutionOutput<N::Receipt>>,
1502 hashed_state: LazyHashedPostState,
1503 trie_output: Arc<TrieUpdates>,
1504 ) -> ExecutedBlock<N> {
1505 let hashed_state = match hashed_state.try_into_inner() {
1509 Ok(state) => state,
1510 Err(handle) => handle.get().clone(),
1511 };
1512 let (deferred_trie_data, deferred_trie_task) =
1513 LazyTrieData::pending(hashed_state, trie_output);
1514 let block_validation_metrics = self.metrics.block_validation.clone();
1515
1516 let block_number = block.number();
1518
1519 let compute_trie_input_task = move || {
1521 let _span = debug_span!(
1522 target: "engine::tree::payload_validator",
1523 "compute_trie_input_task",
1524 block_number
1525 )
1526 .entered();
1527
1528 let compute_start = Instant::now();
1529 let computed = deferred_trie_task.compute_and_publish();
1530 block_validation_metrics
1531 .deferred_trie_compute_duration
1532 .record(compute_start.elapsed().as_secs_f64());
1533
1534 block_validation_metrics
1536 .hashed_post_state_size
1537 .record(computed.sorted.hashed_state.total_len() as f64);
1538 block_validation_metrics
1539 .trie_updates_sorted_size
1540 .record(computed.sorted.trie_updates.total_len() as f64);
1541 };
1542
1543 self.runtime.spawn_blocking_named(DEFERRED_TRIE_WORKER_NAME, compute_trie_input_task);
1545
1546 ExecutedBlock::with_deferred_trie_data(block, execution_outcome, deferred_trie_data)
1547 }
1548
1549 fn calculate_timing_stats(
1550 &self,
1551 block: &RecoveredBlock<N::Block>,
1552 provider_stats: Arc<StateProviderStats>,
1553 cache_stats: Option<Arc<CacheStats>>,
1554 output: &BlockExecutionOutput<N::Receipt>,
1555 execution_duration: Duration,
1556 state_hash_duration: Duration,
1557 ) -> Box<ExecutionTimingStats> {
1558 let accounts_read = provider_stats.total_account_fetches();
1559 let storage_read = provider_stats.total_storage_fetches();
1560 let code_read = provider_stats.total_code_fetches();
1561 let code_bytes_read = provider_stats.total_code_fetched_bytes();
1562
1563 let accounts_changed = output.state.state.len();
1565 let accounts_deleted =
1566 output.state.state.values().filter(|acc| acc.was_destroyed()).count();
1567 let storage_slots_changed =
1568 output.state.state.values().map(|account| account.storage.len()).sum::<usize>();
1569 let storage_slots_deleted = output
1570 .state
1571 .state
1572 .values()
1573 .flat_map(|account| account.storage.values())
1574 .filter(|slot| {
1575 slot.present_value.is_zero() && !slot.previous_or_original_value.is_zero()
1576 })
1577 .count();
1578
1579 let is_new_deployment = |acc: &BundleAccount| -> bool {
1581 let has_code_now = acc.info.as_ref().is_some_and(|info| info.code_hash != KECCAK_EMPTY);
1582 let had_no_code_before = acc
1583 .original_info
1584 .as_ref()
1585 .map(|info| info.code_hash == KECCAK_EMPTY)
1586 .unwrap_or(true);
1587 has_code_now && had_no_code_before
1588 };
1589
1590 let bytecodes_changed =
1591 output.state.state.values().filter(|acc| is_new_deployment(acc)).count();
1592
1593 let unique_new_code_hashes: B256Set = output
1595 .state
1596 .state
1597 .values()
1598 .filter(|acc| is_new_deployment(acc))
1599 .filter_map(|acc| acc.info.as_ref().map(|info| info.code_hash))
1600 .collect();
1601 let code_bytes_written: usize = unique_new_code_hashes
1602 .iter()
1603 .filter_map(|hash| {
1604 output.state.contracts.get(hash).map(|bytecode| bytecode.original_bytes().len())
1605 })
1606 .sum();
1607
1608 let state_read_duration = provider_stats.total_account_fetch_latency() +
1610 provider_stats.total_storage_fetch_latency() +
1611 provider_stats.total_code_fetch_latency();
1612
1613 let eip7702_delegations_set =
1616 output.state.contracts.values().filter(|bytecode| bytecode.is_eip7702()).count();
1617 let eip7702_delegations_cleared = output
1622 .state
1623 .state
1624 .values()
1625 .filter(|acc| {
1626 let original_was_eip7702 = acc
1628 .original_info
1629 .as_ref()
1630 .and_then(|info| info.code.as_ref())
1631 .map(|bytecode| bytecode.is_eip7702())
1632 .unwrap_or(false);
1633
1634 let code_now_empty =
1636 acc.info.as_ref().map(|info| info.code_hash == KECCAK_EMPTY).unwrap_or(false);
1637
1638 original_was_eip7702 && code_now_empty
1639 })
1640 .count();
1641
1642 let (account_cache_hits, account_cache_misses) = cache_stats
1644 .as_ref()
1645 .map(|s| (s.account_hits(), s.account_misses()))
1646 .unwrap_or_default();
1647 let (storage_cache_hits, storage_cache_misses) = cache_stats
1648 .as_ref()
1649 .map(|s| (s.storage_hits(), s.storage_misses()))
1650 .unwrap_or_default();
1651 let (code_cache_hits, code_cache_misses) =
1652 cache_stats.as_ref().map(|s| (s.code_hits(), s.code_misses())).unwrap_or_default();
1653 let (txpool_snapshot_account_hits, txpool_snapshot_account_misses) = cache_stats
1654 .as_ref()
1655 .map(|s| (s.txpool_snapshot_account_hits(), s.txpool_snapshot_account_misses()))
1656 .unwrap_or_default();
1657 let (txpool_snapshot_storage_hits, txpool_snapshot_storage_misses) = cache_stats
1658 .as_ref()
1659 .map(|s| (s.txpool_snapshot_storage_hits(), s.txpool_snapshot_storage_misses()))
1660 .unwrap_or_default();
1661 let (txpool_snapshot_code_hits, txpool_snapshot_code_misses) = cache_stats
1662 .as_ref()
1663 .map(|s| (s.txpool_snapshot_code_hits(), s.txpool_snapshot_code_misses()))
1664 .unwrap_or_default();
1665
1666 Box::new(ExecutionTimingStats {
1668 block_number: block.number(),
1669 block_hash: block.hash(),
1670 gas_used: output.result.gas_used,
1671 tx_count: block.transaction_count(),
1672 execution_duration,
1673 state_read_duration,
1674 state_hash_duration,
1675 accounts_read,
1676 storage_read,
1677 code_read,
1678 code_bytes_read,
1679 accounts_changed,
1680 accounts_deleted,
1681 storage_slots_changed,
1682 storage_slots_deleted,
1683 bytecodes_changed,
1684 code_bytes_written,
1685 eip7702_delegations_set,
1686 eip7702_delegations_cleared,
1687 account_cache_hits,
1688 account_cache_misses,
1689 storage_cache_hits,
1690 storage_cache_misses,
1691 code_cache_hits,
1692 code_cache_misses,
1693 txpool_snapshot_account_hits,
1694 txpool_snapshot_account_misses,
1695 txpool_snapshot_storage_hits,
1696 txpool_snapshot_storage_misses,
1697 txpool_snapshot_code_hits,
1698 txpool_snapshot_code_misses,
1699 })
1700 }
1701}
1702
1703pub trait EngineValidator<
1707 Types: PayloadTypes,
1708 N: NodePrimitives = <<Types as PayloadTypes>::BuiltPayload as BuiltPayload>::Primitives,
1709>: Send + Sync + 'static
1710{
1711 fn validate_payload_attributes_against_header(
1721 &self,
1722 attr: &Types::PayloadAttributes,
1723 header: &N::BlockHeader,
1724 ) -> Result<(), InvalidPayloadAttributesError>;
1725
1726 fn convert_payload_to_block(
1735 &self,
1736 payload: Types::ExecutionData,
1737 ) -> Result<SealedBlock<N::Block>, NewPayloadError>;
1738
1739 fn validate_payload(
1741 &mut self,
1742 payload: Types::ExecutionData,
1743 ctx: TreeCtx<'_, N>,
1744 ) -> ValidationOutcome<N>;
1745
1746 fn validate_block(
1748 &mut self,
1749 block: SealedBlock<N::Block>,
1750 ctx: TreeCtx<'_, N>,
1751 ) -> ValidationOutcome<N>;
1752
1753 fn on_inserted_executed_block(
1758 &self,
1759 block: BuiltPayloadExecutedBlock<N>,
1760 ) -> ProviderResult<ExecutedBlock<N>>;
1761
1762 fn on_canonical_head_changed(&self, _hash: B256, _state: &EngineApiTreeState<N>) {}
1766
1767 fn payload_builder_resources(
1771 &self,
1772 parent_hash: B256,
1773 parent_header: &N::BlockHeader,
1774 timestamp: u64,
1775 state: &mut EngineApiTreeState<N>,
1776 ) -> PayloadBuilderResources;
1777}
1778
1779impl<N, Types, P, Evm, V> EngineValidator<Types> for BasicEngineValidator<P, Evm, V>
1780where
1781 P: DatabaseProviderFactory<
1782 Provider: BlockReader
1783 + StageCheckpointReader
1784 + PruneCheckpointReader
1785 + ChangeSetReader
1786 + StorageChangeSetReader
1787 + StorageSettingsCache
1788 + TryIntoHistoricalStateProvider
1789 + 'static,
1790 > + BlockReader<Header = N::BlockHeader>
1791 + StateProviderFactory
1792 + StateReader
1793 + ChangeSetReader
1794 + Clone
1795 + 'static,
1796 OverlayStateProviderFactory<P, N>: DatabaseProviderROFactory<Provider: TrieCursorFactory + HashedCursorFactory>
1797 + Clone
1798 + 'static,
1799 N: NodePrimitives,
1800 V: PayloadValidator<Types, Block = N::Block> + Clone,
1801 Evm: ConfigureEngineEvm<Types::ExecutionData, Primitives = N> + 'static,
1802 Types: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>,
1803{
1804 fn validate_payload_attributes_against_header(
1805 &self,
1806 attr: &Types::PayloadAttributes,
1807 header: &N::BlockHeader,
1808 ) -> Result<(), InvalidPayloadAttributesError> {
1809 self.validator.validate_payload_attributes_against_header(attr, header)
1810 }
1811
1812 fn convert_payload_to_block(
1813 &self,
1814 payload: Types::ExecutionData,
1815 ) -> Result<SealedBlock<N::Block>, NewPayloadError> {
1816 let block = self.validator.convert_payload_to_block(payload)?;
1817 Ok(block)
1818 }
1819
1820 fn validate_payload(
1821 &mut self,
1822 payload: Types::ExecutionData,
1823 ctx: TreeCtx<'_, N>,
1824 ) -> ValidationOutcome<N> {
1825 self.validate_block_with_state(BlockOrPayload::Payload(payload), ctx)
1826 }
1827
1828 fn validate_block(
1829 &mut self,
1830 block: SealedBlock<N::Block>,
1831 ctx: TreeCtx<'_, N>,
1832 ) -> ValidationOutcome<N> {
1833 self.validate_block_with_state(BlockOrPayload::Block(block), ctx)
1834 }
1835
1836 fn on_inserted_executed_block(
1837 &self,
1838 block: BuiltPayloadExecutedBlock<N>,
1839 ) -> ProviderResult<ExecutedBlock<N>> {
1840 self.payload_processor.on_inserted_executed_block(
1841 block.recovered_block.block_with_parent(),
1842 &block.execution_output.state,
1843 );
1844
1845 Ok(self.spawn_deferred_trie_task(
1846 block.recovered_block,
1847 block.execution_output,
1848 LazyHashedPostState::ready(block.hashed_state),
1849 block.trie_updates,
1850 ))
1851 }
1852
1853 fn on_canonical_head_changed(&self, hash: B256, state: &EngineApiTreeState<N>) {
1854 let Some(txpool_prewarm) = self.txpool_prewarm.as_ref() else { return };
1855
1856 let parent = match self.sealed_header_by_hash(hash, state) {
1859 Ok(Some(header)) => header,
1860 Ok(None) => return,
1861 Err(err) => {
1862 trace!(
1863 target: "engine::tree::txpool_prewarm",
1864 %err,
1865 block_hash = ?hash,
1866 "failed to fetch canonical header for txpool prewarming"
1867 );
1868 return
1869 }
1870 };
1871 let evm_env = match self.evm_config.evm_env(parent.header()) {
1875 Ok(evm_env) => evm_env,
1876 Err(err) => {
1877 trace!(
1878 target: "engine::tree::txpool_prewarm",
1879 %err,
1880 block_hash = ?parent.hash(),
1881 "failed to derive canonical txpool prewarming environment"
1882 );
1883 return
1884 }
1885 };
1886
1887 let provider_builder = match self.state_provider_builder(parent.hash(), state) {
1888 Ok(Some(provider_builder)) => provider_builder,
1889 Ok(None) => return,
1890 Err(err) => {
1891 trace!(
1892 target: "engine::tree::txpool_prewarm",
1893 %err,
1894 block_hash = ?parent.hash(),
1895 "failed to derive canonical txpool prewarming provider"
1896 );
1897 return
1898 }
1899 };
1900 txpool_prewarm.start(parent.hash(), evm_env, provider_builder)
1901 }
1902
1903 fn payload_builder_resources(
1904 &self,
1905 parent_hash: B256,
1906 parent_header: &N::BlockHeader,
1907 timestamp: u64,
1908 state: &mut EngineApiTreeState<N>,
1909 ) -> PayloadBuilderResources {
1910 let execution_cache = self
1911 .config
1912 .share_execution_cache_with_payload_builder()
1913 .then(|| self.payload_processor.cache_for(parent_hash));
1914 let state_root_handle =
1915 self.payload_state_root_handle_for(parent_hash, parent_header, timestamp, state);
1916 let mut resources = PayloadBuilderResources::new(execution_cache, state_root_handle)
1917 .with_lease(PayloadBuilderLease::new(JitPauseGuard::new(&self.evm_config)));
1918 if let Some(txpool_prewarm) = self.txpool_prewarm.as_ref() {
1922 let txpool_lease = PayloadBuilderLease::new(txpool_prewarm.pause());
1923 resources = resources.with_lease(txpool_lease);
1924 }
1925 resources
1926 }
1927}
1928
1929impl<P, Evm, V> WaitForCaches for BasicEngineValidator<P, Evm, V>
1930where
1931 Evm: ConfigureEvm,
1932{
1933 fn wait_for_caches(&self) -> CacheWaitDurations {
1934 debug!(target: "engine::tree::payload_validator", "Waiting for execution cache and sparse trie locks");
1935
1936 let execution_cache = self.payload_processor.execution_cache();
1937 let overlay_manager = self.overlay_manager.clone();
1938 let (execution_tx, execution_rx) = std::sync::mpsc::channel();
1939 let (sparse_trie_tx, sparse_trie_rx) = std::sync::mpsc::channel();
1940
1941 self.runtime.spawn_blocking_named("wait-exec-cache", move || {
1942 let _ = execution_tx.send(execution_cache.wait_for_availability());
1943 });
1944 self.runtime.spawn_blocking_named("wait-sparse-tri", move || {
1945 let _ = sparse_trie_tx.send(overlay_manager.wait_for_sparse_trie_availability());
1946 });
1947
1948 let execution_cache =
1949 execution_rx.recv().expect("execution cache wait task failed to send result");
1950 let sparse_trie =
1951 sparse_trie_rx.recv().expect("sparse trie wait task failed to send result");
1952 debug!(
1953 target: "engine::tree::payload_validator",
1954 ?execution_cache,
1955 ?sparse_trie,
1956 "Execution cache and sparse trie locks acquired"
1957 );
1958 CacheWaitDurations { execution_cache, sparse_trie }
1959 }
1960}
1961
1962#[derive(Debug, Clone)]
1964pub enum BlockOrPayload<T: PayloadTypes> {
1965 Payload(T::ExecutionData),
1967 Block(SealedBlock<BlockTy<<T::BuiltPayload as BuiltPayload>::Primitives>>),
1969}
1970
1971impl<T: PayloadTypes> BlockOrPayload<T> {
1972 pub fn hash(&self) -> B256 {
1974 match self {
1975 Self::Payload(payload) => payload.block_hash(),
1976 Self::Block(block) => block.hash(),
1977 }
1978 }
1979
1980 pub fn num_hash(&self) -> NumHash {
1982 match self {
1983 Self::Payload(payload) => payload.num_hash(),
1984 Self::Block(block) => block.num_hash(),
1985 }
1986 }
1987
1988 pub fn parent_hash(&self) -> B256 {
1990 match self {
1991 Self::Payload(payload) => payload.parent_hash(),
1992 Self::Block(block) => block.parent_hash(),
1993 }
1994 }
1995
1996 pub fn block_with_parent(&self) -> BlockWithParent {
1998 match self {
1999 Self::Payload(payload) => payload.block_with_parent(),
2000 Self::Block(block) => block.block_with_parent(),
2001 }
2002 }
2003
2004 pub const fn type_name(&self) -> &'static str {
2006 match self {
2007 Self::Payload(_) => "payload",
2008 Self::Block(_) => "block",
2009 }
2010 }
2011
2012 pub const fn is_payload(&self) -> bool {
2014 matches!(self, Self::Payload(_))
2015 }
2016
2017 pub const fn is_block(&self) -> bool {
2019 matches!(self, Self::Block(_))
2020 }
2021
2022 pub fn try_decoded_access_list(&self) -> Result<Option<DecodedBal>, alloy_rlp::Error> {
2024 match self {
2025 Self::Payload(payload) => payload
2026 .block_access_list()
2027 .map(|block_access_list| DecodedBal::from_rlp_bytes(block_access_list.clone()))
2028 .transpose(),
2029 Self::Block(_) => Ok(None),
2030 }
2031 }
2032
2033 pub fn transaction_count(&self) -> usize
2035 where
2036 T::ExecutionData: ExecutionPayload,
2037 {
2038 match self {
2039 Self::Payload(payload) => payload.transaction_count(),
2040 Self::Block(block) => block.transaction_count(),
2041 }
2042 }
2043
2044 pub fn withdrawals(&self) -> Option<&[Withdrawal]>
2046 where
2047 T::ExecutionData: ExecutionPayload,
2048 {
2049 match self {
2050 Self::Payload(payload) => payload.withdrawals().map(|w| w.as_slice()),
2051 Self::Block(block) => block.body().withdrawals().map(|w| w.as_slice()),
2052 }
2053 }
2054
2055 pub fn gas_used(&self) -> u64
2057 where
2058 T::ExecutionData: ExecutionPayload,
2059 {
2060 match self {
2061 Self::Payload(payload) => payload.gas_used(),
2062 Self::Block(block) => block.gas_used(),
2063 }
2064 }
2065
2066 pub fn gas_limit(&self) -> u64
2068 where
2069 T::ExecutionData: ExecutionPayload,
2070 {
2071 match self {
2072 Self::Payload(payload) => payload.gas_limit(),
2073 Self::Block(block) => block.gas_limit(),
2074 }
2075 }
2076}