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, 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 providers::OverlayStateProviderFactory, BlockExecutionOutput, BlockNumReader, BlockReader,
150 ChangeSetReader, DatabaseProviderFactory, DatabaseProviderROFactory, HashedPostStateProvider,
151 ProviderError, PruneCheckpointReader, StageCheckpointReader, StateProvider, StateProviderBox,
152 StateProviderFactory, StateReader, StorageChangeSetReader, StorageSettingsCache,
153};
154use reth_revm::db::{states::bundle_state::BundleRetention, BundleAccount, State};
155use reth_storage_overlay::OverlayManager;
156use reth_trie::{
157 hashed_cursor::HashedCursorFactory, trie_cursor::TrieCursorFactory, updates::TrieUpdates,
158 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_USAGE_MULTIPLIER: u64 = 2;
175
176const DEFERRED_TRIE_WORKER_NAME: &str = "deferred-trie";
178
179type ReceiptRootSender<N> =
180 crossbeam_channel::Sender<IndexedReceipt<<N as NodePrimitives>::Receipt>>;
181type ReceiptRootReceiver = tokio::sync::oneshot::Receiver<(B256, alloy_primitives::Bloom)>;
182
183pub struct TreeCtx<'a, N: NodePrimitives> {
188 state: &'a mut EngineApiTreeState<N>,
190 canonical_in_memory_state: &'a CanonicalInMemoryState<N>,
192}
193
194impl<'a, N: NodePrimitives> std::fmt::Debug for TreeCtx<'a, N> {
195 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
196 f.debug_struct("TreeCtx")
197 .field("state", &"EngineApiTreeState")
198 .field("canonical_in_memory_state", &self.canonical_in_memory_state)
199 .finish()
200 }
201}
202
203impl<'a, N: NodePrimitives> TreeCtx<'a, N> {
204 pub const fn new(
206 state: &'a mut EngineApiTreeState<N>,
207 canonical_in_memory_state: &'a CanonicalInMemoryState<N>,
208 ) -> Self {
209 Self { state, canonical_in_memory_state }
210 }
211}
212
213impl<'a, N: NodePrimitives> TreeCtx<'a, N> {
214 pub const fn state(&self) -> &EngineApiTreeState<N> {
216 &*self.state
217 }
218
219 pub const fn state_mut(&mut self) -> &mut EngineApiTreeState<N> {
221 self.state
222 }
223
224 pub const fn canonical_in_memory_state(&self) -> &'a CanonicalInMemoryState<N> {
226 self.canonical_in_memory_state
227 }
228}
229
230struct JitPauseGuard<Evm: ConfigureEvm>(Evm);
236
237impl<Evm: ConfigureEvm> JitPauseGuard<Evm> {
238 fn new(evm_config: &Evm) -> Self {
239 if let Some(jit_backend) = evm_config.jit_backend() {
240 jit_backend.pause();
241 }
242 Self(evm_config.clone())
243 }
244}
245
246impl<Evm: ConfigureEvm> Drop for JitPauseGuard<Evm> {
247 fn drop(&mut self) {
248 if let Some(jit_backend) = self.0.jit_backend() {
249 jit_backend.resume();
250 }
251 }
252}
253
254#[derive(derive_more::Debug)]
262pub struct BasicEngineValidator<P, Evm, V>
263where
264 Evm: ConfigureEvm,
265{
266 provider: P,
268 consensus: Arc<dyn FullConsensus<Evm::Primitives>>,
270 evm_config: Evm,
272 config: TreeConfig,
274 payload_processor: PayloadProcessor<Evm>,
276 precompile_cache_map: PrecompileCacheMap<SpecFor<Evm>>,
278 precompile_cache_metrics: AddressMap<CachedPrecompileMetrics>,
280 #[debug(skip)]
282 invalid_block_hook: Box<dyn InvalidBlockHook<Evm::Primitives>>,
283 metrics: EngineApiMetrics,
285 validator: V,
287 runtime: reth_tasks::Runtime,
289 overlay_manager: OverlayManager<Evm::Primitives>,
291 #[debug(skip)]
293 state_root_strategy: Arc<dyn StateRootStrategy<Evm::Primitives, P, Evm>>,
294 #[debug(skip)]
298 txpool_prewarm: Option<txpool_prewarm::Handle<Evm::Primitives, P, Evm>>,
299}
300
301impl<N, P, Evm, V> BasicEngineValidator<P, Evm, V>
302where
303 N: NodePrimitives,
304 P: DatabaseProviderFactory<
305 Provider: BlockReader
306 + StageCheckpointReader
307 + PruneCheckpointReader
308 + ChangeSetReader
309 + StorageChangeSetReader
310 + BlockNumReader
311 + StorageSettingsCache,
312 > + BlockReader<Header = N::BlockHeader>
313 + ChangeSetReader
314 + BlockNumReader
315 + StateProviderFactory
316 + StateReader
317 + HashedPostStateProvider
318 + Clone
319 + Send
320 + Sync
321 + 'static,
322 OverlayStateProviderFactory<P, N>: DatabaseProviderROFactory<Provider: TrieCursorFactory + HashedCursorFactory>
323 + Clone
324 + Send
325 + Sync
326 + 'static,
327 Evm: ConfigureEvm<Primitives = N> + 'static,
328{
329 #[expect(clippy::too_many_arguments)]
331 pub fn new(
332 provider: P,
333 consensus: Arc<dyn FullConsensus<N>>,
334 evm_config: Evm,
335 validator: V,
336 config: TreeConfig,
337 invalid_block_hook: Box<dyn InvalidBlockHook<N>>,
338 overlay_manager: OverlayManager<N>,
339 runtime: reth_tasks::Runtime,
340 ) -> Self {
341 let precompile_cache_map = PrecompileCacheMap::default();
342 let payload_processor = PayloadProcessor::new(
343 runtime.clone(),
344 evm_config.clone(),
345 &config,
346 precompile_cache_map.clone(),
347 );
348 Self {
349 provider,
350 consensus,
351 evm_config,
352 payload_processor,
353 precompile_cache_map,
354 precompile_cache_metrics: AddressMap::default(),
355 config,
356 invalid_block_hook,
357 metrics: EngineApiMetrics::default(),
358 validator,
359 runtime,
360 overlay_manager,
361 state_root_strategy: Arc::new(DefaultStateRootStrategy::default()),
362 txpool_prewarm: None,
363 }
364 }
365
366 pub fn with_state_root_strategy(
368 mut self,
369 state_root_strategy: Arc<dyn StateRootStrategy<N, P, Evm>>,
370 ) -> Self {
371 self.state_root_strategy = state_root_strategy;
372 self
373 }
374
375 pub fn with_txpool_prewarming(
377 mut self,
378 source: impl crate::tree::TxPoolPrewarmSource<N> + 'static,
379 ) -> Self {
380 self.txpool_prewarm = Some(txpool_prewarm::Handle::spawn(
381 &self.runtime,
382 Arc::new(source),
383 self.evm_config.clone(),
384 ));
385 self
386 }
387
388 #[instrument(level = "debug", target = "engine::tree::payload_validator", skip_all)]
390 pub fn convert_to_block<T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
391 &self,
392 input: BlockOrPayload<T>,
393 ) -> Result<SealedBlock<N::Block>, NewPayloadError>
394 where
395 V: PayloadValidator<T, Block = N::Block>,
396 {
397 match input {
398 BlockOrPayload::Payload(payload) => self.validator.convert_payload_to_block(payload),
399 BlockOrPayload::Block(block) => Ok(block),
400 }
401 }
402
403 pub fn evm_env_for<T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
405 &self,
406 input: &BlockOrPayload<T>,
407 ) -> Result<EvmEnvFor<Evm>, Evm::Error>
408 where
409 V: PayloadValidator<T, Block = N::Block>,
410 Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
411 {
412 match input {
413 BlockOrPayload::Payload(payload) => Ok(self.evm_config.evm_env_for_payload(payload)?),
414 BlockOrPayload::Block(block) => Ok(self.evm_config.evm_env(block.header())?),
415 }
416 }
417
418 pub fn tx_iterator_for<'a, T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
420 &'a self,
421 input: &'a BlockOrPayload<T>,
422 ) -> Result<impl ExecutableTxIterator<Evm>, NewPayloadError>
423 where
424 V: PayloadValidator<T, Block = N::Block>,
425 Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
426 {
427 Ok(match input {
428 BlockOrPayload::Payload(payload) => {
429 let iter = self
430 .evm_config
431 .tx_iterator_for_payload(payload)
432 .map_err(NewPayloadError::other)?;
433 Either::Left(iter)
434 }
435 BlockOrPayload::Block(block) => {
436 let txs = block.body().clone_transactions();
437 let convert = |tx: N::SignedTx| tx.try_into_recovered();
438 Either::Right((txs, convert))
439 }
440 })
441 }
442
443 pub fn execution_ctx_for<'a, T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
445 &self,
446 input: &'a BlockOrPayload<T>,
447 ) -> Result<ExecutionCtxFor<'a, Evm>, Evm::Error>
448 where
449 V: PayloadValidator<T, Block = N::Block>,
450 Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
451 {
452 match input {
453 BlockOrPayload::Payload(payload) => Ok(self.evm_config.context_for_payload(payload)?),
454 BlockOrPayload::Block(block) => Ok(self.evm_config.context_for_block(block)?),
455 }
456 }
457
458 #[instrument(
466 level = "debug",
467 target = "engine::tree::payload_validator",
468 skip_all,
469 fields(
470 parent = ?input.parent_hash(),
471 type_name = ?input.type_name(),
472 )
473 )]
474 pub fn validate_block_with_state<T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
475 &mut self,
476 input: BlockOrPayload<T>,
477 mut ctx: TreeCtx<'_, N>,
478 ) -> InsertPayloadResult<N>
479 where
480 V: PayloadValidator<T, Block = N::Block> + Clone,
481 Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
482 {
483 let parent_hash = input.parent_hash();
484 let _txpool_pause = self.txpool_prewarm.as_ref().map(txpool_prewarm::Handle::pause);
485 let txpool_snapshot =
486 self.txpool_prewarm.as_ref().and_then(|prewarmer| prewarmer.snapshot(parent_hash));
487 let _jit_pause = JitPauseGuard::new(&self.evm_config);
488
489 let parent_block = match self.sealed_header_by_hash(parent_hash, ctx.state()) {
492 Ok(Some(parent_block)) => parent_block,
493 Ok(None) => {
494 return Err(InsertBlockError::new(
495 self.convert_to_block(input)?,
496 ProviderError::HeaderNotFound(parent_hash.into()).into(),
497 )
498 .into())
499 }
500 Err(e) => {
501 return Err(InsertBlockError::new(self.convert_to_block(input)?, e.into()).into())
502 }
503 };
504
505 let validated_block = self.spawn_convert_and_validate(&input, parent_block.clone());
509
510 macro_rules! ensure_ok {
513 ($expr:expr) => {
514 match $expr {
515 Ok(val) => val,
516 Err(e) => {
517 let block = validated_block.try_into_inner().expect("sole handle")?;
518 return Err(InsertBlockError::new(block, e.into()).into())
519 }
520 }
521 };
522 }
523
524 macro_rules! ensure_ok_post_block {
526 ($expr:expr, $block:expr) => {
527 match $expr {
528 Ok(val) => val,
529 Err(e) => {
530 return Err(
531 InsertBlockError::new($block.into_sealed_block(), e.into()).into()
532 )
533 }
534 }
535 };
536 }
537
538 if input.gas_used() > parent_block.gas_limit() * MAX_EXPECTED_GAS_USAGE_MULTIPLIER {
541 if validated_block.get().is_err() {
543 return Err(validated_block
544 .try_into_inner()
545 .expect("sole handle")
546 .expect_err("Err result checked"))
547 }
548 }
549
550 trace!(target: "engine::tree::payload_validator", "Fetching block state provider");
551 let _enter =
552 debug_span!(target: "engine::tree::payload_validator", "state_provider").entered();
553 let Some(provider_builder) =
554 ensure_ok!(self.state_provider_builder(parent_hash, ctx.state()))
555 else {
556 return Err(InsertBlockError::new(
558 validated_block.try_into_inner().expect("sole handle")?,
559 ProviderError::HeaderNotFound(parent_hash.into()).into(),
560 )
561 .into())
562 };
563 drop(_enter);
564
565 let evm_env = debug_span!(target: "engine::tree::payload_validator", "evm_env")
566 .in_scope(|| self.evm_env_for(&input))
567 .map_err(NewPayloadError::other)?;
568
569 let decoded_bal = ensure_ok!(input
571 .try_decoded_access_list()
572 .map_err(|err| ConsensusError::BlockAccessListInvalid(err.to_string())))
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 hashed_state_provider = self.provider.clone();
754 let mut hashed_state_rx = state_root_job.take_hashed_state_rx();
755 let mut hashed_state: LazyHashedPostState =
756 self.runtime.spawn_blocking_named("hash-post-state", move || {
757 let _span = debug_span!(
758 target: "engine::tree::payload_validator",
759 "hashed_post_state",
760 )
761 .entered();
762 if let Some(Ok(state)) = hashed_state_rx.as_mut().map(|rx| rx.recv()) {
763 state
764 } else {
765 Arc::new(hashed_state_provider.hashed_post_state(&hashed_state_output.state))
766 }
767 });
768
769 let block = validated_block.try_into_inner().expect("sole handle")?;
770 let block = block.with_senders(senders);
771
772 let receipt_root_bloom = {
774 let _enter = debug_span!(
775 target: "engine::tree::payload_validator",
776 "wait_receipt_root",
777 )
778 .entered();
779
780 receipt_root_rx
781 .blocking_recv()
782 .inspect_err(|_| {
783 tracing::error!(
784 target: "engine::tree::payload_validator",
785 "Receipt root task dropped sender without result, receipt root calculation likely aborted"
786 );
787 })
788 .ok()
789 };
790
791 ensure_ok_post_block!(
792 self.validate_post_execution(
793 &block,
794 &parent_block,
795 &output,
796 &mut ctx,
797 receipt_root_bloom,
798 built_bal
799 ),
800 block
801 );
802
803 let mut hashed_state_validate_result = debug_span!(
804 target: "engine::tree::payload_validator",
805 "validate_block_post_execution_with_hashed_state"
806 )
807 .in_scope(|| {
808 self.validator.validate_block_post_execution_with_hashed_state(
809 || hashed_state.get(),
810 &block,
811 &parent_block,
812 || provider_builder.build(),
813 )
814 });
815
816 let root_start = Instant::now();
817 let root_outcome = ensure_ok_post_block!(
818 state_root_job.finish(&block, output.clone(), &hashed_state),
819 block
820 );
821 let root_elapsed = root_start.elapsed();
822
823 info!(
824 target: "engine::tree::payload_validator",
825 strategy = state_root_job_name,
826 state_root = ?root_outcome.state_root,
827 elapsed = ?root_elapsed,
828 "State root job finished"
829 );
830
831 let state_root = root_outcome.state_root;
832 let trie_output = root_outcome.trie_updates;
833
834 if let Some(refreshed) = root_outcome.hashed_state {
838 hashed_state = LazyHandle::ready(refreshed);
839 hashed_state_validate_result = debug_span!(
840 target: "engine::tree::payload_validator",
841 "validate_block_post_execution_with_hashed_state"
842 )
843 .in_scope(|| {
844 self.validator.validate_block_post_execution_with_hashed_state(
845 || hashed_state.get(),
846 &block,
847 &parent_block,
848 || provider_builder.build(),
849 )
850 });
851 }
852
853 if let Err(err) = hashed_state_validate_result {
854 if err.is_validation_error() {
855 self.on_invalid_block(&parent_block, &block, &output, None, ctx.state_mut());
856 }
857 return Err(InsertBlockError::new(block.into_sealed_block(), err).into())
858 }
859
860 self.metrics.block_validation.record_state_root(&trie_output, root_elapsed.as_secs_f64());
861 self.metrics
862 .record_state_root_gas_bucket(block.header().gas_used(), root_elapsed.as_secs_f64());
863 debug!(target: "engine::tree::payload_validator", ?root_elapsed, "Calculated state root");
864
865 if state_root != block.header().state_root() {
867 self.on_invalid_block(
869 &parent_block,
870 &block,
871 &output,
872 Some((&trie_output, state_root)),
873 ctx.state_mut(),
874 );
875 let block_state_root = block.header().state_root();
876 return Err(InsertBlockError::new(
877 block.into_sealed_block(),
878 ConsensusError::BodyStateRootDiff(
879 GotExpected { got: state_root, expected: block_state_root }.into(),
880 )
881 .into(),
882 )
883 .into())
884 }
885
886 let timing_stats = state_provider_stats.filter(|_| slow_block_enabled).map(|stats| {
887 self.calculate_timing_stats(
888 &block,
889 stats,
890 cache_stats,
891 &output,
892 execution_duration,
893 root_elapsed,
894 )
895 });
896
897 if let Some(valid_block_tx) = valid_block_tx {
898 let _ = valid_block_tx.send(());
899 }
900
901 let executed_block =
902 self.spawn_deferred_trie_task(Arc::new(block), output, hashed_state, trie_output);
903 let raw_bal = decoded_bal.map(|decoded_bal| decoded_bal.as_raw_bal().clone());
904 Ok(ValidationOutput::new(executed_block, timing_stats).with_raw_bal(raw_bal))
905 }
906
907 #[expect(clippy::type_complexity)]
910 pub fn spawn_convert_and_validate<T>(
911 &self,
912 input: &BlockOrPayload<T>,
913 parent: SealedHeader<N::BlockHeader>,
914 ) -> LazyHandle<Result<SealedBlock<N::Block>, InsertPayloadError<N::Block>>>
915 where
916 T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>,
917 V: PayloadValidator<T, Block = N::Block> + Clone,
918 {
919 let input = input.clone();
920 let validator = self.validator.clone();
921 let consensus = self.consensus.clone();
922 let parent_span = Span::current();
923 self.runtime.spawn_blocking_named("payload-convert", move || {
924 let _span = debug_span!(
925 target: "engine::tree::payload_validator",
926 parent: parent_span,
927 "convert_and_validate",
928 )
929 .entered();
930 let block = match input {
931 BlockOrPayload::Block(block) => block,
932 BlockOrPayload::Payload(payload) => {
933 validator.convert_payload_to_block(payload)?
934 }
935 };
936
937 if let Err(e) = consensus.validate_header(block.sealed_header()) {
938 error!(target: "engine::tree::payload_validator", ?block, "Failed to validate header {}: {e}", block.hash());
939 return Err(InsertBlockError::consensus_error(e, block).into())
940 }
941
942 let _enter = debug_span!(target: "engine::tree::payload_validator", "validate_header_against_parent").entered();
944 if let Err(e) = consensus.validate_header_against_parent(block.sealed_header(), &parent)
945 {
946 warn!(target: "engine::tree::payload_validator", ?block, "Failed to validate header {} against parent: {e}", block.hash());
947 return Err(InsertBlockError::consensus_error(e, block).into())
948 }
949 drop(_enter);
950
951 if let Err(e) =
952 consensus.validate_block_pre_execution_with_tx_root(&block, None)
953 {
954 error!(target: "engine::tree::payload_validator", ?block, "Failed to validate block {}: {e}", block.hash());
955 return Err(InsertBlockError::consensus_error(e, block).into())
956 }
957
958 Ok(block)
959 })
960 }
961
962 fn sealed_header_by_hash(
964 &self,
965 hash: B256,
966 state: &EngineApiTreeState<N>,
967 ) -> ProviderResult<Option<SealedHeader<N::BlockHeader>>> {
968 let header = state.tree_state.sealed_header_by_hash(&hash);
970
971 if header.is_some() {
972 Ok(header)
973 } else {
974 self.provider.sealed_header_by_hash(hash)
975 }
976 }
977
978 #[instrument(level = "debug", target = "engine::tree::payload_validator", skip_all)]
986 #[expect(clippy::type_complexity)]
987 fn execute_block<S, Err, T>(
988 &mut self,
989 state_provider: S,
990 env: ExecutionEnv<Evm>,
991 input: &BlockOrPayload<T>,
992 handle: &mut PayloadHandle<impl ExecutableTxFor<Evm>, Err, N::Receipt>,
993 state_hook: Option<Box<dyn OnStateHook + 'static>>,
994 ) -> Result<
995 (
996 BlockExecutionOutput<N::Receipt>,
997 Vec<Address>,
998 ReceiptRootReceiver,
999 Option<BlockAccessList>,
1000 ),
1001 InsertBlockErrorKind,
1002 >
1003 where
1004 S: StateProvider + Send,
1005 Err: core::error::Error + Send + Sync + 'static,
1006 V: PayloadValidator<T, Block = N::Block>,
1007 T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>,
1008 Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
1009 {
1010 debug!(target: "engine::tree::payload_validator", "Executing block");
1011
1012 let has_bal = env.decoded_bal.is_some();
1013 let mut db = debug_span!(target: "engine::tree", "build_state_db").in_scope(|| {
1014 State::builder()
1015 .with_database(StateProviderDatabase::new(state_provider))
1016 .with_bundle_update()
1017 .with_bal_builder_if(has_bal)
1018 .build()
1019 });
1020
1021 let (spec_id, mut executor) = {
1022 let _span = debug_span!(target: "engine::tree", "create_evm").entered();
1023 let spec_id = *env.evm_env.spec_id();
1024 let evm_config = self.evm_config.clone().with_jit_support();
1025 let evm = evm_config.evm_with_env(&mut db, env.evm_env);
1026 let ctx = self
1027 .execution_ctx_for(input)
1028 .map_err(|e| InsertBlockErrorKind::Other(Box::new(e)))?;
1029 let executor = self.evm_config.create_executor(evm, ctx);
1030 (spec_id, executor)
1031 };
1032
1033 if !self.config.precompile_cache_disabled() {
1034 let _span = debug_span!(target: "engine::tree", "setup_precompile_cache").entered();
1035 executor.evm_mut().precompiles_mut().map_cacheable_precompiles(
1036 |address, precompile| {
1037 let metrics = self
1038 .precompile_cache_metrics
1039 .entry(*address)
1040 .or_insert_with(|| CachedPrecompileMetrics::new_with_address(*address))
1041 .clone();
1042 CachedPrecompile::wrap(
1043 precompile,
1044 self.precompile_cache_map.cache_for_address(*address),
1045 spec_id,
1046 Some(metrics),
1047 )
1048 },
1049 );
1050 }
1051
1052 let transaction_count = input.transaction_count();
1053 let (receipt_tx, result_rx) = self.spawn_receipt_root_task(transaction_count);
1054 let executed_tx_index = Arc::clone(handle.executed_tx_index());
1055 executor.evm_mut().db_mut().set_state_hook(state_hook);
1056
1057 let execution_start = Instant::now();
1058
1059 let (executor, senders) = self.execute_transactions(
1061 executor,
1062 transaction_count,
1063 handle.iter_transactions(),
1064 &receipt_tx,
1065 &executed_tx_index,
1066 has_bal,
1067 )?;
1068 drop(receipt_tx);
1069
1070 let post_exec_start = Instant::now();
1072 let (_evm, result) = debug_span!(target: "engine::tree", "BlockExecutor::finish")
1073 .in_scope(|| executor.finish())
1074 .map(|(evm, result)| (evm.into_db(), result))?;
1075 self.metrics.record_post_execution(post_exec_start.elapsed());
1076
1077 debug_span!(target: "engine::tree", "merge_transitions")
1079 .in_scope(|| db.merge_transitions(BundleRetention::Reverts));
1080
1081 let built_bal = if has_bal { db.take_built_alloy_bal() } else { None };
1082 let output = BlockExecutionOutput { result, state: db.take_bundle() };
1083
1084 let execution_duration = execution_start.elapsed();
1085 self.metrics.record_block_execution(&output, execution_duration);
1086 self.metrics.record_block_execution_gas_bucket(output.result.gas_used, execution_duration);
1087 debug!(target: "engine::tree::payload_validator", elapsed = ?execution_duration, "Executed block");
1088
1089 Ok((output, senders, result_rx, built_bal))
1090 }
1091
1092 fn bal_path_eligible(&self, bal: Option<&DecodedBal>) -> Result<bool, InsertBlockErrorKind> {
1101 let has_bal = bal.is_some();
1102 let parallel_execution = has_bal && !self.config.disable_bal_parallel_execution();
1103 if parallel_execution && self.config.disable_bal_parallel_state_root() {
1104 return Err(InsertBlockErrorKind::Other(
1105 "disabling parallel state root is impossible when parallel execution is enabled"
1106 .into(),
1107 ));
1108 }
1109
1110 Ok(parallel_execution)
1111 }
1112
1113 #[instrument(level = "debug", target = "engine::tree::payload_validator", skip_all)]
1123 #[expect(clippy::type_complexity)]
1124 fn execute_block_bal<Tx, Err, MakeStateProvider, T>(
1125 &self,
1126 env: ExecutionEnv<Evm>,
1127 input: &BlockOrPayload<T>,
1128 handle: &PayloadHandle<Tx, Err, N::Receipt>,
1129 make_state_provider: &MakeStateProvider,
1130 ) -> Result<
1131 (
1132 BlockExecutionOutput<N::Receipt>,
1133 Vec<Address>,
1134 ReceiptRootReceiver,
1135 Option<BlockAccessList>,
1136 ),
1137 InsertBlockErrorKind,
1138 >
1139 where
1140 Tx: ExecutableTxFor<Evm> + Send,
1141 Err: core::error::Error + Send + Sync + 'static,
1142 MakeStateProvider: Fn(bool) -> ProviderResult<StateProviderBox> + Sync,
1143 Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
1144 T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>,
1145 V: PayloadValidator<T, Block = N::Block>,
1146 {
1147 debug!(target: "engine::tree::payload_validator", "Executing block via BAL path");
1148
1149 let (receipt_tx, result_rx) = self.spawn_receipt_root_task(env.transaction_count);
1150 let input_bal = env.decoded_bal.ok_or_else(|| {
1151 InsertBlockErrorKind::Other("BAL execute path: no decoded BAL available".into())
1152 })?;
1153
1154 let make_db = |fill_on_miss| {
1155 let provider = make_state_provider(fill_on_miss)
1156 .map_err(crate::tree::payload_processor::bal::BalExecutionError::Provider)?;
1157 Ok(StateProviderDatabase::new(provider))
1158 };
1159 let execution_start = Instant::now();
1160 let ctx =
1161 self.execution_ctx_for(input).map_err(|e| InsertBlockErrorKind::Other(Box::new(e)))?;
1162 let (output, senders, built_bal) = crate::tree::payload_processor::bal::execute_block(
1163 &self.runtime,
1164 &self.evm_config,
1165 &make_db,
1166 input_bal,
1167 env.evm_env,
1168 ctx,
1169 env.transaction_count,
1170 handle.clone_transaction_receiver(),
1171 receipt_tx,
1172 )?;
1173 let execution_duration = execution_start.elapsed();
1174
1175 self.metrics.record_block_execution(&output, execution_duration);
1176 self.metrics.record_block_execution_gas_bucket(output.result.gas_used, execution_duration);
1177 debug!(
1178 target: "engine::tree::payload_validator",
1179 elapsed = ?execution_duration,
1180 "Executed block via BAL path",
1181 );
1182
1183 Ok((output, senders, result_rx, Some(built_bal)))
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<'a, E, Tx, InnerTx, Err, DB>(
1209 &self,
1210 mut executor: E,
1211 transaction_count: usize,
1212 transactions: impl Iterator<Item = Result<Tx, Err>>,
1213 receipt_tx: &crossbeam_channel::Sender<IndexedReceipt<N::Receipt>>,
1214 executed_tx_index: &AtomicUsize,
1215 has_bal: bool,
1216 ) -> Result<(E, Vec<Address>), BlockExecutionError>
1217 where
1218 E: BlockExecutor<Receipt = N::Receipt, Evm: alloy_evm::Evm<DB = &'a mut State<DB>>>,
1219 Tx: alloy_evm::block::ExecutableTx<E> + alloy_evm::RecoveredTx<InnerTx>,
1220 InnerTx: TxHashRef,
1221 DB: revm::Database + 'a,
1222 Err: core::error::Error + Send + Sync + 'static,
1223 {
1224 let mut senders = Vec::with_capacity(transaction_count);
1225
1226 let pre_exec_start = Instant::now();
1228 debug_span!(target: "engine::tree", "pre_execution")
1229 .in_scope(|| executor.apply_pre_execution_changes())?;
1230 self.metrics.record_pre_execution(pre_exec_start.elapsed());
1231
1232 if has_bal {
1234 executor.evm_mut().db_mut().bump_bal_index();
1235 }
1236
1237 let exec_span = debug_span!(target: "engine::tree", "execution").entered();
1239 let mut transactions = transactions.into_iter();
1240 let mut last_sent_len = 0usize;
1245 loop {
1246 let wait_start = Instant::now();
1249 let Some(tx_result) = transactions.next() else { break };
1250 self.metrics.record_transaction_wait(wait_start.elapsed());
1251
1252 let tx = tx_result.map_err(BlockExecutionError::other)?;
1253 let tx_signer = *<Tx as alloy_evm::RecoveredTx<InnerTx>>::signer(&tx);
1254
1255 senders.push(tx_signer);
1256
1257 let _enter = tracing::enabled!(target: "engine::tree", Level::TRACE).then(|| {
1258 tracing::trace_span!(
1259 target: "engine::tree",
1260 "execute tx",
1261 tx_index = senders.len() - 1,
1262 )
1263 .entered()
1264 });
1265 if tracing::enabled!(target: "engine::tree", Level::TRACE) {
1266 trace!(target: "engine::tree", "Executing transaction");
1267 }
1268
1269 let tx_start = Instant::now();
1270 executor.execute_transaction(tx)?;
1271 self.metrics.record_transaction_execution(tx_start.elapsed());
1272
1273 executed_tx_index.store(senders.len(), Ordering::Relaxed);
1275
1276 let current_len = executor.receipts().len();
1277 if current_len > last_sent_len {
1278 last_sent_len = current_len;
1279 if let Some(receipt) = executor.receipts().last() {
1281 let tx_index = current_len - 1;
1282 let _ = receipt_tx.send(IndexedReceipt::new(tx_index, receipt.clone()));
1283 }
1284 }
1285 if has_bal {
1287 executor.evm_mut().db_mut().bump_bal_index();
1288 }
1289 }
1290
1291 drop(exec_span);
1292
1293 Ok((executor, senders))
1294 }
1295
1296 #[instrument(level = "debug", target = "engine::tree::payload_validator", skip_all)]
1308 fn validate_post_execution<T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
1309 &self,
1310 block: &RecoveredBlock<N::Block>,
1311 parent_block: &SealedHeader<N::BlockHeader>,
1312 output: &BlockExecutionOutput<N::Receipt>,
1313 ctx: &mut TreeCtx<'_, N>,
1314 receipt_root_bloom: Option<ReceiptRootBloom>,
1315 built_bal: Option<BlockAccessList>,
1316 ) -> Result<(), InsertBlockErrorKind>
1317 where
1318 V: PayloadValidator<T, Block = N::Block>,
1319 {
1320 let start = Instant::now();
1321
1322 trace!(target: "engine::tree::payload_validator", block=?block.num_hash(), "Validating block consensus");
1323
1324 let _enter =
1326 debug_span!(target: "engine::tree::payload_validator", "validate_block_post_execution")
1327 .entered();
1328 let block_access_list_hash =
1329 built_bal.as_ref().map(|bal| compute_block_access_list_hash(bal));
1330
1331 if let Err(err) = self.consensus.validate_block_post_execution(
1332 block,
1333 output,
1334 receipt_root_bloom,
1335 block_access_list_hash,
1336 ) {
1337 self.on_invalid_block(parent_block, block, output, None, ctx.state_mut());
1339 return Err(err.into())
1340 }
1341 drop(_enter);
1342
1343 self.metrics
1345 .block_validation
1346 .post_execution_validation_duration
1347 .record(start.elapsed().as_secs_f64());
1348
1349 Ok(())
1350 }
1351
1352 #[instrument(
1357 level = "debug",
1358 target = "engine::tree::payload_validator",
1359 skip_all,
1360 fields(
1361 has_hint_stream = hint_stream.is_some(),
1362 has_hashed_update_stream = hashed_update_stream.is_some(),
1363 parallel_bal_execution
1364 )
1365 )]
1366 fn spawn_payload_processor<T: ExecutableTxIterator<Evm>>(
1367 &self,
1368 env: ExecutionEnv<Evm>,
1369 txs: T,
1370 provider_builder: StateProviderBuilder<N, P>,
1371 hint_stream: Option<StateRootHintStream>,
1372 hashed_update_stream: Option<StateRootUpdateStream>,
1373 parallel_bal_execution: bool,
1374 ) -> Result<
1375 PayloadHandle<
1376 impl ExecutableTxFor<Evm> + use<N, P, Evm, V, T>,
1377 impl core::error::Error + Send + Sync + 'static + use<N, P, Evm, V, T>,
1378 N::Receipt,
1379 >,
1380 InsertBlockErrorKind,
1381 > {
1382 let start = Instant::now();
1383 let handle = self.payload_processor.spawn_with_state_root_streams(
1384 env,
1385 txs,
1386 provider_builder,
1387 hint_stream,
1388 hashed_update_stream,
1389 parallel_bal_execution,
1390 );
1391
1392 self.metrics.block_validation.spawn_payload_processor.record(start.elapsed().as_secs_f64());
1393
1394 Ok(handle)
1395 }
1396
1397 fn state_provider_builder(
1402 &self,
1403 hash: B256,
1404 state: &EngineApiTreeState<N>,
1405 ) -> ProviderResult<Option<StateProviderBuilder<N, P>>> {
1406 if let Some((historical, blocks)) = state.tree_state.blocks_by_hash(hash) {
1407 debug!(target: "engine::tree::payload_validator", %hash, %historical, "found canonical state for block in memory, creating provider builder");
1408 return Ok(Some(StateProviderBuilder::new(
1410 self.provider.clone(),
1411 historical,
1412 Some(blocks),
1413 )))
1414 }
1415
1416 if let Some(header) = self.provider.header(hash)? {
1418 debug!(target: "engine::tree::payload_validator", %hash, number = %header.number(), "found canonical state for block in database, creating provider builder");
1419 return Ok(Some(StateProviderBuilder::new(self.provider.clone(), hash, None)))
1422 }
1423
1424 debug!(target: "engine::tree::payload_validator", %hash, "no canonical state found for block");
1425 Ok(None)
1426 }
1427
1428 fn on_invalid_block(
1430 &self,
1431 parent_header: &SealedHeader<N::BlockHeader>,
1432 block: &RecoveredBlock<N::Block>,
1433 output: &BlockExecutionOutput<N::Receipt>,
1434 trie_updates: Option<(&TrieUpdates, B256)>,
1435 state: &mut EngineApiTreeState<N>,
1436 ) {
1437 if state.invalid_headers.get(&block.hash()).is_some() {
1438 return
1440 }
1441 self.invalid_block_hook.on_invalid_block(parent_header, block, output, trie_updates);
1442 }
1443
1444 fn payload_state_root_handle_for(
1447 &self,
1448 parent_hash: B256,
1449 parent_header: &N::BlockHeader,
1450 timestamp: u64,
1451 state: &mut EngineApiTreeState<N>,
1452 ) -> Option<PayloadStateRootHandle> {
1453 let provider_builder = match self.state_provider_builder(parent_hash, state) {
1454 Ok(Some(provider_builder)) => provider_builder,
1455 Ok(None) => return None,
1456 Err(err) => {
1457 warn!(
1458 target: "engine::tree::payload_validator",
1459 %err,
1460 %parent_hash,
1461 "failed to prepare payload-builder state-root provider"
1462 );
1463 return None
1464 }
1465 };
1466 let overlay_factory = OverlayStateProviderFactory::new(
1467 self.provider.clone(),
1468 state.tree_state.overlay_manager.overlay_builder(parent_hash),
1469 );
1470
1471 match self.state_root_strategy.prepare_payload_builder(PayloadStateRootJobContext::new(
1472 &self.runtime,
1473 &self.overlay_manager,
1474 parent_hash,
1475 parent_header,
1476 timestamp,
1477 state,
1478 provider_builder,
1479 overlay_factory,
1480 &self.config,
1481 )) {
1482 Ok(handle) => handle,
1483 Err(err) => {
1484 warn!(
1485 target: "engine::tree::payload_validator",
1486 %err,
1487 %parent_hash,
1488 "failed to prepare payload-builder state-root job"
1489 );
1490 None
1491 }
1492 }
1493 }
1494
1495 fn spawn_deferred_trie_task(
1508 &self,
1509 block: Arc<RecoveredBlock<N::Block>>,
1510 execution_outcome: Arc<BlockExecutionOutput<N::Receipt>>,
1511 hashed_state: LazyHashedPostState,
1512 trie_output: Arc<TrieUpdates>,
1513 ) -> ExecutedBlock<N> {
1514 let hashed_state = match hashed_state.try_into_inner() {
1518 Ok(state) => state,
1519 Err(handle) => handle.get().clone(),
1520 };
1521 let (deferred_trie_data, deferred_trie_task) =
1522 LazyTrieData::pending(hashed_state, trie_output);
1523 let block_validation_metrics = self.metrics.block_validation.clone();
1524
1525 let block_number = block.number();
1527
1528 let compute_trie_input_task = move || {
1530 let _span = debug_span!(
1531 target: "engine::tree::payload_validator",
1532 "compute_trie_input_task",
1533 block_number
1534 )
1535 .entered();
1536
1537 let compute_start = Instant::now();
1538 let computed = deferred_trie_task.compute_and_publish();
1539 block_validation_metrics
1540 .deferred_trie_compute_duration
1541 .record(compute_start.elapsed().as_secs_f64());
1542
1543 block_validation_metrics
1545 .hashed_post_state_size
1546 .record(computed.sorted.hashed_state.total_len() as f64);
1547 block_validation_metrics
1548 .trie_updates_sorted_size
1549 .record(computed.sorted.trie_updates.total_len() as f64);
1550 };
1551
1552 self.runtime.spawn_blocking_named(DEFERRED_TRIE_WORKER_NAME, compute_trie_input_task);
1554
1555 ExecutedBlock::with_deferred_trie_data(block, execution_outcome, deferred_trie_data)
1556 }
1557
1558 fn calculate_timing_stats(
1559 &self,
1560 block: &RecoveredBlock<N::Block>,
1561 provider_stats: Arc<StateProviderStats>,
1562 cache_stats: Option<Arc<CacheStats>>,
1563 output: &BlockExecutionOutput<N::Receipt>,
1564 execution_duration: Duration,
1565 state_hash_duration: Duration,
1566 ) -> Box<ExecutionTimingStats> {
1567 let accounts_read = provider_stats.total_account_fetches();
1568 let storage_read = provider_stats.total_storage_fetches();
1569 let code_read = provider_stats.total_code_fetches();
1570 let code_bytes_read = provider_stats.total_code_fetched_bytes();
1571
1572 let accounts_changed = output.state.state.len();
1574 let accounts_deleted =
1575 output.state.state.values().filter(|acc| acc.was_destroyed()).count();
1576 let storage_slots_changed =
1577 output.state.state.values().map(|account| account.storage.len()).sum::<usize>();
1578 let storage_slots_deleted = output
1579 .state
1580 .state
1581 .values()
1582 .flat_map(|account| account.storage.values())
1583 .filter(|slot| {
1584 slot.present_value.is_zero() && !slot.previous_or_original_value.is_zero()
1585 })
1586 .count();
1587
1588 let is_new_deployment = |acc: &BundleAccount| -> bool {
1590 let has_code_now = acc.info.as_ref().is_some_and(|info| info.code_hash != KECCAK_EMPTY);
1591 let had_no_code_before = acc
1592 .original_info
1593 .as_ref()
1594 .map(|info| info.code_hash == KECCAK_EMPTY)
1595 .unwrap_or(true);
1596 has_code_now && had_no_code_before
1597 };
1598
1599 let bytecodes_changed =
1600 output.state.state.values().filter(|acc| is_new_deployment(acc)).count();
1601
1602 let unique_new_code_hashes: B256Set = output
1604 .state
1605 .state
1606 .values()
1607 .filter(|acc| is_new_deployment(acc))
1608 .filter_map(|acc| acc.info.as_ref().map(|info| info.code_hash))
1609 .collect();
1610 let code_bytes_written: usize = unique_new_code_hashes
1611 .iter()
1612 .filter_map(|hash| {
1613 output.state.contracts.get(hash).map(|bytecode| bytecode.original_bytes().len())
1614 })
1615 .sum();
1616
1617 let state_read_duration = provider_stats.total_account_fetch_latency() +
1619 provider_stats.total_storage_fetch_latency() +
1620 provider_stats.total_code_fetch_latency();
1621
1622 let eip7702_delegations_set =
1625 output.state.contracts.values().filter(|bytecode| bytecode.is_eip7702()).count();
1626 let eip7702_delegations_cleared = output
1631 .state
1632 .state
1633 .values()
1634 .filter(|acc| {
1635 let original_was_eip7702 = acc
1637 .original_info
1638 .as_ref()
1639 .and_then(|info| info.code.as_ref())
1640 .map(|bytecode| bytecode.is_eip7702())
1641 .unwrap_or(false);
1642
1643 let code_now_empty =
1645 acc.info.as_ref().map(|info| info.code_hash == KECCAK_EMPTY).unwrap_or(false);
1646
1647 original_was_eip7702 && code_now_empty
1648 })
1649 .count();
1650
1651 let (account_cache_hits, account_cache_misses) = cache_stats
1653 .as_ref()
1654 .map(|s| (s.account_hits(), s.account_misses()))
1655 .unwrap_or_default();
1656 let (storage_cache_hits, storage_cache_misses) = cache_stats
1657 .as_ref()
1658 .map(|s| (s.storage_hits(), s.storage_misses()))
1659 .unwrap_or_default();
1660 let (code_cache_hits, code_cache_misses) =
1661 cache_stats.as_ref().map(|s| (s.code_hits(), s.code_misses())).unwrap_or_default();
1662 let (txpool_snapshot_account_hits, txpool_snapshot_account_misses) = cache_stats
1663 .as_ref()
1664 .map(|s| (s.txpool_snapshot_account_hits(), s.txpool_snapshot_account_misses()))
1665 .unwrap_or_default();
1666 let (txpool_snapshot_storage_hits, txpool_snapshot_storage_misses) = cache_stats
1667 .as_ref()
1668 .map(|s| (s.txpool_snapshot_storage_hits(), s.txpool_snapshot_storage_misses()))
1669 .unwrap_or_default();
1670 let (txpool_snapshot_code_hits, txpool_snapshot_code_misses) = cache_stats
1671 .as_ref()
1672 .map(|s| (s.txpool_snapshot_code_hits(), s.txpool_snapshot_code_misses()))
1673 .unwrap_or_default();
1674
1675 Box::new(ExecutionTimingStats {
1677 block_number: block.number(),
1678 block_hash: block.hash(),
1679 gas_used: output.result.gas_used,
1680 tx_count: block.transaction_count(),
1681 execution_duration,
1682 state_read_duration,
1683 state_hash_duration,
1684 accounts_read,
1685 storage_read,
1686 code_read,
1687 code_bytes_read,
1688 accounts_changed,
1689 accounts_deleted,
1690 storage_slots_changed,
1691 storage_slots_deleted,
1692 bytecodes_changed,
1693 code_bytes_written,
1694 eip7702_delegations_set,
1695 eip7702_delegations_cleared,
1696 account_cache_hits,
1697 account_cache_misses,
1698 storage_cache_hits,
1699 storage_cache_misses,
1700 code_cache_hits,
1701 code_cache_misses,
1702 txpool_snapshot_account_hits,
1703 txpool_snapshot_account_misses,
1704 txpool_snapshot_storage_hits,
1705 txpool_snapshot_storage_misses,
1706 txpool_snapshot_code_hits,
1707 txpool_snapshot_code_misses,
1708 })
1709 }
1710}
1711
1712pub trait EngineValidator<
1716 Types: PayloadTypes,
1717 N: NodePrimitives = <<Types as PayloadTypes>::BuiltPayload as BuiltPayload>::Primitives,
1718>: Send + Sync + 'static
1719{
1720 fn validate_payload_attributes_against_header(
1730 &self,
1731 attr: &Types::PayloadAttributes,
1732 header: &N::BlockHeader,
1733 ) -> Result<(), InvalidPayloadAttributesError>;
1734
1735 fn convert_payload_to_block(
1744 &self,
1745 payload: Types::ExecutionData,
1746 ) -> Result<SealedBlock<N::Block>, NewPayloadError>;
1747
1748 fn validate_payload(
1750 &mut self,
1751 payload: Types::ExecutionData,
1752 ctx: TreeCtx<'_, N>,
1753 ) -> ValidationOutcome<N>;
1754
1755 fn validate_block(
1757 &mut self,
1758 block: SealedBlock<N::Block>,
1759 ctx: TreeCtx<'_, N>,
1760 ) -> ValidationOutcome<N>;
1761
1762 fn on_inserted_executed_block(
1767 &self,
1768 block: BuiltPayloadExecutedBlock<N>,
1769 ) -> ProviderResult<ExecutedBlock<N>>;
1770
1771 fn on_canonical_head_changed(&self, _hash: B256, _state: &EngineApiTreeState<N>) {}
1775
1776 fn payload_builder_resources(
1780 &self,
1781 parent_hash: B256,
1782 parent_header: &N::BlockHeader,
1783 timestamp: u64,
1784 state: &mut EngineApiTreeState<N>,
1785 ) -> PayloadBuilderResources;
1786}
1787
1788impl<N, Types, P, Evm, V> EngineValidator<Types> for BasicEngineValidator<P, Evm, V>
1789where
1790 P: DatabaseProviderFactory<
1791 Provider: BlockReader
1792 + StageCheckpointReader
1793 + PruneCheckpointReader
1794 + ChangeSetReader
1795 + StorageChangeSetReader
1796 + BlockNumReader
1797 + StorageSettingsCache,
1798 > + BlockReader<Header = N::BlockHeader>
1799 + StateProviderFactory
1800 + StateReader
1801 + ChangeSetReader
1802 + BlockNumReader
1803 + HashedPostStateProvider
1804 + Clone
1805 + 'static,
1806 OverlayStateProviderFactory<P, N>: DatabaseProviderROFactory<Provider: TrieCursorFactory + HashedCursorFactory>
1807 + Clone
1808 + Send
1809 + Sync
1810 + 'static,
1811 N: NodePrimitives,
1812 V: PayloadValidator<Types, Block = N::Block> + Clone,
1813 Evm: ConfigureEngineEvm<Types::ExecutionData, Primitives = N> + 'static,
1814 Types: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>,
1815{
1816 fn validate_payload_attributes_against_header(
1817 &self,
1818 attr: &Types::PayloadAttributes,
1819 header: &N::BlockHeader,
1820 ) -> Result<(), InvalidPayloadAttributesError> {
1821 self.validator.validate_payload_attributes_against_header(attr, header)
1822 }
1823
1824 fn convert_payload_to_block(
1825 &self,
1826 payload: Types::ExecutionData,
1827 ) -> Result<SealedBlock<N::Block>, NewPayloadError> {
1828 let block = self.validator.convert_payload_to_block(payload)?;
1829 Ok(block)
1830 }
1831
1832 fn validate_payload(
1833 &mut self,
1834 payload: Types::ExecutionData,
1835 ctx: TreeCtx<'_, N>,
1836 ) -> ValidationOutcome<N> {
1837 self.validate_block_with_state(BlockOrPayload::Payload(payload), ctx)
1838 }
1839
1840 fn validate_block(
1841 &mut self,
1842 block: SealedBlock<N::Block>,
1843 ctx: TreeCtx<'_, N>,
1844 ) -> ValidationOutcome<N> {
1845 self.validate_block_with_state(BlockOrPayload::Block(block), ctx)
1846 }
1847
1848 fn on_inserted_executed_block(
1849 &self,
1850 block: BuiltPayloadExecutedBlock<N>,
1851 ) -> ProviderResult<ExecutedBlock<N>> {
1852 self.payload_processor.on_inserted_executed_block(
1853 block.recovered_block.block_with_parent(),
1854 &block.execution_output.state,
1855 );
1856
1857 Ok(self.spawn_deferred_trie_task(
1858 block.recovered_block,
1859 block.execution_output,
1860 LazyHashedPostState::ready(block.hashed_state),
1861 block.trie_updates,
1862 ))
1863 }
1864
1865 fn on_canonical_head_changed(&self, hash: B256, state: &EngineApiTreeState<N>) {
1866 let Some(txpool_prewarm) = self.txpool_prewarm.as_ref() else { return };
1867
1868 let parent = match self.sealed_header_by_hash(hash, state) {
1871 Ok(Some(header)) => header,
1872 Ok(None) => return,
1873 Err(err) => {
1874 trace!(
1875 target: "engine::tree::txpool_prewarm",
1876 %err,
1877 block_hash = ?hash,
1878 "failed to fetch canonical header for txpool prewarming"
1879 );
1880 return
1881 }
1882 };
1883 let evm_env = match self.evm_config.evm_env(parent.header()) {
1887 Ok(evm_env) => evm_env,
1888 Err(err) => {
1889 trace!(
1890 target: "engine::tree::txpool_prewarm",
1891 %err,
1892 block_hash = ?parent.hash(),
1893 "failed to derive canonical txpool prewarming environment"
1894 );
1895 return
1896 }
1897 };
1898
1899 let provider_builder = match self.state_provider_builder(parent.hash(), state) {
1900 Ok(Some(provider_builder)) => provider_builder,
1901 Ok(None) => return,
1902 Err(err) => {
1903 trace!(
1904 target: "engine::tree::txpool_prewarm",
1905 %err,
1906 block_hash = ?parent.hash(),
1907 "failed to derive canonical txpool prewarming provider"
1908 );
1909 return
1910 }
1911 };
1912 txpool_prewarm.start(parent.hash(), evm_env, provider_builder)
1913 }
1914
1915 fn payload_builder_resources(
1916 &self,
1917 parent_hash: B256,
1918 parent_header: &N::BlockHeader,
1919 timestamp: u64,
1920 state: &mut EngineApiTreeState<N>,
1921 ) -> PayloadBuilderResources {
1922 let execution_cache = self
1923 .config
1924 .share_execution_cache_with_payload_builder()
1925 .then(|| self.payload_processor.cache_for(parent_hash));
1926 let state_root_handle =
1927 self.payload_state_root_handle_for(parent_hash, parent_header, timestamp, state);
1928 let mut resources = PayloadBuilderResources::new(execution_cache, state_root_handle)
1929 .with_lease(PayloadBuilderLease::new(JitPauseGuard::new(&self.evm_config)));
1930 if let Some(txpool_prewarm) = self.txpool_prewarm.as_ref() {
1934 let txpool_lease = PayloadBuilderLease::new(txpool_prewarm.pause());
1935 resources = resources.with_lease(txpool_lease);
1936 }
1937 resources
1938 }
1939}
1940
1941impl<P, Evm, V> WaitForCaches for BasicEngineValidator<P, Evm, V>
1942where
1943 Evm: ConfigureEvm,
1944{
1945 fn wait_for_caches(&self) -> CacheWaitDurations {
1946 debug!(target: "engine::tree::payload_validator", "Waiting for execution cache and sparse trie locks");
1947
1948 let execution_cache = self.payload_processor.execution_cache();
1949 let overlay_manager = self.overlay_manager.clone();
1950 let (execution_tx, execution_rx) = std::sync::mpsc::channel();
1951 let (sparse_trie_tx, sparse_trie_rx) = std::sync::mpsc::channel();
1952
1953 self.runtime.spawn_blocking_named("wait-exec-cache", move || {
1954 let _ = execution_tx.send(execution_cache.wait_for_availability());
1955 });
1956 self.runtime.spawn_blocking_named("wait-sparse-tri", move || {
1957 let _ = sparse_trie_tx.send(overlay_manager.wait_for_sparse_trie_availability());
1958 });
1959
1960 let execution_cache =
1961 execution_rx.recv().expect("execution cache wait task failed to send result");
1962 let sparse_trie =
1963 sparse_trie_rx.recv().expect("sparse trie wait task failed to send result");
1964 debug!(
1965 target: "engine::tree::payload_validator",
1966 ?execution_cache,
1967 ?sparse_trie,
1968 "Execution cache and sparse trie locks acquired"
1969 );
1970 CacheWaitDurations { execution_cache, sparse_trie }
1971 }
1972}
1973
1974#[derive(Debug, Clone)]
1976pub enum BlockOrPayload<T: PayloadTypes> {
1977 Payload(T::ExecutionData),
1979 Block(SealedBlock<BlockTy<<T::BuiltPayload as BuiltPayload>::Primitives>>),
1981}
1982
1983impl<T: PayloadTypes> BlockOrPayload<T> {
1984 pub fn hash(&self) -> B256 {
1986 match self {
1987 Self::Payload(payload) => payload.block_hash(),
1988 Self::Block(block) => block.hash(),
1989 }
1990 }
1991
1992 pub fn num_hash(&self) -> NumHash {
1994 match self {
1995 Self::Payload(payload) => payload.num_hash(),
1996 Self::Block(block) => block.num_hash(),
1997 }
1998 }
1999
2000 pub fn parent_hash(&self) -> B256 {
2002 match self {
2003 Self::Payload(payload) => payload.parent_hash(),
2004 Self::Block(block) => block.parent_hash(),
2005 }
2006 }
2007
2008 pub fn block_with_parent(&self) -> BlockWithParent {
2010 match self {
2011 Self::Payload(payload) => payload.block_with_parent(),
2012 Self::Block(block) => block.block_with_parent(),
2013 }
2014 }
2015
2016 pub const fn type_name(&self) -> &'static str {
2018 match self {
2019 Self::Payload(_) => "payload",
2020 Self::Block(_) => "block",
2021 }
2022 }
2023
2024 pub const fn is_payload(&self) -> bool {
2026 matches!(self, Self::Payload(_))
2027 }
2028
2029 pub const fn is_block(&self) -> bool {
2031 matches!(self, Self::Block(_))
2032 }
2033
2034 pub fn try_decoded_access_list(&self) -> Result<Option<DecodedBal>, alloy_rlp::Error> {
2036 match self {
2037 Self::Payload(payload) => payload
2038 .block_access_list()
2039 .map(|block_access_list| DecodedBal::from_rlp_bytes(block_access_list.clone()))
2040 .transpose(),
2041 Self::Block(_) => Ok(None),
2042 }
2043 }
2044
2045 pub fn transaction_count(&self) -> usize
2047 where
2048 T::ExecutionData: ExecutionPayload,
2049 {
2050 match self {
2051 Self::Payload(payload) => payload.transaction_count(),
2052 Self::Block(block) => block.transaction_count(),
2053 }
2054 }
2055
2056 pub fn withdrawals(&self) -> Option<&[Withdrawal]>
2058 where
2059 T::ExecutionData: ExecutionPayload,
2060 {
2061 match self {
2062 Self::Payload(payload) => payload.withdrawals().map(|w| w.as_slice()),
2063 Self::Block(block) => block.body().withdrawals().map(|w| w.as_slice()),
2064 }
2065 }
2066
2067 pub fn gas_used(&self) -> u64
2069 where
2070 T::ExecutionData: ExecutionPayload,
2071 {
2072 match self {
2073 Self::Payload(payload) => payload.gas_used(),
2074 Self::Block(block) => block.gas_used(),
2075 }
2076 }
2077
2078 pub fn gas_limit(&self) -> u64
2080 where
2081 T::ExecutionData: ExecutionPayload,
2082 {
2083 match self {
2084 Self::Payload(payload) => payload.gas_limit(),
2085 Self::Block(block) => block.gas_limit(),
2086 }
2087 }
2088}