1use crate::tree::{
99 error::{
100 BlockAccessListDecodeError, InsertBlockError, InsertBlockErrorKind, InsertPayloadError,
101 },
102 instrumented_state::{InstrumentedStateProvider, StateProviderMetrics, StateProviderStats},
103 metrics::PrewarmReuseStats,
104 payload_processor::{
105 reuse::{PrewarmResults, ReuseMiss},
106 PayloadProcessor,
107 },
108 precompile_cache::{CachedPrecompile, CachedPrecompileMetrics, PrecompileCacheMap},
109 txpool_prewarm,
110 types::{InsertPayloadResult, ValidationOutput},
111 CacheWaitDurations, CachedStateProvider, EngineApiMetrics, EngineApiTreeState, ExecutionEnv,
112 PayloadHandle, StateProviderDatabase, TreeConfig, WaitForCaches,
113};
114use alloy_consensus::{transaction::Either, Transaction as _, TxReceipt as _};
115use alloy_eip7928::{
116 bal::{Bal, DecodedBal},
117 BlockAccessList,
118};
119use alloy_eips::{eip1898::BlockWithParent, eip4895::Withdrawal, NumHash};
120use alloy_evm::Evm;
121use alloy_primitives::{
122 map::{AddressMap, B256Set},
123 B256,
124};
125use reth_tasks::LazyHandle;
126
127use crate::tree::{
128 payload_processor::receipt_root_task::{IndexedReceipt, ReceiptRootTaskHandle},
129 state_root_strategy::{
130 DefaultStateRootStrategy, LazyHashedPostState, PayloadStateRootHandle,
131 PayloadStateRootJobContext, StateRootHintStream, StateRootJobContext, StateRootStrategy,
132 StateRootUpdateStream,
133 },
134};
135use alloy_primitives::Address;
136use reth_chain_state::{CanonicalInMemoryState, ExecutedBlock, ExecutionTimingStats};
137use reth_consensus::{ConsensusError, FullConsensus, ReceiptRootBloom};
138use reth_engine_primitives::{
139 ConfigureEngineEvm, ExecutableTxIterator, ExecutionPayload, InvalidBlockHook, PayloadValidator,
140};
141use reth_errors::{BlockExecutionError, BlockValidationError, ProviderResult};
142use reth_evm::{
143 block::{BlockExecutor, TxResult},
144 execute::ExecutableTxFor,
145 ConfigureEvm, EvmEnvFor, ExecutionCtxFor, HaltReasonFor, OnStateHook, SpecFor,
146 TxExecutionResultFor,
147};
148use reth_execution_cache::{CacheFillMode, CacheStats};
149use reth_execution_types::DecodedRevmBal;
150use reth_network_p2p::full_block::SealedBlockWithAccessList;
151use reth_payload_builder::{PayloadBuilderLease, PayloadBuilderResources};
152use reth_payload_primitives::{
153 BuiltPayload, BuiltPayloadExecutedBlock, InvalidPayloadAttributesError, NewPayloadError,
154 PayloadTypes,
155};
156use reth_primitives_traits::{
157 AlloyBlockHeader, BlockBody, BlockTy, FastInstant as Instant, GotExpected, NodePrimitives,
158 RecoveredBlock, SealedBlock, SealedHeader, SignerRecoverable, TxTy,
159};
160use reth_provider::{
161 BlockExecutionOutput, BlockReader, ChangeSetReader, DatabaseProviderFactory,
162 DatabaseProviderROFactory, EvmStateProvider, EvmStateProviderBox, HistoryReader, ProviderError,
163 PruneCheckpointReader, StageCheckpointReader, StateProvider, StateProviderFactory, StateReader,
164 StorageChangeSetReader, StorageSettingsCache,
165};
166use reth_revm::db::{states::bundle_state::BundleRetention, BundleAccount, State};
167use reth_storage_overlay::{OverlayManager, OverlayStateProviderFactory};
168use reth_trie::{updates::TrieUpdates, HashedPostState, KeccakKeyHasher, LazyTrieData};
169use revm::{
170 context::Block as _,
171 state::{bal::Bal as RevmBal, AccountInfo},
172};
173use std::{
174 sync::{
175 atomic::{AtomicUsize, Ordering},
176 Arc,
177 },
178 time::Duration,
179};
180use tracing::{debug, debug_span, error, info, instrument, trace, warn, Level, Span};
181
182pub use crate::tree::types::ValidationOutcome;
183
184const MAX_EXPECTED_GAS_LIMIT_MULTIPLIER: u64 = 2;
187
188const DEFERRED_TRIE_WORKER_NAME: &str = "deferred-trie";
190
191type ReceiptRootSender<N> =
192 crossbeam_channel::Sender<IndexedReceipt<<N as NodePrimitives>::Receipt>>;
193type ReceiptRootReceiver = tokio::sync::oneshot::Receiver<(B256, alloy_primitives::Bloom)>;
194
195pub struct TreeCtx<'a, N: NodePrimitives> {
200 state: &'a mut EngineApiTreeState<N>,
202 canonical_in_memory_state: &'a CanonicalInMemoryState<N>,
204}
205
206impl<'a, N: NodePrimitives> std::fmt::Debug for TreeCtx<'a, N> {
207 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
208 f.debug_struct("TreeCtx")
209 .field("state", &"EngineApiTreeState")
210 .field("canonical_in_memory_state", &self.canonical_in_memory_state)
211 .finish()
212 }
213}
214
215impl<'a, N: NodePrimitives> TreeCtx<'a, N> {
216 pub const fn new(
218 state: &'a mut EngineApiTreeState<N>,
219 canonical_in_memory_state: &'a CanonicalInMemoryState<N>,
220 ) -> Self {
221 Self { state, canonical_in_memory_state }
222 }
223}
224
225impl<'a, N: NodePrimitives> TreeCtx<'a, N> {
226 pub const fn state(&self) -> &EngineApiTreeState<N> {
228 &*self.state
229 }
230
231 pub const fn state_mut(&mut self) -> &mut EngineApiTreeState<N> {
233 self.state
234 }
235
236 pub const fn canonical_in_memory_state(&self) -> &'a CanonicalInMemoryState<N> {
238 self.canonical_in_memory_state
239 }
240}
241
242struct JitPauseGuard<Evm: ConfigureEvm>(Evm);
248
249impl<Evm: ConfigureEvm> JitPauseGuard<Evm> {
250 fn new(evm_config: &Evm) -> Self {
251 if let Some(jit_backend) = evm_config.jit_backend() {
252 jit_backend.pause();
253 }
254 Self(evm_config.clone())
255 }
256}
257
258impl<Evm: ConfigureEvm> Drop for JitPauseGuard<Evm> {
259 fn drop(&mut self) {
260 if let Some(jit_backend) = self.0.jit_backend() {
261 jit_backend.resume();
262 }
263 }
264}
265
266#[derive(derive_more::Debug)]
274pub struct BasicEngineValidator<P, Evm, V>
275where
276 Evm: ConfigureEvm,
277{
278 provider: P,
280 consensus: Arc<dyn FullConsensus<Evm::Primitives>>,
282 evm_config: Evm,
284 config: TreeConfig,
286 payload_processor: PayloadProcessor<Evm>,
288 precompile_cache_map: PrecompileCacheMap<SpecFor<Evm>>,
290 precompile_cache_metrics: AddressMap<CachedPrecompileMetrics>,
292 #[debug(skip)]
294 invalid_block_hook: Box<dyn InvalidBlockHook<Evm::Primitives>>,
295 metrics: EngineApiMetrics,
297 validator: V,
299 runtime: reth_tasks::Runtime,
301 overlay_manager: OverlayManager<Evm::Primitives>,
303 #[debug(skip)]
305 state_root_strategy: Arc<dyn StateRootStrategy<Evm::Primitives, P, Evm>>,
306 #[debug(skip)]
310 txpool_prewarm: Option<txpool_prewarm::Handle<Evm::Primitives, P, Evm>>,
311 bal_hash_buf: Vec<u8>,
313}
314
315impl<N, P, Evm, V> BasicEngineValidator<P, Evm, V>
316where
317 N: NodePrimitives,
318 P: DatabaseProviderFactory<
319 Provider: BlockReader
320 + StageCheckpointReader
321 + PruneCheckpointReader
322 + ChangeSetReader
323 + StorageChangeSetReader
324 + StorageSettingsCache
325 + HistoryReader
326 + 'static,
327 > + BlockReader<Header = N::BlockHeader>
328 + ChangeSetReader
329 + StateProviderFactory
330 + StateReader
331 + Clone
332 + 'static,
333 Evm: ConfigureEvm<Primitives = N> + 'static,
334{
335 #[expect(clippy::too_many_arguments)]
337 pub fn new(
338 provider: P,
339 consensus: Arc<dyn FullConsensus<N>>,
340 evm_config: Evm,
341 validator: V,
342 config: TreeConfig,
343 invalid_block_hook: Box<dyn InvalidBlockHook<N>>,
344 overlay_manager: OverlayManager<N>,
345 runtime: reth_tasks::Runtime,
346 ) -> Self {
347 let precompile_cache_map = PrecompileCacheMap::default();
348 let payload_processor = PayloadProcessor::new(
349 runtime.clone(),
350 evm_config.clone(),
351 &config,
352 precompile_cache_map.clone(),
353 );
354 Self {
355 provider,
356 consensus,
357 evm_config,
358 payload_processor,
359 precompile_cache_map,
360 precompile_cache_metrics: AddressMap::default(),
361 config,
362 invalid_block_hook,
363 metrics: EngineApiMetrics::default(),
364 validator,
365 runtime,
366 overlay_manager,
367 state_root_strategy: Arc::new(DefaultStateRootStrategy::default()),
368 txpool_prewarm: None,
369 bal_hash_buf: Vec::new(),
370 }
371 }
372
373 pub fn with_state_root_strategy(
375 mut self,
376 state_root_strategy: Arc<dyn StateRootStrategy<N, P, Evm>>,
377 ) -> Self {
378 self.state_root_strategy = state_root_strategy;
379 self
380 }
381
382 pub fn with_txpool_prewarming(
384 mut self,
385 source: impl crate::tree::TxPoolPrewarmSource<N> + 'static,
386 ) -> Self {
387 self.txpool_prewarm = Some(txpool_prewarm::Handle::spawn(
388 &self.runtime,
389 Arc::new(source),
390 self.evm_config.clone(),
391 self.evm_config.reuses_prewarm_results() && self.config.prewarm_reuse(),
392 ));
393 self
394 }
395
396 #[instrument(level = "debug", target = "engine::tree::payload_validator", skip_all)]
398 pub fn convert_to_block<T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
399 &self,
400 input: BlockOrPayload<T>,
401 ) -> Result<SealedBlock<N::Block>, NewPayloadError>
402 where
403 V: PayloadValidator<T, Block = N::Block>,
404 {
405 match input {
406 BlockOrPayload::Payload(payload) => self.validator.convert_payload_to_block(payload),
407 BlockOrPayload::Block(block) => Ok(block.split().0),
408 }
409 }
410
411 pub fn evm_env_for<T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
413 &self,
414 input: &BlockOrPayload<T>,
415 ) -> Result<EvmEnvFor<Evm>, Evm::Error>
416 where
417 V: PayloadValidator<T, Block = N::Block>,
418 Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
419 {
420 match input {
421 BlockOrPayload::Payload(payload) => Ok(self.evm_config.evm_env_for_payload(payload)?),
422 BlockOrPayload::Block(block) => Ok(self.evm_config.evm_env(block.header())?),
423 }
424 }
425
426 pub fn tx_iterator_for<'a, T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
428 &'a self,
429 input: &'a BlockOrPayload<T>,
430 ) -> Result<impl ExecutableTxIterator<Evm>, NewPayloadError>
431 where
432 V: PayloadValidator<T, Block = N::Block>,
433 Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
434 {
435 Ok(match input {
436 BlockOrPayload::Payload(payload) => {
437 let iter = self
438 .evm_config
439 .tx_iterator_for_payload(payload)
440 .map_err(NewPayloadError::other)?;
441 Either::Left(iter)
442 }
443 BlockOrPayload::Block(block) => {
444 let txs = block.body().clone_transactions();
445 let convert = |tx: N::SignedTx| tx.try_into_recovered();
446 Either::Right((txs, convert))
447 }
448 })
449 }
450
451 pub fn execution_ctx_for<'a, T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
453 &self,
454 input: &'a BlockOrPayload<T>,
455 ) -> Result<ExecutionCtxFor<'a, Evm>, Evm::Error>
456 where
457 V: PayloadValidator<T, Block = N::Block>,
458 Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
459 {
460 match input {
461 BlockOrPayload::Payload(payload) => Ok(self.evm_config.context_for_payload(payload)?),
462 BlockOrPayload::Block(block) => Ok(self.evm_config.context_for_block(block)?),
463 }
464 }
465
466 #[instrument(
474 level = "debug",
475 target = "engine::tree::payload_validator",
476 skip_all,
477 fields(
478 parent = ?input.parent_hash(),
479 type_name = ?input.type_name(),
480 )
481 )]
482 pub fn validate_block_with_state<T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
483 &mut self,
484 input: BlockOrPayload<T>,
485 mut ctx: TreeCtx<'_, N>,
486 ) -> InsertPayloadResult<N>
487 where
488 V: PayloadValidator<T, Block = N::Block> + Clone,
489 Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
490 {
491 let parent_hash = input.parent_hash();
492 let _txpool_pause = self.txpool_prewarm.as_ref().map(txpool_prewarm::Handle::pause);
493 let txpool_snapshot =
494 self.txpool_prewarm.as_ref().and_then(|prewarmer| prewarmer.snapshot(parent_hash));
495 let _jit_pause = JitPauseGuard::new(&self.evm_config);
496
497 let parent_block = match self.sealed_header_by_hash(parent_hash, ctx.state()) {
500 Ok(Some(parent_block)) => parent_block,
501 Ok(None) => {
502 return Err(InsertBlockError::new(
503 self.convert_to_block(input)?,
504 ProviderError::HeaderNotFound(parent_hash.into()).into(),
505 )
506 .into())
507 }
508 Err(e) => {
509 return Err(InsertBlockError::new(self.convert_to_block(input)?, e.into()).into())
510 }
511 };
512
513 let validated_block = self.spawn_convert_and_validate(&input, parent_block.clone());
517
518 macro_rules! ensure_ok {
521 ($expr:expr) => {
522 match $expr {
523 Ok(val) => val,
524 Err(e) => {
525 let block = validated_block.try_into_inner().expect("sole handle")?;
526 return Err(InsertBlockError::new(block, e.into()).into())
527 }
528 }
529 };
530 }
531
532 macro_rules! ensure_ok_post_block {
534 ($expr:expr, $block:expr) => {
535 match $expr {
536 Ok(val) => val,
537 Err(e) => {
538 return Err(
539 InsertBlockError::new($block.into_sealed_block(), e.into()).into()
540 )
541 }
542 }
543 };
544 }
545
546 if input.gas_limit() >
549 parent_block.gas_limit().saturating_mul(MAX_EXPECTED_GAS_LIMIT_MULTIPLIER)
550 {
551 if validated_block.get().is_err() {
553 return Err(validated_block
554 .try_into_inner()
555 .expect("sole handle")
556 .expect_err("Err result checked"))
557 }
558 }
559
560 trace!(target: "engine::tree::payload_validator", "Fetching block state provider");
561 let _enter =
562 debug_span!(target: "engine::tree::payload_validator", "state_provider").entered();
563 let Some(state_provider_factory) =
564 ensure_ok!(self.overlay_state_provider_factory(parent_hash, ctx.state()))
565 else {
566 return Err(InsertBlockError::new(
568 validated_block.try_into_inner().expect("sole handle")?,
569 ProviderError::HeaderNotFound(parent_hash.into()).into(),
570 )
571 .into())
572 };
573 drop(_enter);
574
575 let evm_env = debug_span!(target: "engine::tree::payload_validator", "evm_env")
576 .in_scope(|| self.evm_env_for(&input))
577 .map_err(NewPayloadError::other)?;
578 let pool_seeds = self
579 .txpool_prewarm
580 .as_ref()
581 .and_then(|prewarmer| prewarmer.seeds(parent_hash, &evm_env));
582
583 let decoded_bal =
586 ensure_ok!(input.try_decoded_access_list().map_err(BlockAccessListDecodeError::new))
587 .map(Arc::new);
588
589 if let Some(decoded_bal) = decoded_bal.as_deref() {
590 ensure_ok!(decoded_bal
592 .as_bal()
593 .validate_gas_limit(input.gas_limit())
594 .map_err(ConsensusError::from));
595 }
596
597 let env = ExecutionEnv {
598 evm_env,
599 hash: input.hash(),
600 parent_hash: input.parent_hash(),
601 parent_state_root: parent_block.state_root(),
602 transaction_count: input.transaction_count(),
603 gas_used: input.gas_used(),
604 withdrawals: input.withdrawals().map(|w| w.to_vec()),
605 decoded_bal: decoded_bal.as_ref().map(Arc::clone),
606 txpool_snapshot: txpool_snapshot.clone(),
607 pool_seeds,
608 };
609
610 let txs = self.tx_iterator_for(&input)?;
612
613 let parallel_bal_execution = ensure_ok!(self.bal_path_eligible(env.decoded_bal.as_deref()));
614
615 let mut state_root_job =
617 ensure_ok!(self.state_root_strategy.prepare(StateRootJobContext::new(
618 &self.runtime,
619 &self.overlay_manager,
620 &env,
621 &parent_block,
622 state_provider_factory.clone(),
623 &self.config,
624 parallel_bal_execution,
625 ctx.state_mut(),
626 )));
627 let state_root_job_name = state_root_job.name();
628
629 debug!(
630 target: "engine::tree::payload_validator",
631 strategy = state_root_job_name,
632 "Prepared state root job"
633 );
634
635 let execution_state_hook = state_root_job.take_execution_hook();
638 let hint_stream = state_root_job.take_hint_stream();
641 let hashed_update_stream = state_root_job.take_hashed_update_stream();
642
643 let mut handle = ensure_ok!(self.spawn_payload_processor(
645 env.clone(),
646 txs,
647 state_provider_factory.clone(),
648 hint_stream,
649 hashed_update_stream,
650 parallel_bal_execution,
651 ));
652
653 let slow_block_enabled = self.config.slow_block_threshold().is_some();
655 let cache_stats = slow_block_enabled.then(|| Arc::new(CacheStats::default()));
656 let instrument_state_provider = slow_block_enabled || self.config.state_provider_metrics();
657 let state_provider_metrics =
658 instrument_state_provider.then(|| StateProviderMetrics::with_source("engine"));
659 let state_provider_stats =
660 instrument_state_provider.then(|| Arc::new(StateProviderStats::default()));
661 let execution_cache = handle.caches().map(|caches| (caches, handle.cache_metrics()));
662
663 let make_state_provider = |fill_on_miss: bool| -> ProviderResult<EvmStateProviderBox> {
683 let provider = state_provider_factory.database_provider_ro()?.into_evm_state_provider();
684 let provider: EvmStateProviderBox =
685 if let Some((caches, cache_metrics)) = &execution_cache {
686 let fill_mode = if fill_on_miss {
687 CacheFillMode::FillOnMiss
688 } else {
689 CacheFillMode::LookupOnly
690 };
691 Box::new(
692 CachedStateProvider::new_with_mode(
693 provider,
694 caches.clone(),
695 fill_mode,
696 cache_metrics.clone(),
697 cache_stats.clone(),
698 )
699 .with_txpool_snapshot(txpool_snapshot.clone()),
700 )
701 } else {
702 Box::new(provider)
703 };
704
705 let provider: EvmStateProviderBox = if instrument_state_provider {
706 let stats = state_provider_stats
707 .as_ref()
708 .expect("instrumented state provider requires shared stats");
709 let metrics = state_provider_metrics
710 .as_ref()
711 .expect("instrumented state provider requires metrics");
712 Box::new(InstrumentedStateProvider::with_stats(
713 provider,
714 metrics.clone(),
715 Arc::clone(stats),
716 ))
717 } else {
718 provider
719 };
720
721 Ok(provider)
722 };
723
724 let execute_block_start = Instant::now();
728 let execution_result = if parallel_bal_execution {
729 self.execute_block_bal(env, &input, &handle, &make_state_provider)
730 } else {
731 let state_provider = make_state_provider(false);
732 match state_provider {
733 Ok(state_provider) => self.execute_block(
734 state_provider,
735 env,
736 &input,
737 &mut handle,
738 execution_state_hook,
739 ),
740 Err(err) => Err(err.into()),
741 }
742 };
743 let execution_duration = execute_block_start.elapsed();
744 if let (Some(metrics), Some(stats)) = (&state_provider_metrics, &state_provider_stats) {
745 metrics.record_totals(stats);
746 }
747 let (output, senders, receipt_root_rx, executed_bal) = ensure_ok!(execution_result);
748 let (built_bal, revm_bal) =
749 executed_bal.map(|ExecutedBal { alloy, revm }| (alloy, revm)).unzip();
750
751 handle.stop_prewarming_execution();
753
754 let output = Arc::new(output);
758
759 let valid_block_tx = handle.terminate_caching(Some(output.clone()));
762
763 let hashed_state_output = output.clone();
767 let mut hashed_state_rx = state_root_job.take_hashed_state_rx();
768 let parent_span = Span::current();
769 let mut hashed_state: LazyHashedPostState =
770 self.runtime.spawn_blocking_named("hash-post-state", move || {
771 let _span = debug_span!(
772 target: "engine::tree::payload_validator",
773 parent: parent_span,
774 "hashed_post_state",
775 )
776 .entered();
777 if let Some(Ok(state)) = hashed_state_rx.as_mut().map(|rx| rx.recv()) {
778 state
779 } else {
780 Arc::new(HashedPostState::from_bundle_state::<KeccakKeyHasher>(
781 hashed_state_output.state.state(),
782 ))
783 }
784 });
785
786 let block = validated_block.try_into_inner().expect("sole handle")?;
787 let block = block.with_senders(senders);
788
789 let receipt_root_bloom = {
791 let _enter = debug_span!(
792 target: "engine::tree::payload_validator",
793 "wait_receipt_root",
794 )
795 .entered();
796
797 receipt_root_rx
798 .blocking_recv()
799 .inspect_err(|_| {
800 tracing::error!(
801 target: "engine::tree::payload_validator",
802 "Receipt root task dropped sender without result, receipt root calculation likely aborted"
803 );
804 })
805 .ok()
806 };
807
808 ensure_ok_post_block!(
809 self.validate_post_execution(
810 &block,
811 &parent_block,
812 &output,
813 &mut ctx,
814 receipt_root_bloom,
815 built_bal
816 ),
817 block
818 );
819
820 let mut hashed_state_validate_result = debug_span!(
821 target: "engine::tree::payload_validator",
822 "validate_block_post_execution_with_hashed_state"
823 )
824 .in_scope(|| {
825 self.validator.validate_block_post_execution_with_hashed_state(
826 || hashed_state.get(),
827 &block,
828 &parent_block,
829 || {
830 state_provider_factory
831 .database_provider_ro()
832 .map(|provider| Box::new(provider) as _)
833 },
834 )
835 });
836
837 let root_start = Instant::now();
838 let root_outcome = ensure_ok_post_block!(
839 state_root_job.finish(&block, output.clone(), &hashed_state),
840 block
841 );
842 let root_elapsed = root_start.elapsed();
843
844 info!(
845 target: "engine::tree::payload_validator",
846 strategy = state_root_job_name,
847 state_root = ?root_outcome.state_root,
848 elapsed = ?root_elapsed,
849 "State root job finished"
850 );
851
852 let state_root = root_outcome.state_root;
853 let trie_output = root_outcome.trie_updates;
854
855 if let Some(refreshed) = root_outcome.hashed_state {
859 hashed_state = LazyHandle::ready(refreshed);
860 hashed_state_validate_result = debug_span!(
861 target: "engine::tree::payload_validator",
862 "validate_block_post_execution_with_hashed_state"
863 )
864 .in_scope(|| {
865 self.validator.validate_block_post_execution_with_hashed_state(
866 || hashed_state.get(),
867 &block,
868 &parent_block,
869 || {
870 state_provider_factory
871 .database_provider_ro()
872 .map(|provider| Box::new(provider) as _)
873 },
874 )
875 });
876 }
877
878 if let Err(err) = hashed_state_validate_result {
879 if err.is_validation_error() {
880 self.on_invalid_block(&parent_block, &block, &output, None, ctx.state_mut());
881 }
882 return Err(InsertBlockError::new(block.into_sealed_block(), err).into())
883 }
884
885 self.metrics.block_validation.record_state_root(&trie_output, root_elapsed.as_secs_f64());
886 self.metrics
887 .record_state_root_gas_bucket(block.header().gas_used(), root_elapsed.as_secs_f64());
888 debug!(target: "engine::tree::payload_validator", ?root_elapsed, "Calculated state root");
889
890 if state_root != block.header().state_root() {
892 self.on_invalid_block(
894 &parent_block,
895 &block,
896 &output,
897 Some((&trie_output, state_root)),
898 ctx.state_mut(),
899 );
900 let block_state_root = block.header().state_root();
901 return Err(InsertBlockError::new(
902 block.into_sealed_block(),
903 ConsensusError::BodyStateRootDiff(
904 GotExpected { got: state_root, expected: block_state_root }.into(),
905 )
906 .into(),
907 )
908 .into())
909 }
910
911 let timing_stats = state_provider_stats.filter(|_| slow_block_enabled).map(|stats| {
912 self.calculate_timing_stats(
913 &block,
914 stats,
915 cache_stats,
916 &output,
917 execution_duration,
918 root_elapsed,
919 )
920 });
921
922 if let Some(valid_block_tx) = valid_block_tx {
923 let _ = valid_block_tx.send(());
924 }
925
926 let bal = revm_bal.zip(decoded_bal).map(|(revm_bal, decoded_bal)| {
931 Arc::new(DecodedRevmBal::with_raw_bal(revm_bal, decoded_bal.as_raw_bal().clone()))
932 });
933 let executed_block = self
934 .spawn_deferred_trie_task(Arc::new(block), output, hashed_state, trie_output)
935 .with_bal(bal);
936 Ok(ValidationOutput::new(executed_block, timing_stats))
937 }
938
939 #[expect(clippy::type_complexity)]
942 pub fn spawn_convert_and_validate<T>(
943 &self,
944 input: &BlockOrPayload<T>,
945 parent: SealedHeader<N::BlockHeader>,
946 ) -> LazyHandle<Result<SealedBlock<N::Block>, InsertPayloadError<N::Block>>>
947 where
948 T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>,
949 V: PayloadValidator<T, Block = N::Block> + Clone,
950 {
951 let input = input.clone();
952 let validator = self.validator.clone();
953 let consensus = self.consensus.clone();
954 let parent_span = Span::current();
955 self.runtime.spawn_blocking_named("payload-convert", move || {
956 let _span = debug_span!(
957 target: "engine::tree::payload_validator",
958 parent: parent_span,
959 "convert_and_validate",
960 )
961 .entered();
962 let block = match input {
963 BlockOrPayload::Block(block) => block.split().0,
964 BlockOrPayload::Payload(payload) => {
965 validator.convert_payload_to_block(payload)?
966 }
967 };
968
969 if let Err(e) = consensus.validate_header(block.sealed_header()) {
970 error!(target: "engine::tree::payload_validator", block = ?block.block_with_parent(), %e, "Failed to validate header");
971 return Err(InsertBlockError::consensus_error(e, block).into())
972 }
973
974 let _enter = debug_span!(target: "engine::tree::payload_validator", "validate_header_against_parent").entered();
976 if let Err(e) = consensus.validate_header_against_parent(block.sealed_header(), &parent)
977 {
978 warn!(target: "engine::tree::payload_validator", block = ?block.block_with_parent(), %e, "Failed to validate header against parent");
979 return Err(InsertBlockError::consensus_error(e, block).into())
980 }
981 drop(_enter);
982
983 if let Err(e) =
984 consensus.validate_block_pre_execution_with_tx_root(&block, None)
985 {
986 error!(target: "engine::tree::payload_validator", block = ?block.block_with_parent(), %e, "Failed to validate block");
987 return Err(InsertBlockError::consensus_error(e, block).into())
988 }
989
990 Ok(block)
991 })
992 }
993
994 fn sealed_header_by_hash(
996 &self,
997 hash: B256,
998 state: &EngineApiTreeState<N>,
999 ) -> ProviderResult<Option<SealedHeader<N::BlockHeader>>> {
1000 let header = state.tree_state.sealed_header_by_hash(&hash);
1002
1003 if header.is_some() {
1004 Ok(header)
1005 } else {
1006 self.provider.sealed_header_by_hash(hash)
1007 }
1008 }
1009
1010 #[instrument(level = "debug", target = "engine::tree::payload_validator", skip_all)]
1018 #[expect(clippy::type_complexity)]
1019 fn execute_block<S, Err, T>(
1020 &mut self,
1021 state_provider: S,
1022 env: ExecutionEnv<Evm>,
1023 input: &BlockOrPayload<T>,
1024 handle: &mut PayloadHandle<impl ExecutableTxFor<Evm>, Err, N::Receipt, HaltReasonFor<Evm>>,
1025 state_hook: Option<Box<dyn OnStateHook + 'static>>,
1026 ) -> Result<
1027 (BlockExecutionOutput<N::Receipt>, Vec<Address>, ReceiptRootReceiver, Option<ExecutedBal>),
1028 InsertBlockErrorKind,
1029 >
1030 where
1031 S: EvmStateProvider + Send,
1032 Err: core::error::Error + Send + Sync + 'static,
1033 V: PayloadValidator<T, Block = N::Block>,
1034 T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>,
1035 Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
1036 {
1037 debug!(target: "engine::tree::payload_validator", "Executing block");
1038
1039 let has_bal = input.has_block_access_list();
1040 let mut db = debug_span!(target: "engine::tree", "build_state_db").in_scope(|| {
1041 State::builder()
1042 .with_database(StateProviderDatabase::new(state_provider))
1043 .with_bundle_update()
1044 .with_bal_builder_if(has_bal)
1045 .build()
1046 });
1047
1048 let (spec_id, mut executor) = {
1049 let _span = debug_span!(target: "engine::tree", "create_evm").entered();
1050 let spec_id = *env.evm_env.spec_id();
1051 let evm_config = self.evm_config.clone().with_jit_support();
1052 let evm = evm_config.evm_with_env(&mut db, env.evm_env);
1053 let ctx = self
1054 .execution_ctx_for(input)
1055 .map_err(|e| InsertBlockErrorKind::Other(Box::new(e)))?;
1056 let executor = self.evm_config.create_executor(evm, ctx);
1057 (spec_id, executor)
1058 };
1059
1060 if !self.config.precompile_cache_disabled() {
1061 let _span = debug_span!(target: "engine::tree", "setup_precompile_cache").entered();
1062 executor.evm_mut().precompiles_mut().map_cacheable_precompiles(
1063 |address, precompile| {
1064 let metrics = self
1065 .precompile_cache_metrics
1066 .entry(*address)
1067 .or_insert_with(|| CachedPrecompileMetrics::new_with_address(*address))
1068 .clone();
1069 CachedPrecompile::wrap(
1070 precompile,
1071 self.precompile_cache_map.cache_for_address(*address),
1072 spec_id,
1073 Some(metrics),
1074 )
1075 },
1076 );
1077 }
1078
1079 let transaction_count = input.transaction_count();
1080 let (receipt_tx, result_rx) = self.spawn_receipt_root_task(transaction_count);
1081 let executed_tx_index = Arc::clone(handle.executed_tx_index());
1082 let prewarm_results = handle.prewarm_results().filter(|_| !has_bal);
1084 executor.evm_mut().db_mut().set_state_hook(state_hook);
1085
1086 let execution_start = Instant::now();
1087
1088 let (executor, senders) = self.execute_transactions(
1090 executor,
1091 transaction_count,
1092 handle.iter_transactions(),
1093 &receipt_tx,
1094 &executed_tx_index,
1095 has_bal,
1096 prewarm_results.as_deref(),
1097 )?;
1098 drop(receipt_tx);
1099 if let Some(prewarm_results) = prewarm_results {
1101 self.runtime.spawn_drop(prewarm_results);
1102 }
1103
1104 let post_exec_start = Instant::now();
1106 let (_evm, result) = debug_span!(target: "engine::tree", "BlockExecutor::finish")
1107 .in_scope(|| executor.finish())
1108 .map(|(evm, result)| (evm.into_db(), result))?;
1109 self.metrics.record_post_execution(post_exec_start.elapsed());
1110
1111 debug_span!(target: "engine::tree", "merge_transitions")
1113 .in_scope(|| db.merge_transitions(BundleRetention::Reverts));
1114
1115 let built_bal = db.take_built_bal().map(|revm_bal| ExecutedBal {
1119 alloy: revm_bal.clone().into_alloy_bal(),
1120 revm: Arc::new(revm_bal),
1121 });
1122 let output = BlockExecutionOutput { result, state: db.take_bundle() };
1123
1124 let execution_duration = execution_start.elapsed();
1125 self.metrics.record_block_execution(&output, execution_duration);
1126 self.metrics.record_block_execution_gas_bucket(output.result.gas_used, execution_duration);
1127 debug!(target: "engine::tree::payload_validator", elapsed = ?execution_duration, "Executed block");
1128
1129 Ok((output, senders, result_rx, built_bal))
1130 }
1131
1132 fn bal_path_eligible(&self, bal: Option<&DecodedBal>) -> Result<bool, InsertBlockErrorKind> {
1141 let has_bal = bal.is_some();
1142 let parallel_execution = has_bal && !self.config.disable_bal_parallel_execution();
1143 if parallel_execution && self.config.disable_bal_parallel_state_root() {
1144 return Err(InsertBlockErrorKind::Other(
1145 "disabling parallel state root is impossible when parallel execution is enabled"
1146 .into(),
1147 ));
1148 }
1149
1150 Ok(parallel_execution)
1151 }
1152
1153 #[instrument(level = "debug", target = "engine::tree::payload_validator", skip_all)]
1164 #[expect(clippy::type_complexity)]
1165 fn execute_block_bal<Tx, Err, MakeStateProvider, T>(
1166 &self,
1167 env: ExecutionEnv<Evm>,
1168 input: &BlockOrPayload<T>,
1169 handle: &PayloadHandle<Tx, Err, N::Receipt, HaltReasonFor<Evm>>,
1170 make_state_provider: &MakeStateProvider,
1171 ) -> Result<
1172 (BlockExecutionOutput<N::Receipt>, Vec<Address>, ReceiptRootReceiver, Option<ExecutedBal>),
1173 InsertBlockErrorKind,
1174 >
1175 where
1176 Tx: ExecutableTxFor<Evm> + Send,
1177 Err: core::error::Error + Send + Sync + 'static,
1178 MakeStateProvider: Fn(bool) -> ProviderResult<EvmStateProviderBox> + Sync,
1179 Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
1180 T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>,
1181 V: PayloadValidator<T, Block = N::Block>,
1182 {
1183 debug!(target: "engine::tree::payload_validator", "Executing block via BAL path");
1184
1185 let (receipt_tx, result_rx) = self.spawn_receipt_root_task(env.transaction_count);
1186 let input_bal = env.decoded_bal.ok_or_else(|| {
1187 InsertBlockErrorKind::Other("BAL execute path: no decoded BAL available".into())
1188 })?;
1189
1190 let make_db = |fill_on_miss| {
1191 let provider = make_state_provider(fill_on_miss)
1192 .map_err(crate::tree::payload_processor::bal::BalExecutionError::Provider)?;
1193 Ok(StateProviderDatabase::new(provider))
1194 };
1195 let execution_start = Instant::now();
1196 let ctx =
1197 self.execution_ctx_for(input).map_err(|e| InsertBlockErrorKind::Other(Box::new(e)))?;
1198 let (output, senders, built_bal, received_bal_revm) =
1199 crate::tree::payload_processor::bal::execute_block(
1200 &self.runtime,
1201 &self.evm_config,
1202 &make_db,
1203 input_bal,
1204 env.evm_env,
1205 ctx,
1206 env.transaction_count,
1207 handle.clone_transaction_receiver(),
1208 receipt_tx,
1209 )?;
1210 let execution_duration = execution_start.elapsed();
1211
1212 self.metrics.record_block_execution(&output, execution_duration);
1213 self.metrics.record_block_execution_gas_bucket(output.result.gas_used, execution_duration);
1214 debug!(
1215 target: "engine::tree::payload_validator",
1216 elapsed = ?execution_duration,
1217 "Executed block via BAL path",
1218 );
1219
1220 Ok((
1224 output,
1225 senders,
1226 result_rx,
1227 Some(ExecutedBal { alloy: built_bal, revm: received_bal_revm }),
1228 ))
1229 }
1230
1231 fn spawn_receipt_root_task(
1232 &self,
1233 receipts_len: usize,
1234 ) -> (ReceiptRootSender<N>, ReceiptRootReceiver) {
1235 let (receipt_tx, receipt_rx) = crossbeam_channel::unbounded();
1237 let (result_tx, result_rx) = tokio::sync::oneshot::channel();
1238 let task_handle = ReceiptRootTaskHandle::new(receipt_rx, result_tx);
1239 self.runtime.spawn_blocking_named("receipt-root", move || task_handle.run(receipts_len));
1240
1241 (receipt_tx, result_rx)
1242 }
1243
1244 #[expect(clippy::too_many_arguments)]
1254 fn execute_transactions<'a, E, Tx, Err, DB>(
1255 &self,
1256 mut executor: E,
1257 transaction_count: usize,
1258 transactions: impl Iterator<Item = Result<Tx, Err>>,
1259 receipt_tx: &crossbeam_channel::Sender<IndexedReceipt<N::Receipt>>,
1260 executed_tx_index: &AtomicUsize,
1261 has_bal: bool,
1262 prewarm_results: Option<&PrewarmResults<HaltReasonFor<Evm>>>,
1263 ) -> Result<(E, Vec<Address>), BlockExecutionError>
1264 where
1265 E: BlockExecutor<
1266 Receipt = N::Receipt,
1267 Result = TxExecutionResultFor<Evm>,
1268 Evm: alloy_evm::Evm<DB = &'a mut State<DB>, HaltReason = HaltReasonFor<Evm>>,
1269 >,
1270 Tx: alloy_evm::block::ExecutableTx<E> + alloy_evm::RecoveredTx<TxTy<N>>,
1271 DB: revm::Database + 'a,
1272 Err: core::error::Error + Send + Sync + 'static,
1273 {
1274 let mut senders = Vec::with_capacity(transaction_count);
1275 let mut reuse_stats = PrewarmReuseStats::default();
1276
1277 let pre_exec_start = Instant::now();
1279 debug_span!(target: "engine::tree", "pre_execution")
1280 .in_scope(|| executor.apply_pre_execution_changes())?;
1281 self.metrics.record_pre_execution(pre_exec_start.elapsed());
1282
1283 if has_bal {
1285 executor.evm_mut().db_mut().bump_bal_index();
1286 }
1287
1288 let exec_span = debug_span!(target: "engine::tree", "execution").entered();
1290 let mut transactions = transactions.into_iter();
1291 let mut last_sent_len = 0usize;
1296 loop {
1297 let wait_start = Instant::now();
1300 let Some(tx_result) = transactions.next() else { break };
1301 self.metrics.record_transaction_wait(wait_start.elapsed());
1302
1303 let tx = tx_result.map_err(BlockValidationError::other)?;
1304 let tx_signer = *<Tx as alloy_evm::RecoveredTx<TxTy<N>>>::signer(&tx);
1305
1306 senders.push(tx_signer);
1307
1308 let _enter = tracing::enabled!(target: "engine::tree", Level::TRACE).then(|| {
1309 tracing::trace_span!(
1310 target: "engine::tree",
1311 "execute tx",
1312 tx_index = senders.len() - 1,
1313 )
1314 .entered()
1315 });
1316 if tracing::enabled!(target: "engine::tree", Level::TRACE) {
1317 trace!(target: "engine::tree", "Executing transaction");
1318 }
1319
1320 let tx_start = Instant::now();
1321 let index = senders.len() - 1;
1322 match prewarm_results {
1323 Some(results) => {
1324 let outcome = self.commit_prewarm_result(
1325 &mut executor,
1326 results,
1327 index,
1328 <Tx as alloy_evm::RecoveredTx<TxTy<N>>>::tx(&tx),
1329 )?;
1330 if outcome.is_err() {
1331 executor.execute_transaction_with_result_closure(tx, |result| {
1333 results.publish_executed(index, &result.result().state)
1334 })?;
1335 }
1336 reuse_stats.record(outcome);
1337 }
1338 None => {
1339 executor.execute_transaction(tx)?;
1340 }
1341 }
1342 self.metrics.record_transaction_execution(tx_start.elapsed());
1343
1344 executed_tx_index.store(senders.len(), Ordering::Relaxed);
1346
1347 let current_len = executor.receipts().len();
1348 if current_len > last_sent_len {
1349 last_sent_len = current_len;
1350 if let Some(receipt) = executor.receipts().last() {
1352 let tx_index = current_len - 1;
1353 let _ = receipt_tx.send(IndexedReceipt::new(tx_index, receipt.clone()));
1354 }
1355 }
1356 if has_bal {
1358 executor.evm_mut().db_mut().bump_bal_index();
1359 }
1360 }
1361
1362 drop(exec_span);
1363
1364 if prewarm_results.is_some() {
1365 self.metrics.prewarm_reuse.record(reuse_stats);
1366 }
1367
1368 Ok((executor, senders))
1369 }
1370
1371 fn commit_prewarm_result<'a, E, DB>(
1374 &self,
1375 executor: &mut E,
1376 results: &PrewarmResults<HaltReasonFor<Evm>>,
1377 index: usize,
1378 tx: &TxTy<N>,
1379 ) -> Result<Result<(), ReuseMiss>, BlockExecutionError>
1380 where
1381 E: BlockExecutor<
1382 Receipt = N::Receipt,
1383 Result = TxExecutionResultFor<Evm>,
1384 Evm: alloy_evm::Evm<DB = &'a mut State<DB>, HaltReason = HaltReasonFor<Evm>>,
1385 >,
1386 DB: revm::Database + 'a,
1387 {
1388 let block = executor.evm().block();
1391 let block_gas_used = executor.receipts().last().map_or(0, |r| r.cumulative_gas_used());
1392 if tx.gas_limit() > block.gas_limit().saturating_sub(block_gas_used) {
1393 return Ok(Err(ReuseMiss::Stale))
1394 }
1395
1396 let result = match results
1397 .take_valid(index, executor.evm_mut().db_mut())
1398 .map_err(BlockExecutionError::other)?
1399 {
1400 Ok(result) => result,
1401 Err(miss) => return Ok(Err(miss)),
1402 };
1403 let Some(result) = self.evm_config.prewarm_tx_result(tx, result) else {
1404 return Ok(Err(ReuseMiss::Stale))
1405 };
1406 executor.commit_transaction(result);
1407 Ok(Ok(()))
1408 }
1409
1410 #[instrument(level = "debug", target = "engine::tree::payload_validator", skip_all)]
1422 fn validate_post_execution<T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
1423 &mut self,
1424 block: &RecoveredBlock<N::Block>,
1425 parent_block: &SealedHeader<N::BlockHeader>,
1426 output: &BlockExecutionOutput<N::Receipt>,
1427 ctx: &mut TreeCtx<'_, N>,
1428 receipt_root_bloom: Option<ReceiptRootBloom>,
1429 built_bal: Option<BlockAccessList>,
1430 ) -> Result<(), InsertBlockErrorKind>
1431 where
1432 V: PayloadValidator<T, Block = N::Block>,
1433 {
1434 let start = Instant::now();
1435
1436 trace!(target: "engine::tree::payload_validator", block=?block.num_hash(), "Validating block consensus");
1437
1438 let _enter =
1440 debug_span!(target: "engine::tree::payload_validator", "validate_block_post_execution")
1441 .entered();
1442 let built_bal = built_bal.map(Bal::from);
1443 let block_access_list_hash =
1444 built_bal.as_ref().map(|bal| bal.compute_hash_with_buf(&mut self.bal_hash_buf));
1445
1446 let validation_result = built_bal
1447 .as_ref()
1448 .map(|bal| bal.validate_gas_limit(block.gas_limit()).map_err(ConsensusError::from))
1449 .transpose()
1450 .and_then(|_| {
1451 self.consensus.validate_block_post_execution(
1452 block,
1453 output,
1454 receipt_root_bloom,
1455 block_access_list_hash,
1456 )
1457 });
1458
1459 if let Err(err) = validation_result {
1460 self.on_invalid_block(parent_block, block, output, None, ctx.state_mut());
1462 return Err(err.into())
1463 }
1464 drop(_enter);
1465
1466 self.metrics
1468 .block_validation
1469 .post_execution_validation_duration
1470 .record(start.elapsed().as_secs_f64());
1471
1472 Ok(())
1473 }
1474
1475 #[instrument(
1480 level = "debug",
1481 target = "engine::tree::payload_validator",
1482 skip_all,
1483 fields(
1484 has_hint_stream = hint_stream.is_some(),
1485 has_hashed_update_stream = hashed_update_stream.is_some(),
1486 parallel_bal_execution
1487 )
1488 )]
1489 fn spawn_payload_processor<T: ExecutableTxIterator<Evm>>(
1490 &self,
1491 env: ExecutionEnv<Evm>,
1492 txs: T,
1493 state_provider_factory: OverlayStateProviderFactory<P, N>,
1494 hint_stream: Option<StateRootHintStream>,
1495 hashed_update_stream: Option<StateRootUpdateStream>,
1496 parallel_bal_execution: bool,
1497 ) -> Result<
1498 PayloadHandle<
1499 impl ExecutableTxFor<Evm> + use<N, P, Evm, V, T>,
1500 impl core::error::Error + Send + Sync + 'static + use<N, P, Evm, V, T>,
1501 N::Receipt,
1502 HaltReasonFor<Evm>,
1503 >,
1504 InsertBlockErrorKind,
1505 > {
1506 let start = Instant::now();
1507 let handle = self.payload_processor.spawn_with_state_root_streams(
1508 env,
1509 txs,
1510 state_provider_factory,
1511 hint_stream,
1512 hashed_update_stream,
1513 parallel_bal_execution,
1514 );
1515
1516 self.metrics.block_validation.spawn_payload_processor.record(start.elapsed().as_secs_f64());
1517
1518 Ok(handle)
1519 }
1520
1521 fn overlay_state_provider_factory(
1525 &self,
1526 hash: B256,
1527 state: &EngineApiTreeState<N>,
1528 ) -> ProviderResult<Option<OverlayStateProviderFactory<P, N>>> {
1529 if !state.tree_state.contains_hash(&hash) && self.provider.header(hash)?.is_none() {
1530 debug!(target: "engine::tree::payload_validator", %hash, "no canonical state found for block");
1531 return Ok(None)
1532 }
1533
1534 Ok(Some(OverlayStateProviderFactory::new(
1535 self.provider.clone(),
1536 state.tree_state.overlay_manager.overlay_builder(hash),
1537 )))
1538 }
1539
1540 fn on_invalid_block(
1542 &self,
1543 parent_header: &SealedHeader<N::BlockHeader>,
1544 block: &RecoveredBlock<N::Block>,
1545 output: &BlockExecutionOutput<N::Receipt>,
1546 trie_updates: Option<(&TrieUpdates, B256)>,
1547 state: &mut EngineApiTreeState<N>,
1548 ) {
1549 if state.invalid_headers.get(&block.hash()).is_some() {
1550 return
1552 }
1553 self.invalid_block_hook.on_invalid_block(parent_header, block, output, trie_updates);
1554 }
1555
1556 fn payload_state_root_handle_for(
1559 &self,
1560 parent_hash: B256,
1561 parent_header: &N::BlockHeader,
1562 timestamp: u64,
1563 state: &mut EngineApiTreeState<N>,
1564 ) -> Option<PayloadStateRootHandle> {
1565 let state_provider_factory = match self.overlay_state_provider_factory(parent_hash, state) {
1566 Ok(Some(state_provider_factory)) => state_provider_factory,
1567 Ok(None) => return None,
1568 Err(err) => {
1569 warn!(
1570 target: "engine::tree::payload_validator",
1571 %err,
1572 %parent_hash,
1573 "failed to prepare payload-builder state-root provider"
1574 );
1575 return None
1576 }
1577 };
1578 match self.state_root_strategy.prepare_payload_builder(PayloadStateRootJobContext::new(
1579 &self.runtime,
1580 &self.overlay_manager,
1581 parent_hash,
1582 parent_header,
1583 timestamp,
1584 state,
1585 state_provider_factory,
1586 &self.config,
1587 )) {
1588 Ok(handle) => handle,
1589 Err(err) => {
1590 warn!(
1591 target: "engine::tree::payload_validator",
1592 %err,
1593 %parent_hash,
1594 "failed to prepare payload-builder state-root job"
1595 );
1596 None
1597 }
1598 }
1599 }
1600
1601 fn spawn_deferred_trie_task(
1614 &self,
1615 block: Arc<RecoveredBlock<N::Block>>,
1616 execution_outcome: Arc<BlockExecutionOutput<N::Receipt>>,
1617 hashed_state: LazyHashedPostState,
1618 trie_output: Arc<TrieUpdates>,
1619 ) -> ExecutedBlock<N> {
1620 let hashed_state = match hashed_state.try_into_inner() {
1624 Ok(state) => state,
1625 Err(handle) => handle.get().clone(),
1626 };
1627 let (deferred_trie_data, deferred_trie_task) =
1628 LazyTrieData::pending(hashed_state, trie_output);
1629 let block_validation_metrics = self.metrics.block_validation.clone();
1630
1631 let block_number = block.number();
1633
1634 let compute_trie_input_task = move || {
1636 let _span = debug_span!(
1637 target: "engine::tree::payload_validator",
1638 "compute_trie_input_task",
1639 block_number
1640 )
1641 .entered();
1642
1643 let compute_start = Instant::now();
1644 let computed = deferred_trie_task.compute_and_publish();
1645 block_validation_metrics
1646 .deferred_trie_compute_duration
1647 .record(compute_start.elapsed().as_secs_f64());
1648
1649 block_validation_metrics
1651 .hashed_post_state_size
1652 .record(computed.hashed_state.total_len() as f64);
1653 block_validation_metrics
1654 .trie_updates_sorted_size
1655 .record(computed.trie_updates.total_len() as f64);
1656 };
1657
1658 self.runtime.spawn_blocking_named(DEFERRED_TRIE_WORKER_NAME, compute_trie_input_task);
1660
1661 ExecutedBlock::with_deferred_trie_data(block, execution_outcome, deferred_trie_data)
1662 }
1663
1664 fn calculate_timing_stats(
1665 &self,
1666 block: &RecoveredBlock<N::Block>,
1667 provider_stats: Arc<StateProviderStats>,
1668 cache_stats: Option<Arc<CacheStats>>,
1669 output: &BlockExecutionOutput<N::Receipt>,
1670 execution_duration: Duration,
1671 state_hash_duration: Duration,
1672 ) -> Box<ExecutionTimingStats> {
1673 let accounts_read = provider_stats.total_account_fetches();
1674 let storage_read = provider_stats.total_storage_fetches();
1675 let code_read = provider_stats.total_code_fetches();
1676 let code_bytes_read = provider_stats.total_code_fetched_bytes();
1677
1678 let accounts_changed = output.state.len();
1680 let accounts_deleted =
1681 output.state.state.values().filter(|acc| acc.was_destroyed()).count();
1682 let storage_slots_changed =
1683 output.state.state.values().map(|account| account.storage.len()).sum::<usize>();
1684 let storage_slots_deleted = output
1685 .state
1686 .state
1687 .values()
1688 .flat_map(|account| account.storage.values())
1689 .filter(|slot| {
1690 slot.present_value.is_zero() && !slot.previous_or_original_value.is_zero()
1691 })
1692 .count();
1693
1694 let is_new_deployment = |acc: &BundleAccount| -> bool {
1696 let has_code_now = acc.info.as_ref().is_some_and(|info| !info.is_empty_code_hash());
1697 let had_no_code_before =
1698 acc.original_info.as_ref().is_none_or(AccountInfo::is_empty_code_hash);
1699 has_code_now && had_no_code_before
1700 };
1701
1702 let bytecodes_changed =
1703 output.state.state.values().filter(|acc| is_new_deployment(acc)).count();
1704
1705 let unique_new_code_hashes: B256Set = output
1707 .state
1708 .state
1709 .values()
1710 .filter(|acc| is_new_deployment(acc))
1711 .filter_map(|acc| acc.info.as_ref().map(|info| info.code_hash))
1712 .collect();
1713 let code_bytes_written: usize = unique_new_code_hashes
1714 .iter()
1715 .filter_map(|hash| output.state.contracts.get(hash).map(|bytecode| bytecode.len()))
1716 .sum();
1717
1718 let state_read_duration = provider_stats.total_account_fetch_latency() +
1720 provider_stats.total_storage_fetch_latency() +
1721 provider_stats.total_code_fetch_latency();
1722
1723 let eip7702_delegations_set =
1726 output.state.contracts.values().filter(|bytecode| bytecode.is_eip7702()).count();
1727 let eip7702_delegations_cleared = output
1732 .state
1733 .state
1734 .values()
1735 .filter(|acc| {
1736 let original_was_eip7702 = acc
1738 .original_info
1739 .as_ref()
1740 .and_then(|info| info.code.as_ref())
1741 .is_some_and(|bytecode| bytecode.is_eip7702());
1742
1743 let code_now_empty = acc.info.as_ref().is_some_and(AccountInfo::is_empty_code_hash);
1745
1746 original_was_eip7702 && code_now_empty
1747 })
1748 .count();
1749
1750 let (account_cache_hits, account_cache_misses) = cache_stats
1752 .as_ref()
1753 .map(|s| (s.account_hits(), s.account_misses()))
1754 .unwrap_or_default();
1755 let (storage_cache_hits, storage_cache_misses) = cache_stats
1756 .as_ref()
1757 .map(|s| (s.storage_hits(), s.storage_misses()))
1758 .unwrap_or_default();
1759 let (code_cache_hits, code_cache_misses) =
1760 cache_stats.as_ref().map(|s| (s.code_hits(), s.code_misses())).unwrap_or_default();
1761 let (txpool_snapshot_account_hits, txpool_snapshot_account_misses) = cache_stats
1762 .as_ref()
1763 .map(|s| (s.txpool_snapshot_account_hits(), s.txpool_snapshot_account_misses()))
1764 .unwrap_or_default();
1765 let (txpool_snapshot_storage_hits, txpool_snapshot_storage_misses) = cache_stats
1766 .as_ref()
1767 .map(|s| (s.txpool_snapshot_storage_hits(), s.txpool_snapshot_storage_misses()))
1768 .unwrap_or_default();
1769 let (txpool_snapshot_code_hits, txpool_snapshot_code_misses) = cache_stats
1770 .as_ref()
1771 .map(|s| (s.txpool_snapshot_code_hits(), s.txpool_snapshot_code_misses()))
1772 .unwrap_or_default();
1773
1774 Box::new(ExecutionTimingStats {
1776 block_number: block.number(),
1777 block_hash: block.hash(),
1778 gas_used: output.result.gas_used,
1779 tx_count: block.transaction_count(),
1780 execution_duration,
1781 state_read_duration,
1782 state_hash_duration,
1783 accounts_read,
1784 storage_read,
1785 code_read,
1786 code_bytes_read,
1787 accounts_changed,
1788 accounts_deleted,
1789 storage_slots_changed,
1790 storage_slots_deleted,
1791 bytecodes_changed,
1792 code_bytes_written,
1793 eip7702_delegations_set,
1794 eip7702_delegations_cleared,
1795 account_cache_hits,
1796 account_cache_misses,
1797 storage_cache_hits,
1798 storage_cache_misses,
1799 code_cache_hits,
1800 code_cache_misses,
1801 txpool_snapshot_account_hits,
1802 txpool_snapshot_account_misses,
1803 txpool_snapshot_storage_hits,
1804 txpool_snapshot_storage_misses,
1805 txpool_snapshot_code_hits,
1806 txpool_snapshot_code_misses,
1807 })
1808 }
1809}
1810
1811pub trait EngineValidator<
1815 Types: PayloadTypes,
1816 N: NodePrimitives = <<Types as PayloadTypes>::BuiltPayload as BuiltPayload>::Primitives,
1817>: Send + Sync + 'static
1818{
1819 fn validate_payload_attributes_against_header(
1829 &self,
1830 attr: &Types::PayloadAttributes,
1831 header: &N::BlockHeader,
1832 ) -> Result<(), InvalidPayloadAttributesError>;
1833
1834 fn convert_payload_to_block(
1843 &self,
1844 payload: Types::ExecutionData,
1845 ) -> Result<SealedBlock<N::Block>, NewPayloadError>;
1846
1847 fn validate_payload(
1849 &mut self,
1850 payload: Types::ExecutionData,
1851 ctx: TreeCtx<'_, N>,
1852 ) -> ValidationOutcome<N>;
1853
1854 fn validate_block(
1856 &mut self,
1857 block: SealedBlockWithAccessList<N::Block>,
1858 ctx: TreeCtx<'_, N>,
1859 ) -> ValidationOutcome<N>;
1860
1861 fn on_inserted_executed_block(
1866 &self,
1867 block: BuiltPayloadExecutedBlock<N>,
1868 ) -> ProviderResult<ExecutedBlock<N>>;
1869
1870 fn on_canonical_head_changed(&self, _hash: B256, _state: &EngineApiTreeState<N>) {}
1874
1875 fn payload_builder_resources(
1879 &self,
1880 parent_hash: B256,
1881 parent_header: &N::BlockHeader,
1882 timestamp: u64,
1883 state: &mut EngineApiTreeState<N>,
1884 ) -> PayloadBuilderResources;
1885}
1886
1887impl<N, Types, P, Evm, V> EngineValidator<Types> for BasicEngineValidator<P, Evm, V>
1888where
1889 P: DatabaseProviderFactory<
1890 Provider: BlockReader
1891 + StageCheckpointReader
1892 + PruneCheckpointReader
1893 + ChangeSetReader
1894 + StorageChangeSetReader
1895 + StorageSettingsCache
1896 + HistoryReader
1897 + 'static,
1898 > + BlockReader<Header = N::BlockHeader>
1899 + StateProviderFactory
1900 + StateReader
1901 + ChangeSetReader
1902 + Clone
1903 + 'static,
1904 N: NodePrimitives,
1905 V: PayloadValidator<Types, Block = N::Block> + Clone,
1906 Evm: ConfigureEngineEvm<Types::ExecutionData, Primitives = N> + 'static,
1907 Types: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>,
1908{
1909 fn validate_payload_attributes_against_header(
1910 &self,
1911 attr: &Types::PayloadAttributes,
1912 header: &N::BlockHeader,
1913 ) -> Result<(), InvalidPayloadAttributesError> {
1914 self.validator.validate_payload_attributes_against_header(attr, header)
1915 }
1916
1917 fn convert_payload_to_block(
1918 &self,
1919 payload: Types::ExecutionData,
1920 ) -> Result<SealedBlock<N::Block>, NewPayloadError> {
1921 let block = self.validator.convert_payload_to_block(payload)?;
1922 Ok(block)
1923 }
1924
1925 fn validate_payload(
1926 &mut self,
1927 payload: Types::ExecutionData,
1928 ctx: TreeCtx<'_, N>,
1929 ) -> ValidationOutcome<N> {
1930 self.validate_block_with_state(BlockOrPayload::Payload(payload), ctx)
1931 }
1932
1933 fn validate_block(
1934 &mut self,
1935 block: SealedBlockWithAccessList<N::Block>,
1936 ctx: TreeCtx<'_, N>,
1937 ) -> ValidationOutcome<N> {
1938 self.validate_block_with_state(BlockOrPayload::Block(block), ctx)
1939 }
1940
1941 fn on_inserted_executed_block(
1942 &self,
1943 block: BuiltPayloadExecutedBlock<N>,
1944 ) -> ProviderResult<ExecutedBlock<N>> {
1945 self.payload_processor.on_inserted_executed_block(
1946 block.recovered_block.block_with_parent(),
1947 &block.execution_output.state,
1948 );
1949
1950 Ok(self.spawn_deferred_trie_task(
1951 block.recovered_block,
1952 block.execution_output,
1953 LazyHashedPostState::ready(block.hashed_state),
1954 block.trie_updates,
1955 ))
1956 }
1957
1958 fn on_canonical_head_changed(&self, hash: B256, state: &EngineApiTreeState<N>) {
1959 let Some(txpool_prewarm) = self.txpool_prewarm.as_ref() else { return };
1960
1961 let parent = match self.sealed_header_by_hash(hash, state) {
1964 Ok(Some(header)) => header,
1965 Ok(None) => return,
1966 Err(err) => {
1967 trace!(
1968 target: "engine::tree::txpool_prewarm",
1969 %err,
1970 block_hash = ?hash,
1971 "failed to fetch canonical header for txpool prewarming"
1972 );
1973 return
1974 }
1975 };
1976 let evm_env = match self.evm_config.evm_env(parent.header()) {
1981 Ok(evm_env) => evm_env,
1982 Err(err) => {
1983 trace!(
1984 target: "engine::tree::txpool_prewarm",
1985 %err,
1986 block_hash = ?parent.hash(),
1987 "failed to derive canonical txpool prewarming environment"
1988 );
1989 return
1990 }
1991 };
1992
1993 let state_provider_factory = match self.overlay_state_provider_factory(parent.hash(), state)
1994 {
1995 Ok(Some(state_provider_factory)) => state_provider_factory,
1996 Ok(None) => return,
1997 Err(err) => {
1998 trace!(
1999 target: "engine::tree::txpool_prewarm",
2000 %err,
2001 block_hash = ?parent.hash(),
2002 "failed to derive canonical txpool prewarming provider"
2003 );
2004 return
2005 }
2006 };
2007 txpool_prewarm.start(&parent, evm_env, state_provider_factory)
2008 }
2009
2010 fn payload_builder_resources(
2011 &self,
2012 parent_hash: B256,
2013 parent_header: &N::BlockHeader,
2014 timestamp: u64,
2015 state: &mut EngineApiTreeState<N>,
2016 ) -> PayloadBuilderResources {
2017 let execution_cache = self
2018 .config
2019 .share_execution_cache_with_payload_builder()
2020 .then(|| self.payload_processor.cache_for(parent_hash));
2021 let state_root_handle =
2022 self.payload_state_root_handle_for(parent_hash, parent_header, timestamp, state);
2023 let mut resources = PayloadBuilderResources::new(execution_cache, state_root_handle)
2024 .with_lease(PayloadBuilderLease::new(JitPauseGuard::new(&self.evm_config)));
2025 if let Some(txpool_prewarm) = self.txpool_prewarm.as_ref() {
2029 let txpool_lease = PayloadBuilderLease::new(txpool_prewarm.pause());
2030 resources = resources.with_lease(txpool_lease);
2031 }
2032 resources
2033 }
2034}
2035
2036impl<P, Evm, V> WaitForCaches for BasicEngineValidator<P, Evm, V>
2037where
2038 Evm: ConfigureEvm,
2039{
2040 fn wait_for_caches(&self) -> CacheWaitDurations {
2041 debug!(target: "engine::tree::payload_validator", "Waiting for execution cache and sparse trie locks");
2042
2043 let execution_cache = self.payload_processor.execution_cache();
2044 let overlay_manager = self.overlay_manager.clone();
2045 let (execution_tx, execution_rx) = std::sync::mpsc::channel();
2046 let (sparse_trie_tx, sparse_trie_rx) = std::sync::mpsc::channel();
2047
2048 self.runtime.spawn_blocking_named("wait-exec-cache", move || {
2049 let _ = execution_tx.send(execution_cache.wait_for_availability());
2050 });
2051 self.runtime.spawn_blocking_named("wait-sparse-tri", move || {
2052 let _ = sparse_trie_tx.send(overlay_manager.wait_for_sparse_trie_availability());
2053 });
2054
2055 let execution_cache =
2056 execution_rx.recv().expect("execution cache wait task failed to send result");
2057 let sparse_trie =
2058 sparse_trie_rx.recv().expect("sparse trie wait task failed to send result");
2059 debug!(
2060 target: "engine::tree::payload_validator",
2061 ?execution_cache,
2062 ?sparse_trie,
2063 "Execution cache and sparse trie locks acquired"
2064 );
2065 CacheWaitDurations { execution_cache, sparse_trie }
2066 }
2067}
2068
2069#[derive(Debug, Clone)]
2071pub enum BlockOrPayload<T: PayloadTypes> {
2072 Payload(T::ExecutionData),
2074 Block(SealedBlockWithAccessList<BlockTy<<T::BuiltPayload as BuiltPayload>::Primitives>>),
2076}
2077
2078impl<T: PayloadTypes> BlockOrPayload<T> {
2079 pub fn hash(&self) -> B256 {
2081 match self {
2082 Self::Payload(payload) => payload.block_hash(),
2083 Self::Block(block) => block.hash(),
2084 }
2085 }
2086
2087 pub fn num_hash(&self) -> NumHash {
2089 match self {
2090 Self::Payload(payload) => payload.num_hash(),
2091 Self::Block(block) => block.num_hash(),
2092 }
2093 }
2094
2095 pub fn parent_hash(&self) -> B256 {
2097 match self {
2098 Self::Payload(payload) => payload.parent_hash(),
2099 Self::Block(block) => block.parent_hash(),
2100 }
2101 }
2102
2103 pub fn block_with_parent(&self) -> BlockWithParent {
2105 match self {
2106 Self::Payload(payload) => payload.block_with_parent(),
2107 Self::Block(block) => block.block_with_parent(),
2108 }
2109 }
2110
2111 pub const fn type_name(&self) -> &'static str {
2113 match self {
2114 Self::Payload(_) => "payload",
2115 Self::Block(_) => "block",
2116 }
2117 }
2118
2119 pub const fn is_payload(&self) -> bool {
2121 matches!(self, Self::Payload(_))
2122 }
2123
2124 pub const fn is_block(&self) -> bool {
2126 matches!(self, Self::Block(_))
2127 }
2128
2129 pub fn try_decoded_access_list(&self) -> Result<Option<DecodedBal>, alloy_rlp::Error> {
2131 match self {
2132 Self::Payload(payload) => payload
2133 .block_access_list()
2134 .map(|block_access_list| DecodedBal::from_rlp_bytes(block_access_list.clone()))
2135 .transpose(),
2136 Self::Block(block) => block.data().clone().map(DecodedBal::from_raw_bal).transpose(),
2137 }
2138 }
2139
2140 pub fn has_block_access_list(&self) -> bool {
2142 match self {
2143 Self::Payload(payload) => payload.block_access_list().is_some(),
2144 Self::Block(block) => block.block_access_list_hash().is_some(),
2145 }
2146 }
2147
2148 pub fn transaction_count(&self) -> usize
2150 where
2151 T::ExecutionData: ExecutionPayload,
2152 {
2153 match self {
2154 Self::Payload(payload) => payload.transaction_count(),
2155 Self::Block(block) => block.transaction_count(),
2156 }
2157 }
2158
2159 pub fn withdrawals(&self) -> Option<&[Withdrawal]>
2161 where
2162 T::ExecutionData: ExecutionPayload,
2163 {
2164 match self {
2165 Self::Payload(payload) => payload.withdrawals().map(|w| w.as_slice()),
2166 Self::Block(block) => block.body().withdrawals().map(|w| w.as_slice()),
2167 }
2168 }
2169
2170 pub fn gas_used(&self) -> u64
2172 where
2173 T::ExecutionData: ExecutionPayload,
2174 {
2175 match self {
2176 Self::Payload(payload) => payload.gas_used(),
2177 Self::Block(block) => block.gas_used(),
2178 }
2179 }
2180
2181 pub fn gas_limit(&self) -> u64
2183 where
2184 T::ExecutionData: ExecutionPayload,
2185 {
2186 match self {
2187 Self::Payload(payload) => payload.gas_limit(),
2188 Self::Block(block) => block.gas_limit(),
2189 }
2190 }
2191}
2192
2193struct ExecutedBal {
2195 alloy: BlockAccessList,
2197 revm: Arc<RevmBal>,
2199}