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 types::{InsertPayloadResult, ValidationOutput},
104 CacheWaitDurations, CachedStateProvider, EngineApiMetrics, EngineApiTreeState, ExecutionEnv,
105 PayloadHandle, StateProviderBuilder, StateProviderDatabase, TreeConfig, WaitForCaches,
106};
107use alloy_consensus::transaction::{Either, TxHashRef};
108use alloy_eip7928::{bal::DecodedBal, compute_block_access_list_hash, BlockAccessList};
109use alloy_eips::{eip1898::BlockWithParent, eip4895::Withdrawal, NumHash};
110use alloy_evm::Evm;
111use alloy_primitives::{
112 map::{AddressMap, B256Set},
113 B256,
114};
115use reth_tasks::LazyHandle;
116
117use crate::tree::{
118 payload_processor::receipt_root_task::{IndexedReceipt, ReceiptRootTaskHandle},
119 state_root_strategy::{
120 DefaultStateRootStrategy, LazyHashedPostState, PayloadStateRootHandle,
121 PayloadStateRootJobContext, StateRootHintStream, StateRootJobContext, StateRootStrategy,
122 StateRootUpdateStream,
123 },
124};
125use alloy_consensus::constants::KECCAK_EMPTY;
126use alloy_primitives::Address;
127use reth_chain_state::{
128 CanonicalInMemoryState, ExecutedBlock, ExecutionTimingStats, StateTrieOverlayManager,
129};
130use reth_consensus::{ConsensusError, FullConsensus, ReceiptRootBloom};
131use reth_engine_primitives::{
132 ConfigureEngineEvm, ExecutableTxIterator, ExecutionPayload, InvalidBlockHook, PayloadValidator,
133};
134use reth_errors::{BlockExecutionError, ProviderResult};
135use reth_evm::{
136 block::BlockExecutor, execute::ExecutableTxFor, ConfigureEvm, EvmEnvFor, ExecutionCtxFor,
137 OnStateHook, SpecFor,
138};
139use reth_execution_cache::{CacheFillMode, CacheStats, SavedCache};
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::{OverlayBuilder, OverlayStateProviderFactory},
150 BlockExecutionOutput, BlockNumReader, BlockReader, ChangeSetReader, DatabaseProviderFactory,
151 DatabaseProviderROFactory, HashedPostStateProvider, ProviderError, PruneCheckpointReader,
152 StageCheckpointReader, StateProvider, StateProviderBox, StateProviderFactory, StateReader,
153 StorageChangeSetReader, StorageSettingsCache,
154};
155use reth_revm::db::{states::bundle_state::BundleRetention, BundleAccount, State};
156use reth_trie::{
157 hashed_cursor::HashedCursorFactory, prefix_set::TriePrefixSetsMut,
158 trie_cursor::TrieCursorFactory, updates::TrieUpdates, LazyTrieData,
159};
160use reth_trie_db::ChangesetCache;
161use std::{
162 sync::{
163 atomic::{AtomicUsize, Ordering},
164 Arc,
165 },
166 time::Duration,
167};
168use tracing::{debug, debug_span, error, info, instrument, trace, warn, Level, Span};
169
170pub use crate::tree::types::ValidationOutcome;
171
172const MAX_EXPECTED_GAS_USAGE_MULTIPLIER: u64 = 2;
176
177const DEFERRED_TRIE_WORKER_NAME: &str = "deferred-trie";
179
180type ReceiptRootSender<N> =
181 crossbeam_channel::Sender<IndexedReceipt<<N as NodePrimitives>::Receipt>>;
182type ReceiptRootReceiver = tokio::sync::oneshot::Receiver<(B256, alloy_primitives::Bloom)>;
183
184pub struct TreeCtx<'a, N: NodePrimitives> {
189 state: &'a mut EngineApiTreeState<N>,
191 canonical_in_memory_state: &'a CanonicalInMemoryState<N>,
193}
194
195impl<'a, N: NodePrimitives> std::fmt::Debug for TreeCtx<'a, N> {
196 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
197 f.debug_struct("TreeCtx")
198 .field("state", &"EngineApiTreeState")
199 .field("canonical_in_memory_state", &self.canonical_in_memory_state)
200 .finish()
201 }
202}
203
204impl<'a, N: NodePrimitives> TreeCtx<'a, N> {
205 pub const fn new(
207 state: &'a mut EngineApiTreeState<N>,
208 canonical_in_memory_state: &'a CanonicalInMemoryState<N>,
209 ) -> Self {
210 Self { state, canonical_in_memory_state }
211 }
212}
213
214impl<'a, N: NodePrimitives> TreeCtx<'a, N> {
215 pub const fn state(&self) -> &EngineApiTreeState<N> {
217 &*self.state
218 }
219
220 pub const fn state_mut(&mut self) -> &mut EngineApiTreeState<N> {
222 self.state
223 }
224
225 pub const fn canonical_in_memory_state(&self) -> &'a CanonicalInMemoryState<N> {
227 self.canonical_in_memory_state
228 }
229}
230
231struct JitPauseGuard<Evm: ConfigureEvm>(Evm);
237
238impl<Evm: ConfigureEvm> JitPauseGuard<Evm> {
239 fn new(evm_config: &Evm) -> Self {
240 if let Some(jit_backend) = evm_config.jit_backend() {
241 jit_backend.pause();
242 }
243 Self(evm_config.clone())
244 }
245}
246
247impl<Evm: ConfigureEvm> Drop for JitPauseGuard<Evm> {
248 fn drop(&mut self) {
249 if let Some(jit_backend) = self.0.jit_backend() {
250 jit_backend.resume();
251 }
252 }
253}
254
255#[derive(derive_more::Debug)]
263pub struct BasicEngineValidator<P, Evm, V>
264where
265 Evm: ConfigureEvm,
266{
267 provider: P,
269 consensus: Arc<dyn FullConsensus<Evm::Primitives>>,
271 evm_config: Evm,
273 config: TreeConfig,
275 payload_processor: PayloadProcessor<Evm>,
277 precompile_cache_map: PrecompileCacheMap<SpecFor<Evm>>,
279 precompile_cache_metrics: AddressMap<CachedPrecompileMetrics>,
281 #[debug(skip)]
283 invalid_block_hook: Box<dyn InvalidBlockHook<Evm::Primitives>>,
284 metrics: EngineApiMetrics,
286 validator: V,
288 changeset_cache: ChangesetCache,
290 runtime: reth_tasks::Runtime,
292 state_trie_overlays: StateTrieOverlayManager<Evm::Primitives>,
294 #[debug(skip)]
296 state_root_strategy: Arc<dyn StateRootStrategy<Evm::Primitives, P, Evm>>,
297}
298
299impl<N, P, Evm, V> BasicEngineValidator<P, Evm, V>
300where
301 N: NodePrimitives,
302 P: DatabaseProviderFactory<
303 Provider: BlockReader
304 + StageCheckpointReader
305 + PruneCheckpointReader
306 + ChangeSetReader
307 + StorageChangeSetReader
308 + BlockNumReader
309 + StorageSettingsCache,
310 > + BlockReader<Header = N::BlockHeader>
311 + ChangeSetReader
312 + BlockNumReader
313 + StateProviderFactory
314 + StateReader
315 + HashedPostStateProvider
316 + Clone
317 + 'static,
318 OverlayStateProviderFactory<P, N>: DatabaseProviderROFactory<Provider: TrieCursorFactory + HashedCursorFactory>
319 + Clone
320 + Send
321 + Sync
322 + 'static,
323 Evm: ConfigureEvm<Primitives = N> + 'static,
324{
325 #[expect(clippy::too_many_arguments)]
327 pub fn new(
328 provider: P,
329 consensus: Arc<dyn FullConsensus<N>>,
330 evm_config: Evm,
331 validator: V,
332 config: TreeConfig,
333 invalid_block_hook: Box<dyn InvalidBlockHook<N>>,
334 changeset_cache: ChangesetCache,
335 state_trie_overlays: StateTrieOverlayManager<N>,
336 runtime: reth_tasks::Runtime,
337 ) -> Self {
338 let precompile_cache_map = PrecompileCacheMap::default();
339 let payload_processor = PayloadProcessor::new(
340 runtime.clone(),
341 evm_config.clone(),
342 &config,
343 precompile_cache_map.clone(),
344 );
345 Self {
346 provider,
347 consensus,
348 evm_config,
349 payload_processor,
350 precompile_cache_map,
351 precompile_cache_metrics: AddressMap::default(),
352 config,
353 invalid_block_hook,
354 metrics: EngineApiMetrics::default(),
355 validator,
356 changeset_cache,
357 runtime,
358 state_trie_overlays,
359 state_root_strategy: Arc::new(DefaultStateRootStrategy::default()),
360 }
361 }
362
363 pub fn with_state_root_strategy(
365 mut self,
366 state_root_strategy: Arc<dyn StateRootStrategy<N, P, Evm>>,
367 ) -> Self {
368 self.state_root_strategy = state_root_strategy;
369 self
370 }
371
372 #[instrument(level = "debug", target = "engine::tree::payload_validator", skip_all)]
374 pub fn convert_to_block<T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
375 &self,
376 input: BlockOrPayload<T>,
377 ) -> Result<SealedBlock<N::Block>, NewPayloadError>
378 where
379 V: PayloadValidator<T, Block = N::Block>,
380 {
381 match input {
382 BlockOrPayload::Payload(payload) => self.validator.convert_payload_to_block(payload),
383 BlockOrPayload::Block(block) => Ok(block),
384 }
385 }
386
387 pub fn evm_env_for<T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
389 &self,
390 input: &BlockOrPayload<T>,
391 ) -> Result<EvmEnvFor<Evm>, Evm::Error>
392 where
393 V: PayloadValidator<T, Block = N::Block>,
394 Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
395 {
396 match input {
397 BlockOrPayload::Payload(payload) => Ok(self.evm_config.evm_env_for_payload(payload)?),
398 BlockOrPayload::Block(block) => Ok(self.evm_config.evm_env(block.header())?),
399 }
400 }
401
402 pub fn tx_iterator_for<'a, T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
404 &'a self,
405 input: &'a BlockOrPayload<T>,
406 ) -> Result<impl ExecutableTxIterator<Evm>, NewPayloadError>
407 where
408 V: PayloadValidator<T, Block = N::Block>,
409 Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
410 {
411 Ok(match input {
412 BlockOrPayload::Payload(payload) => {
413 let iter = self
414 .evm_config
415 .tx_iterator_for_payload(payload)
416 .map_err(NewPayloadError::other)?;
417 Either::Left(iter)
418 }
419 BlockOrPayload::Block(block) => {
420 let txs = block.body().clone_transactions();
421 let convert = |tx: N::SignedTx| tx.try_into_recovered();
422 Either::Right((txs, convert))
423 }
424 })
425 }
426
427 pub fn execution_ctx_for<'a, T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
429 &self,
430 input: &'a BlockOrPayload<T>,
431 ) -> Result<ExecutionCtxFor<'a, Evm>, Evm::Error>
432 where
433 V: PayloadValidator<T, Block = N::Block>,
434 Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
435 {
436 match input {
437 BlockOrPayload::Payload(payload) => Ok(self.evm_config.context_for_payload(payload)?),
438 BlockOrPayload::Block(block) => Ok(self.evm_config.context_for_block(block)?),
439 }
440 }
441
442 #[instrument(
450 level = "debug",
451 target = "engine::tree::payload_validator",
452 skip_all,
453 fields(
454 parent = ?input.parent_hash(),
455 type_name = ?input.type_name(),
456 )
457 )]
458 pub fn validate_block_with_state<T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
459 &mut self,
460 input: BlockOrPayload<T>,
461 mut ctx: TreeCtx<'_, N>,
462 ) -> InsertPayloadResult<N>
463 where
464 V: PayloadValidator<T, Block = N::Block> + Clone,
465 Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
466 {
467 let parent_hash = input.parent_hash();
468 let _jit_pause = JitPauseGuard::new(&self.evm_config);
469
470 let parent_block = match self.sealed_header_by_hash(parent_hash, ctx.state()) {
473 Ok(Some(parent_block)) => parent_block,
474 Ok(None) => {
475 return Err(InsertBlockError::new(
476 self.convert_to_block(input)?,
477 ProviderError::HeaderNotFound(parent_hash.into()).into(),
478 )
479 .into())
480 }
481 Err(e) => {
482 return Err(InsertBlockError::new(self.convert_to_block(input)?, e.into()).into())
483 }
484 };
485
486 let validated_block = self.spawn_convert_and_validate(&input, parent_block.clone());
490
491 macro_rules! ensure_ok {
494 ($expr:expr) => {
495 match $expr {
496 Ok(val) => val,
497 Err(e) => {
498 let block = validated_block.try_into_inner().expect("sole handle")?;
499 return Err(InsertBlockError::new(block, e.into()).into())
500 }
501 }
502 };
503 }
504
505 macro_rules! ensure_ok_post_block {
507 ($expr:expr, $block:expr) => {
508 match $expr {
509 Ok(val) => val,
510 Err(e) => {
511 return Err(
512 InsertBlockError::new($block.into_sealed_block(), e.into()).into()
513 )
514 }
515 }
516 };
517 }
518
519 if input.gas_used() > parent_block.gas_limit() * MAX_EXPECTED_GAS_USAGE_MULTIPLIER {
522 if validated_block.get().is_err() {
524 return Err(validated_block
525 .try_into_inner()
526 .expect("sole handle")
527 .expect_err("Err result checked"))
528 }
529 }
530
531 trace!(target: "engine::tree::payload_validator", "Fetching block state provider");
532 let _enter =
533 debug_span!(target: "engine::tree::payload_validator", "state_provider").entered();
534 let Some(provider_builder) =
535 ensure_ok!(self.state_provider_builder(parent_hash, ctx.state()))
536 else {
537 return Err(InsertBlockError::new(
539 validated_block.try_into_inner().expect("sole handle")?,
540 ProviderError::HeaderNotFound(parent_hash.into()).into(),
541 )
542 .into())
543 };
544 drop(_enter);
545
546 let evm_env = debug_span!(target: "engine::tree::payload_validator", "evm_env")
547 .in_scope(|| self.evm_env_for(&input))
548 .map_err(NewPayloadError::other)?;
549
550 let decoded_bal = ensure_ok!(input
552 .try_decoded_access_list()
553 .map_err(|err| ConsensusError::BlockAccessListInvalid(err.to_string())))
554 .map(Arc::new);
555
556 if let Some(decoded_bal) = decoded_bal.as_deref() {
557 ensure_ok!(decoded_bal
559 .as_bal()
560 .validate_gas_limit(input.gas_limit())
561 .map_err(ConsensusError::from));
562 }
563
564 let env = ExecutionEnv {
565 evm_env,
566 hash: input.hash(),
567 parent_hash: input.parent_hash(),
568 parent_state_root: parent_block.state_root(),
569 transaction_count: input.transaction_count(),
570 gas_used: input.gas_used(),
571 withdrawals: input.withdrawals().map(|w| w.to_vec()),
572 decoded_bal: decoded_bal.as_ref().map(Arc::clone),
573 };
574
575 let txs = self.tx_iterator_for(&input)?;
577
578 let provider_factory = self.provider.clone();
580 let overlay_builder = Self::overlay_builder_for_parent(
581 parent_hash,
582 ctx.state(),
583 self.changeset_cache.clone(),
584 );
585 let overlay_factory = OverlayStateProviderFactory::new(provider_factory, overlay_builder);
586
587 let parallel_bal_execution = ensure_ok!(self.bal_path_eligible(env.decoded_bal.as_deref()));
588
589 let mut state_root_job =
591 ensure_ok!(self.state_root_strategy.prepare(StateRootJobContext::new(
592 &self.runtime,
593 &self.state_trie_overlays,
594 &env,
595 provider_builder.clone(),
596 overlay_factory,
597 &self.config,
598 parallel_bal_execution,
599 ctx.state_mut(),
600 )));
601 let state_root_job_name = state_root_job.name();
602
603 debug!(
604 target: "engine::tree::payload_validator",
605 strategy = state_root_job_name,
606 "Prepared state root job"
607 );
608
609 let execution_state_hook = state_root_job.take_execution_hook();
612 let hint_stream = state_root_job.take_hint_stream();
615 let hashed_update_stream = state_root_job.take_hashed_update_stream();
616
617 let mut handle = ensure_ok!(self.spawn_payload_processor(
619 env.clone(),
620 txs,
621 provider_builder.clone(),
622 hint_stream,
623 hashed_update_stream,
624 parallel_bal_execution,
625 ));
626
627 let slow_block_enabled = self.config.slow_block_threshold().is_some();
629 let cache_stats = slow_block_enabled.then(|| Arc::new(CacheStats::default()));
630 let instrument_state_provider = slow_block_enabled || self.config.state_provider_metrics();
631 let state_provider_metrics =
632 instrument_state_provider.then(|| StateProviderMetrics::with_source("engine"));
633 let state_provider_stats =
634 instrument_state_provider.then(|| Arc::new(StateProviderStats::default()));
635 let execution_cache = handle.caches().map(|caches| (caches, handle.cache_metrics()));
636
637 let make_state_provider = |fill_on_miss: bool| -> ProviderResult<StateProviderBox> {
657 let provider = provider_builder.build()?;
658 let mut provider = if let Some((caches, cache_metrics)) = &execution_cache {
659 let fill_mode = if fill_on_miss {
660 CacheFillMode::FillOnMiss
661 } else {
662 CacheFillMode::LookupOnly
663 };
664 Box::new(CachedStateProvider::new_with_mode(
665 provider,
666 caches.clone(),
667 fill_mode,
668 cache_metrics.clone(),
669 cache_stats.clone(),
670 )) as StateProviderBox
671 } else {
672 provider
673 };
674
675 if instrument_state_provider {
676 let stats = state_provider_stats
677 .as_ref()
678 .expect("instrumented state provider requires shared stats");
679 let metrics = state_provider_metrics
680 .as_ref()
681 .expect("instrumented state provider requires metrics");
682 provider = Box::new(InstrumentedStateProvider::with_stats(
683 provider,
684 metrics.clone(),
685 Arc::clone(stats),
686 ));
687 }
688
689 Ok(provider)
690 };
691
692 let execute_block_start = Instant::now();
696 let execution_result = if parallel_bal_execution {
697 self.execute_block_bal(env, &input, &handle, &make_state_provider)
698 } else {
699 let state_provider = make_state_provider(false);
700 match state_provider {
701 Ok(state_provider) => self.execute_block(
702 state_provider,
703 env,
704 &input,
705 &mut handle,
706 execution_state_hook,
707 ),
708 Err(err) => Err(err.into()),
709 }
710 };
711 let execution_duration = execute_block_start.elapsed();
712 if let (Some(metrics), Some(stats)) = (&state_provider_metrics, &state_provider_stats) {
713 metrics.record_totals(stats);
714 }
715 let (output, senders, receipt_root_rx, built_bal) = ensure_ok!(execution_result);
716
717 handle.stop_prewarming_execution();
719
720 let output = Arc::new(output);
724
725 let valid_block_tx = handle.terminate_caching(Some(output.clone()));
728
729 let hashed_state_output = output.clone();
733 let hashed_state_provider = self.provider.clone();
734 let mut hashed_state_rx = state_root_job.take_hashed_state_rx();
735 let mut hashed_state: LazyHashedPostState =
736 self.runtime.spawn_blocking_named("hash-post-state", move || {
737 let _span = debug_span!(
738 target: "engine::tree::payload_validator",
739 "hashed_post_state",
740 )
741 .entered();
742 let state = if let Some(Ok(state)) = hashed_state_rx.as_mut().map(|rx| rx.recv()) {
743 state
744 } else {
745 hashed_state_provider.hashed_post_state(&hashed_state_output.state)
746 };
747 Arc::new(state)
748 });
749
750 let block = validated_block.try_into_inner().expect("sole handle")?;
751 let block = block.with_senders(senders);
752
753 let receipt_root_bloom = {
755 let _enter = debug_span!(
756 target: "engine::tree::payload_validator",
757 "wait_receipt_root",
758 )
759 .entered();
760
761 receipt_root_rx
762 .blocking_recv()
763 .inspect_err(|_| {
764 tracing::error!(
765 target: "engine::tree::payload_validator",
766 "Receipt root task dropped sender without result, receipt root calculation likely aborted"
767 );
768 })
769 .ok()
770 };
771
772 ensure_ok_post_block!(
773 self.validate_post_execution(
774 &block,
775 &parent_block,
776 &output,
777 &mut ctx,
778 receipt_root_bloom,
779 built_bal
780 ),
781 block
782 );
783
784 let mut hashed_state_validate_result = debug_span!(
785 target: "engine::tree::payload_validator",
786 "validate_block_post_execution_with_hashed_state"
787 )
788 .in_scope(|| {
789 self.validator
790 .validate_block_post_execution_with_hashed_state(&|| hashed_state.get(), &block)
791 });
792
793 let root_start = Instant::now();
794 let root_outcome = ensure_ok_post_block!(
795 state_root_job.finish(&block, output.clone(), &hashed_state),
796 block
797 );
798 let root_elapsed = root_start.elapsed();
799
800 info!(
801 target: "engine::tree::payload_validator",
802 strategy = state_root_job_name,
803 state_root = ?root_outcome.state_root,
804 elapsed = ?root_elapsed,
805 "State root job finished"
806 );
807
808 let state_root = root_outcome.state_root;
809 let trie_output = root_outcome.trie_updates;
810 let changed_paths = root_outcome.changed_paths;
811
812 if let Some(refreshed) = root_outcome.hashed_state {
816 hashed_state = LazyHandle::ready(refreshed);
817 hashed_state_validate_result = debug_span!(
818 target: "engine::tree::payload_validator",
819 "validate_block_post_execution_with_hashed_state"
820 )
821 .in_scope(|| {
822 self.validator
823 .validate_block_post_execution_with_hashed_state(&|| hashed_state.get(), &block)
824 });
825 }
826
827 if let Err(err) = hashed_state_validate_result {
828 self.on_invalid_block(&parent_block, &block, &output, None, ctx.state_mut());
830 return Err(InsertBlockError::new(block.into_sealed_block(), err.into()).into())
831 }
832
833 self.metrics.block_validation.record_state_root(&trie_output, root_elapsed.as_secs_f64());
834 self.metrics
835 .record_state_root_gas_bucket(block.header().gas_used(), root_elapsed.as_secs_f64());
836 debug!(target: "engine::tree::payload_validator", ?root_elapsed, "Calculated state root");
837
838 if state_root != block.header().state_root() {
840 self.on_invalid_block(
842 &parent_block,
843 &block,
844 &output,
845 Some((&trie_output, state_root)),
846 ctx.state_mut(),
847 );
848 let block_state_root = block.header().state_root();
849 return Err(InsertBlockError::new(
850 block.into_sealed_block(),
851 ConsensusError::BodyStateRootDiff(
852 GotExpected { got: state_root, expected: block_state_root }.into(),
853 )
854 .into(),
855 )
856 .into())
857 }
858
859 let timing_stats = state_provider_stats.filter(|_| slow_block_enabled).map(|stats| {
860 self.calculate_timing_stats(
861 &block,
862 stats,
863 cache_stats,
864 &output,
865 execution_duration,
866 root_elapsed,
867 )
868 });
869
870 if let Some(valid_block_tx) = valid_block_tx {
871 let _ = valid_block_tx.send(());
872 }
873
874 let executed_block = self.spawn_deferred_trie_task(
875 Arc::new(block),
876 output,
877 hashed_state,
878 trie_output,
879 changed_paths,
880 );
881 let raw_bal = decoded_bal.map(|decoded_bal| decoded_bal.as_raw_bal().clone());
882 Ok(ValidationOutput::new(executed_block, timing_stats).with_raw_bal(raw_bal))
883 }
884
885 #[expect(clippy::type_complexity)]
888 pub fn spawn_convert_and_validate<T>(
889 &self,
890 input: &BlockOrPayload<T>,
891 parent: SealedHeader<N::BlockHeader>,
892 ) -> LazyHandle<Result<SealedBlock<N::Block>, InsertPayloadError<N::Block>>>
893 where
894 T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>,
895 V: PayloadValidator<T, Block = N::Block> + Clone,
896 {
897 let input = input.clone();
898 let validator = self.validator.clone();
899 let consensus = self.consensus.clone();
900 let parent_span = Span::current();
901 self.runtime.spawn_blocking_named("payload-convert", move || {
902 let _span = debug_span!(
903 target: "engine::tree::payload_validator",
904 parent: parent_span,
905 "convert_and_validate",
906 )
907 .entered();
908 let block = match input {
909 BlockOrPayload::Block(block) => block,
910 BlockOrPayload::Payload(payload) => {
911 validator.convert_payload_to_block(payload)?
912 }
913 };
914
915 if let Err(e) = consensus.validate_header(block.sealed_header()) {
916 error!(target: "engine::tree::payload_validator", ?block, "Failed to validate header {}: {e}", block.hash());
917 return Err(InsertBlockError::consensus_error(e, block).into())
918 }
919
920 let _enter = debug_span!(target: "engine::tree::payload_validator", "validate_header_against_parent").entered();
922 if let Err(e) = consensus.validate_header_against_parent(block.sealed_header(), &parent)
923 {
924 warn!(target: "engine::tree::payload_validator", ?block, "Failed to validate header {} against parent: {e}", block.hash());
925 return Err(InsertBlockError::consensus_error(e, block).into())
926 }
927 drop(_enter);
928
929 if let Err(e) =
930 consensus.validate_block_pre_execution_with_tx_root(&block, None)
931 {
932 error!(target: "engine::tree::payload_validator", ?block, "Failed to validate block {}: {e}", block.hash());
933 return Err(InsertBlockError::consensus_error(e, block).into())
934 }
935
936 Ok(block)
937 })
938 }
939
940 fn sealed_header_by_hash(
942 &self,
943 hash: B256,
944 state: &EngineApiTreeState<N>,
945 ) -> ProviderResult<Option<SealedHeader<N::BlockHeader>>> {
946 let header = state.tree_state.sealed_header_by_hash(&hash);
948
949 if header.is_some() {
950 Ok(header)
951 } else {
952 self.provider.sealed_header_by_hash(hash)
953 }
954 }
955
956 #[instrument(level = "debug", target = "engine::tree::payload_validator", skip_all)]
964 #[expect(clippy::type_complexity)]
965 fn execute_block<S, Err, T>(
966 &mut self,
967 state_provider: S,
968 env: ExecutionEnv<Evm>,
969 input: &BlockOrPayload<T>,
970 handle: &mut PayloadHandle<impl ExecutableTxFor<Evm>, Err, N::Receipt>,
971 state_hook: Option<Box<dyn OnStateHook + 'static>>,
972 ) -> Result<
973 (
974 BlockExecutionOutput<N::Receipt>,
975 Vec<Address>,
976 ReceiptRootReceiver,
977 Option<BlockAccessList>,
978 ),
979 InsertBlockErrorKind,
980 >
981 where
982 S: StateProvider + Send,
983 Err: core::error::Error + Send + Sync + 'static,
984 V: PayloadValidator<T, Block = N::Block>,
985 T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>,
986 Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
987 {
988 debug!(target: "engine::tree::payload_validator", "Executing block");
989
990 let has_bal = env.decoded_bal.is_some();
991 let mut db = debug_span!(target: "engine::tree", "build_state_db").in_scope(|| {
992 State::builder()
993 .with_database(StateProviderDatabase::new(state_provider))
994 .with_bundle_update()
995 .with_bal_builder_if(has_bal)
996 .build()
997 });
998
999 let (spec_id, mut executor) = {
1000 let _span = debug_span!(target: "engine::tree", "create_evm").entered();
1001 let spec_id = *env.evm_env.spec_id();
1002 let evm_config = self.evm_config.clone().with_jit_support();
1003 let evm = evm_config.evm_with_env(&mut db, env.evm_env);
1004 let ctx = self
1005 .execution_ctx_for(input)
1006 .map_err(|e| InsertBlockErrorKind::Other(Box::new(e)))?;
1007 let executor = self.evm_config.create_executor(evm, ctx);
1008 (spec_id, executor)
1009 };
1010
1011 if !self.config.precompile_cache_disabled() {
1012 let _span = debug_span!(target: "engine::tree", "setup_precompile_cache").entered();
1013 executor.evm_mut().precompiles_mut().map_cacheable_precompiles(
1014 |address, precompile| {
1015 let metrics = self
1016 .precompile_cache_metrics
1017 .entry(*address)
1018 .or_insert_with(|| CachedPrecompileMetrics::new_with_address(*address))
1019 .clone();
1020 CachedPrecompile::wrap(
1021 precompile,
1022 self.precompile_cache_map.cache_for_address(*address),
1023 spec_id,
1024 Some(metrics),
1025 )
1026 },
1027 );
1028 }
1029
1030 let transaction_count = input.transaction_count();
1031 let (receipt_tx, result_rx) = self.spawn_receipt_root_task(transaction_count);
1032 let executed_tx_index = Arc::clone(handle.executed_tx_index());
1033 executor.evm_mut().db_mut().set_state_hook(state_hook);
1034
1035 let execution_start = Instant::now();
1036
1037 let (executor, senders) = self.execute_transactions(
1039 executor,
1040 transaction_count,
1041 handle.iter_transactions(),
1042 &receipt_tx,
1043 &executed_tx_index,
1044 has_bal,
1045 )?;
1046 drop(receipt_tx);
1047
1048 let post_exec_start = Instant::now();
1050 let (_evm, result) = debug_span!(target: "engine::tree", "BlockExecutor::finish")
1051 .in_scope(|| executor.finish())
1052 .map(|(evm, result)| (evm.into_db(), result))?;
1053 self.metrics.record_post_execution(post_exec_start.elapsed());
1054
1055 debug_span!(target: "engine::tree", "merge_transitions")
1057 .in_scope(|| db.merge_transitions(BundleRetention::Reverts));
1058
1059 let built_bal = if has_bal { db.take_built_alloy_bal() } else { None };
1060 let output = BlockExecutionOutput { result, state: db.take_bundle() };
1061
1062 let execution_duration = execution_start.elapsed();
1063 self.metrics.record_block_execution(&output, execution_duration);
1064 self.metrics.record_block_execution_gas_bucket(output.result.gas_used, execution_duration);
1065 debug!(target: "engine::tree::payload_validator", elapsed = ?execution_duration, "Executed block");
1066
1067 Ok((output, senders, result_rx, built_bal))
1068 }
1069
1070 fn bal_path_eligible(&self, bal: Option<&DecodedBal>) -> Result<bool, InsertBlockErrorKind> {
1079 let has_bal = bal.is_some();
1080 let parallel_execution = has_bal && !self.config.disable_bal_parallel_execution();
1081 if parallel_execution && self.config.disable_bal_parallel_state_root() {
1082 return Err(InsertBlockErrorKind::Other(
1083 "disabling parallel state root is impossible when parallel execution is enabled"
1084 .into(),
1085 ));
1086 }
1087
1088 Ok(parallel_execution)
1089 }
1090
1091 #[instrument(level = "debug", target = "engine::tree::payload_validator", skip_all)]
1101 #[expect(clippy::type_complexity)]
1102 fn execute_block_bal<Tx, Err, MakeStateProvider, T>(
1103 &self,
1104 env: ExecutionEnv<Evm>,
1105 input: &BlockOrPayload<T>,
1106 handle: &PayloadHandle<Tx, Err, N::Receipt>,
1107 make_state_provider: &MakeStateProvider,
1108 ) -> Result<
1109 (
1110 BlockExecutionOutput<N::Receipt>,
1111 Vec<Address>,
1112 ReceiptRootReceiver,
1113 Option<BlockAccessList>,
1114 ),
1115 InsertBlockErrorKind,
1116 >
1117 where
1118 Tx: ExecutableTxFor<Evm> + Send,
1119 Err: core::error::Error + Send + Sync + 'static,
1120 MakeStateProvider: Fn(bool) -> ProviderResult<StateProviderBox> + Sync,
1121 Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
1122 T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>,
1123 V: PayloadValidator<T, Block = N::Block>,
1124 {
1125 debug!(target: "engine::tree::payload_validator", "Executing block via BAL path");
1126
1127 let (receipt_tx, result_rx) = self.spawn_receipt_root_task(env.transaction_count);
1128 let input_bal = env.decoded_bal.ok_or_else(|| {
1129 InsertBlockErrorKind::Other("BAL execute path: no decoded BAL available".into())
1130 })?;
1131
1132 let make_db = |fill_on_miss| {
1133 let provider = make_state_provider(fill_on_miss)
1134 .map_err(crate::tree::payload_processor::bal::BalExecutionError::Provider)?;
1135 Ok(StateProviderDatabase::new(provider))
1136 };
1137 let execution_start = Instant::now();
1138 let ctx =
1139 self.execution_ctx_for(input).map_err(|e| InsertBlockErrorKind::Other(Box::new(e)))?;
1140 let (output, senders, built_bal) = crate::tree::payload_processor::bal::execute_block(
1141 &self.runtime,
1142 &self.evm_config,
1143 &make_db,
1144 input_bal,
1145 env.evm_env,
1146 ctx,
1147 env.transaction_count,
1148 handle.clone_transaction_receiver(),
1149 receipt_tx,
1150 )?;
1151 let execution_duration = execution_start.elapsed();
1152
1153 self.metrics.record_block_execution(&output, execution_duration);
1154 self.metrics.record_block_execution_gas_bucket(output.result.gas_used, execution_duration);
1155 debug!(
1156 target: "engine::tree::payload_validator",
1157 elapsed = ?execution_duration,
1158 "Executed block via BAL path",
1159 );
1160
1161 Ok((output, senders, result_rx, Some(built_bal)))
1162 }
1163
1164 fn spawn_receipt_root_task(
1165 &self,
1166 receipts_len: usize,
1167 ) -> (ReceiptRootSender<N>, ReceiptRootReceiver) {
1168 let (receipt_tx, receipt_rx) = crossbeam_channel::unbounded();
1170 let (result_tx, result_rx) = tokio::sync::oneshot::channel();
1171 let task_handle = ReceiptRootTaskHandle::new(receipt_rx, result_tx);
1172 self.runtime.spawn_blocking_named("receipt-root", move || task_handle.run(receipts_len));
1173
1174 (receipt_tx, result_rx)
1175 }
1176
1177 fn execute_transactions<'a, E, Tx, InnerTx, Err, DB>(
1187 &self,
1188 mut executor: E,
1189 transaction_count: usize,
1190 transactions: impl Iterator<Item = Result<Tx, Err>>,
1191 receipt_tx: &crossbeam_channel::Sender<IndexedReceipt<N::Receipt>>,
1192 executed_tx_index: &AtomicUsize,
1193 has_bal: bool,
1194 ) -> Result<(E, Vec<Address>), BlockExecutionError>
1195 where
1196 E: BlockExecutor<Receipt = N::Receipt, Evm: alloy_evm::Evm<DB = &'a mut State<DB>>>,
1197 Tx: alloy_evm::block::ExecutableTx<E> + alloy_evm::RecoveredTx<InnerTx>,
1198 InnerTx: TxHashRef,
1199 DB: revm::Database + 'a,
1200 Err: core::error::Error + Send + Sync + 'static,
1201 {
1202 let mut senders = Vec::with_capacity(transaction_count);
1203
1204 let pre_exec_start = Instant::now();
1206 debug_span!(target: "engine::tree", "pre_execution")
1207 .in_scope(|| executor.apply_pre_execution_changes())?;
1208 self.metrics.record_pre_execution(pre_exec_start.elapsed());
1209
1210 if has_bal {
1212 executor.evm_mut().db_mut().bump_bal_index();
1213 }
1214
1215 let exec_span = debug_span!(target: "engine::tree", "execution").entered();
1217 let mut transactions = transactions.into_iter();
1218 let mut last_sent_len = 0usize;
1223 loop {
1224 let wait_start = Instant::now();
1227 let Some(tx_result) = transactions.next() else { break };
1228 self.metrics.record_transaction_wait(wait_start.elapsed());
1229
1230 let tx = tx_result.map_err(BlockExecutionError::other)?;
1231 let tx_signer = *<Tx as alloy_evm::RecoveredTx<InnerTx>>::signer(&tx);
1232
1233 senders.push(tx_signer);
1234
1235 let _enter = tracing::enabled!(target: "engine::tree", Level::TRACE).then(|| {
1236 tracing::trace_span!(
1237 target: "engine::tree",
1238 "execute tx",
1239 tx_index = senders.len() - 1,
1240 )
1241 .entered()
1242 });
1243 if tracing::enabled!(target: "engine::tree", Level::TRACE) {
1244 trace!(target: "engine::tree", "Executing transaction");
1245 }
1246
1247 let tx_start = Instant::now();
1248 executor.execute_transaction(tx)?;
1249 self.metrics.record_transaction_execution(tx_start.elapsed());
1250
1251 executed_tx_index.store(senders.len(), Ordering::Relaxed);
1253
1254 let current_len = executor.receipts().len();
1255 if current_len > last_sent_len {
1256 last_sent_len = current_len;
1257 if let Some(receipt) = executor.receipts().last() {
1259 let tx_index = current_len - 1;
1260 let _ = receipt_tx.send(IndexedReceipt::new(tx_index, receipt.clone()));
1261 }
1262 }
1263 if has_bal {
1265 executor.evm_mut().db_mut().bump_bal_index();
1266 }
1267 }
1268
1269 drop(exec_span);
1270
1271 Ok((executor, senders))
1272 }
1273
1274 #[instrument(level = "debug", target = "engine::tree::payload_validator", skip_all)]
1286 fn validate_post_execution<T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>>(
1287 &self,
1288 block: &RecoveredBlock<N::Block>,
1289 parent_block: &SealedHeader<N::BlockHeader>,
1290 output: &BlockExecutionOutput<N::Receipt>,
1291 ctx: &mut TreeCtx<'_, N>,
1292 receipt_root_bloom: Option<ReceiptRootBloom>,
1293 built_bal: Option<BlockAccessList>,
1294 ) -> Result<(), InsertBlockErrorKind>
1295 where
1296 V: PayloadValidator<T, Block = N::Block>,
1297 {
1298 let start = Instant::now();
1299
1300 trace!(target: "engine::tree::payload_validator", block=?block.num_hash(), "Validating block consensus");
1301
1302 let _enter =
1304 debug_span!(target: "engine::tree::payload_validator", "validate_block_post_execution")
1305 .entered();
1306 let block_access_list_hash =
1307 built_bal.as_ref().map(|bal| compute_block_access_list_hash(bal));
1308
1309 if let Err(err) = self.consensus.validate_block_post_execution(
1310 block,
1311 output,
1312 receipt_root_bloom,
1313 block_access_list_hash,
1314 ) {
1315 self.on_invalid_block(parent_block, block, output, None, ctx.state_mut());
1317 return Err(err.into())
1318 }
1319 drop(_enter);
1320
1321 self.metrics
1323 .block_validation
1324 .post_execution_validation_duration
1325 .record(start.elapsed().as_secs_f64());
1326
1327 Ok(())
1328 }
1329
1330 #[instrument(
1335 level = "debug",
1336 target = "engine::tree::payload_validator",
1337 skip_all,
1338 fields(
1339 has_hint_stream = hint_stream.is_some(),
1340 has_hashed_update_stream = hashed_update_stream.is_some(),
1341 parallel_bal_execution
1342 )
1343 )]
1344 fn spawn_payload_processor<T: ExecutableTxIterator<Evm>>(
1345 &self,
1346 env: ExecutionEnv<Evm>,
1347 txs: T,
1348 provider_builder: StateProviderBuilder<N, P>,
1349 hint_stream: Option<StateRootHintStream>,
1350 hashed_update_stream: Option<StateRootUpdateStream>,
1351 parallel_bal_execution: bool,
1352 ) -> Result<
1353 PayloadHandle<
1354 impl ExecutableTxFor<Evm> + use<N, P, Evm, V, T>,
1355 impl core::error::Error + Send + Sync + 'static + use<N, P, Evm, V, T>,
1356 N::Receipt,
1357 >,
1358 InsertBlockErrorKind,
1359 > {
1360 let start = Instant::now();
1361 let handle = self.payload_processor.spawn_with_state_root_streams(
1362 env,
1363 txs,
1364 provider_builder,
1365 hint_stream,
1366 hashed_update_stream,
1367 parallel_bal_execution,
1368 );
1369
1370 self.metrics.block_validation.spawn_payload_processor.record(start.elapsed().as_secs_f64());
1371
1372 Ok(handle)
1373 }
1374
1375 fn state_provider_builder(
1380 &self,
1381 hash: B256,
1382 state: &EngineApiTreeState<N>,
1383 ) -> ProviderResult<Option<StateProviderBuilder<N, P>>> {
1384 if let Some((historical, blocks)) = state.tree_state.blocks_by_hash(hash) {
1385 debug!(target: "engine::tree::payload_validator", %hash, %historical, "found canonical state for block in memory, creating provider builder");
1386 return Ok(Some(StateProviderBuilder::new(
1388 self.provider.clone(),
1389 historical,
1390 Some(blocks),
1391 )))
1392 }
1393
1394 if let Some(header) = self.provider.header(hash)? {
1396 debug!(target: "engine::tree::payload_validator", %hash, number = %header.number(), "found canonical state for block in database, creating provider builder");
1397 return Ok(Some(StateProviderBuilder::new(self.provider.clone(), hash, None)))
1400 }
1401
1402 debug!(target: "engine::tree::payload_validator", %hash, "no canonical state found for block");
1403 Ok(None)
1404 }
1405
1406 fn on_invalid_block(
1408 &self,
1409 parent_header: &SealedHeader<N::BlockHeader>,
1410 block: &RecoveredBlock<N::Block>,
1411 output: &BlockExecutionOutput<N::Receipt>,
1412 trie_updates: Option<(&TrieUpdates, B256)>,
1413 state: &mut EngineApiTreeState<N>,
1414 ) {
1415 if state.invalid_headers.get(&block.hash()).is_some() {
1416 return
1418 }
1419 self.invalid_block_hook.on_invalid_block(parent_header, block, output, trie_updates);
1420 }
1421
1422 fn overlay_builder_for_parent(
1424 parent_hash: B256,
1425 state: &EngineApiTreeState<N>,
1426 changeset_cache: ChangesetCache,
1427 ) -> OverlayBuilder<N> {
1428 OverlayBuilder::new(parent_hash, changeset_cache)
1429 .with_state_trie_overlay_manager(state.tree_state.state_trie_overlays.clone())
1430 }
1431
1432 fn spawn_deferred_trie_task(
1445 &self,
1446 block: Arc<RecoveredBlock<N::Block>>,
1447 execution_outcome: Arc<BlockExecutionOutput<N::Receipt>>,
1448 hashed_state: LazyHashedPostState,
1449 trie_output: Arc<TrieUpdates>,
1450 changed_paths: Option<Arc<TriePrefixSetsMut>>,
1451 ) -> ExecutedBlock<N> {
1452 let hashed_state = match hashed_state.try_into_inner() {
1456 Ok(state) => state,
1457 Err(handle) => handle.get().clone(),
1458 };
1459 let (deferred_trie_data, deferred_trie_task) =
1460 LazyTrieData::pending(hashed_state, trie_output, changed_paths);
1461 let block_validation_metrics = self.metrics.block_validation.clone();
1462
1463 let block_number = block.number();
1465
1466 let compute_trie_input_task = move || {
1468 let _span = debug_span!(
1469 target: "engine::tree::payload_validator",
1470 "compute_trie_input_task",
1471 block_number
1472 )
1473 .entered();
1474
1475 let compute_start = Instant::now();
1476 let computed = deferred_trie_task.compute_and_publish();
1477 block_validation_metrics
1478 .deferred_trie_compute_duration
1479 .record(compute_start.elapsed().as_secs_f64());
1480
1481 block_validation_metrics
1483 .hashed_post_state_size
1484 .record(computed.sorted.hashed_state.total_len() as f64);
1485 block_validation_metrics
1486 .trie_updates_sorted_size
1487 .record(computed.sorted.trie_updates.total_len() as f64);
1488 };
1489
1490 self.runtime.spawn_blocking_named(DEFERRED_TRIE_WORKER_NAME, compute_trie_input_task);
1492
1493 ExecutedBlock::with_deferred_trie_data(block, execution_outcome, deferred_trie_data)
1494 }
1495
1496 fn calculate_timing_stats(
1497 &self,
1498 block: &RecoveredBlock<N::Block>,
1499 provider_stats: Arc<StateProviderStats>,
1500 cache_stats: Option<Arc<CacheStats>>,
1501 output: &BlockExecutionOutput<N::Receipt>,
1502 execution_duration: Duration,
1503 state_hash_duration: Duration,
1504 ) -> Box<ExecutionTimingStats> {
1505 let accounts_read = provider_stats.total_account_fetches();
1506 let storage_read = provider_stats.total_storage_fetches();
1507 let code_read = provider_stats.total_code_fetches();
1508 let code_bytes_read = provider_stats.total_code_fetched_bytes();
1509
1510 let accounts_changed = output.state.state.len();
1512 let accounts_deleted =
1513 output.state.state.values().filter(|acc| acc.was_destroyed()).count();
1514 let storage_slots_changed =
1515 output.state.state.values().map(|account| account.storage.len()).sum::<usize>();
1516 let storage_slots_deleted = output
1517 .state
1518 .state
1519 .values()
1520 .flat_map(|account| account.storage.values())
1521 .filter(|slot| {
1522 slot.present_value.is_zero() && !slot.previous_or_original_value.is_zero()
1523 })
1524 .count();
1525
1526 let is_new_deployment = |acc: &BundleAccount| -> bool {
1528 let has_code_now = acc.info.as_ref().is_some_and(|info| info.code_hash != KECCAK_EMPTY);
1529 let had_no_code_before = acc
1530 .original_info
1531 .as_ref()
1532 .map(|info| info.code_hash == KECCAK_EMPTY)
1533 .unwrap_or(true);
1534 has_code_now && had_no_code_before
1535 };
1536
1537 let bytecodes_changed =
1538 output.state.state.values().filter(|acc| is_new_deployment(acc)).count();
1539
1540 let unique_new_code_hashes: B256Set = output
1542 .state
1543 .state
1544 .values()
1545 .filter(|acc| is_new_deployment(acc))
1546 .filter_map(|acc| acc.info.as_ref().map(|info| info.code_hash))
1547 .collect();
1548 let code_bytes_written: usize = unique_new_code_hashes
1549 .iter()
1550 .filter_map(|hash| {
1551 output.state.contracts.get(hash).map(|bytecode| bytecode.original_bytes().len())
1552 })
1553 .sum();
1554
1555 let state_read_duration = provider_stats.total_account_fetch_latency() +
1557 provider_stats.total_storage_fetch_latency() +
1558 provider_stats.total_code_fetch_latency();
1559
1560 let eip7702_delegations_set =
1563 output.state.contracts.values().filter(|bytecode| bytecode.is_eip7702()).count();
1564 let eip7702_delegations_cleared = output
1569 .state
1570 .state
1571 .values()
1572 .filter(|acc| {
1573 let original_was_eip7702 = acc
1575 .original_info
1576 .as_ref()
1577 .and_then(|info| info.code.as_ref())
1578 .map(|bytecode| bytecode.is_eip7702())
1579 .unwrap_or(false);
1580
1581 let code_now_empty =
1583 acc.info.as_ref().map(|info| info.code_hash == KECCAK_EMPTY).unwrap_or(false);
1584
1585 original_was_eip7702 && code_now_empty
1586 })
1587 .count();
1588
1589 let (account_cache_hits, account_cache_misses) = cache_stats
1591 .as_ref()
1592 .map(|s| (s.account_hits(), s.account_misses()))
1593 .unwrap_or_default();
1594 let (storage_cache_hits, storage_cache_misses) = cache_stats
1595 .as_ref()
1596 .map(|s| (s.storage_hits(), s.storage_misses()))
1597 .unwrap_or_default();
1598 let (code_cache_hits, code_cache_misses) =
1599 cache_stats.as_ref().map(|s| (s.code_hits(), s.code_misses())).unwrap_or_default();
1600
1601 Box::new(ExecutionTimingStats {
1603 block_number: block.number(),
1604 block_hash: block.hash(),
1605 gas_used: output.result.gas_used,
1606 tx_count: block.transaction_count(),
1607 execution_duration,
1608 state_read_duration,
1609 state_hash_duration,
1610 accounts_read,
1611 storage_read,
1612 code_read,
1613 code_bytes_read,
1614 accounts_changed,
1615 accounts_deleted,
1616 storage_slots_changed,
1617 storage_slots_deleted,
1618 bytecodes_changed,
1619 code_bytes_written,
1620 eip7702_delegations_set,
1621 eip7702_delegations_cleared,
1622 account_cache_hits,
1623 account_cache_misses,
1624 storage_cache_hits,
1625 storage_cache_misses,
1626 code_cache_hits,
1627 code_cache_misses,
1628 })
1629 }
1630}
1631
1632pub trait EngineValidator<
1636 Types: PayloadTypes,
1637 N: NodePrimitives = <<Types as PayloadTypes>::BuiltPayload as BuiltPayload>::Primitives,
1638>: Send + Sync + 'static
1639{
1640 fn validate_payload_attributes_against_header(
1650 &self,
1651 attr: &Types::PayloadAttributes,
1652 header: &N::BlockHeader,
1653 ) -> Result<(), InvalidPayloadAttributesError>;
1654
1655 fn convert_payload_to_block(
1664 &self,
1665 payload: Types::ExecutionData,
1666 ) -> Result<SealedBlock<N::Block>, NewPayloadError>;
1667
1668 fn validate_payload(
1670 &mut self,
1671 payload: Types::ExecutionData,
1672 ctx: TreeCtx<'_, N>,
1673 ) -> ValidationOutcome<N>;
1674
1675 fn validate_block(
1677 &mut self,
1678 block: SealedBlock<N::Block>,
1679 ctx: TreeCtx<'_, N>,
1680 ) -> ValidationOutcome<N>;
1681
1682 fn on_inserted_executed_block(
1687 &self,
1688 block: BuiltPayloadExecutedBlock<N>,
1689 ) -> ProviderResult<ExecutedBlock<N>>;
1690
1691 fn cache_for(&self, _block_hash: B256) -> Option<SavedCache>;
1693
1694 fn payload_state_root_handle_for(
1703 &self,
1704 parent_hash: B256,
1705 parent_header: &N::BlockHeader,
1706 timestamp: u64,
1707 state: &mut EngineApiTreeState<N>,
1708 ) -> Option<PayloadStateRootHandle>;
1709}
1710
1711impl<N, Types, P, Evm, V> EngineValidator<Types> for BasicEngineValidator<P, Evm, V>
1712where
1713 P: DatabaseProviderFactory<
1714 Provider: BlockReader
1715 + StageCheckpointReader
1716 + PruneCheckpointReader
1717 + ChangeSetReader
1718 + StorageChangeSetReader
1719 + BlockNumReader
1720 + StorageSettingsCache,
1721 > + BlockReader<Header = N::BlockHeader>
1722 + StateProviderFactory
1723 + StateReader
1724 + ChangeSetReader
1725 + BlockNumReader
1726 + HashedPostStateProvider
1727 + Clone
1728 + 'static,
1729 OverlayStateProviderFactory<P, N>: DatabaseProviderROFactory<Provider: TrieCursorFactory + HashedCursorFactory>
1730 + Clone
1731 + Send
1732 + Sync
1733 + 'static,
1734 N: NodePrimitives,
1735 V: PayloadValidator<Types, Block = N::Block> + Clone,
1736 Evm: ConfigureEngineEvm<Types::ExecutionData, Primitives = N> + 'static,
1737 Types: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>,
1738{
1739 fn validate_payload_attributes_against_header(
1740 &self,
1741 attr: &Types::PayloadAttributes,
1742 header: &N::BlockHeader,
1743 ) -> Result<(), InvalidPayloadAttributesError> {
1744 self.validator.validate_payload_attributes_against_header(attr, header)
1745 }
1746
1747 fn convert_payload_to_block(
1748 &self,
1749 payload: Types::ExecutionData,
1750 ) -> Result<SealedBlock<N::Block>, NewPayloadError> {
1751 let block = self.validator.convert_payload_to_block(payload)?;
1752 Ok(block)
1753 }
1754
1755 fn validate_payload(
1756 &mut self,
1757 payload: Types::ExecutionData,
1758 ctx: TreeCtx<'_, N>,
1759 ) -> ValidationOutcome<N> {
1760 self.validate_block_with_state(BlockOrPayload::Payload(payload), ctx)
1761 }
1762
1763 fn validate_block(
1764 &mut self,
1765 block: SealedBlock<N::Block>,
1766 ctx: TreeCtx<'_, N>,
1767 ) -> ValidationOutcome<N> {
1768 self.validate_block_with_state(BlockOrPayload::Block(block), ctx)
1769 }
1770
1771 fn on_inserted_executed_block(
1772 &self,
1773 block: BuiltPayloadExecutedBlock<N>,
1774 ) -> ProviderResult<ExecutedBlock<N>> {
1775 self.payload_processor.on_inserted_executed_block(
1776 block.recovered_block.block_with_parent(),
1777 &block.execution_output.state,
1778 );
1779
1780 Ok(self.spawn_deferred_trie_task(
1781 block.recovered_block,
1782 block.execution_output,
1783 LazyHashedPostState::ready(block.hashed_state),
1784 block.trie_updates,
1785 block.changed_paths,
1786 ))
1787 }
1788
1789 fn cache_for(&self, block_hash: B256) -> Option<SavedCache> {
1790 Some(self.payload_processor.cache_for(block_hash))
1791 }
1792
1793 fn payload_state_root_handle_for(
1794 &self,
1795 parent_hash: B256,
1796 parent_header: &N::BlockHeader,
1797 timestamp: u64,
1798 state: &mut EngineApiTreeState<N>,
1799 ) -> Option<PayloadStateRootHandle> {
1800 let provider_builder = match self.state_provider_builder(parent_hash, state) {
1801 Ok(Some(provider_builder)) => provider_builder,
1802 Ok(None) => return None,
1803 Err(err) => {
1804 warn!(
1805 target: "engine::tree::payload_validator",
1806 %err,
1807 %parent_hash,
1808 "failed to prepare payload-builder state-root provider"
1809 );
1810 return None
1811 }
1812 };
1813 let overlay_factory = OverlayStateProviderFactory::new(
1814 self.provider.clone(),
1815 Self::overlay_builder_for_parent(parent_hash, state, self.changeset_cache.clone()),
1816 );
1817
1818 match self.state_root_strategy.prepare_payload_builder(PayloadStateRootJobContext::new(
1819 &self.runtime,
1820 &self.state_trie_overlays,
1821 parent_hash,
1822 parent_header,
1823 timestamp,
1824 state,
1825 provider_builder,
1826 overlay_factory,
1827 &self.config,
1828 )) {
1829 Ok(handle) => handle,
1830 Err(err) => {
1831 warn!(
1832 target: "engine::tree::payload_validator",
1833 %err,
1834 %parent_hash,
1835 "failed to prepare payload-builder state-root job"
1836 );
1837 None
1838 }
1839 }
1840 }
1841}
1842
1843impl<P, Evm, V> WaitForCaches for BasicEngineValidator<P, Evm, V>
1844where
1845 Evm: ConfigureEvm,
1846{
1847 fn wait_for_caches(&self) -> CacheWaitDurations {
1848 debug!(target: "engine::tree::payload_validator", "Waiting for execution cache and sparse trie locks");
1849
1850 let execution_cache = self.payload_processor.execution_cache();
1851 let state_trie_overlays = self.state_trie_overlays.clone();
1852 let (execution_tx, execution_rx) = std::sync::mpsc::channel();
1853 let (sparse_trie_tx, sparse_trie_rx) = std::sync::mpsc::channel();
1854
1855 self.runtime.spawn_blocking_named("wait-exec-cache", move || {
1856 let _ = execution_tx.send(execution_cache.wait_for_availability());
1857 });
1858 self.runtime.spawn_blocking_named("wait-sparse-tri", move || {
1859 let _ = sparse_trie_tx.send(state_trie_overlays.wait_for_sparse_trie_availability());
1860 });
1861
1862 let execution_cache =
1863 execution_rx.recv().expect("execution cache wait task failed to send result");
1864 let sparse_trie =
1865 sparse_trie_rx.recv().expect("sparse trie wait task failed to send result");
1866 debug!(
1867 target: "engine::tree::payload_validator",
1868 ?execution_cache,
1869 ?sparse_trie,
1870 "Execution cache and sparse trie locks acquired"
1871 );
1872 CacheWaitDurations { execution_cache, sparse_trie }
1873 }
1874}
1875
1876#[derive(Debug, Clone)]
1878pub enum BlockOrPayload<T: PayloadTypes> {
1879 Payload(T::ExecutionData),
1881 Block(SealedBlock<BlockTy<<T::BuiltPayload as BuiltPayload>::Primitives>>),
1883}
1884
1885impl<T: PayloadTypes> BlockOrPayload<T> {
1886 pub fn hash(&self) -> B256 {
1888 match self {
1889 Self::Payload(payload) => payload.block_hash(),
1890 Self::Block(block) => block.hash(),
1891 }
1892 }
1893
1894 pub fn num_hash(&self) -> NumHash {
1896 match self {
1897 Self::Payload(payload) => payload.num_hash(),
1898 Self::Block(block) => block.num_hash(),
1899 }
1900 }
1901
1902 pub fn parent_hash(&self) -> B256 {
1904 match self {
1905 Self::Payload(payload) => payload.parent_hash(),
1906 Self::Block(block) => block.parent_hash(),
1907 }
1908 }
1909
1910 pub fn block_with_parent(&self) -> BlockWithParent {
1912 match self {
1913 Self::Payload(payload) => payload.block_with_parent(),
1914 Self::Block(block) => block.block_with_parent(),
1915 }
1916 }
1917
1918 pub const fn type_name(&self) -> &'static str {
1920 match self {
1921 Self::Payload(_) => "payload",
1922 Self::Block(_) => "block",
1923 }
1924 }
1925
1926 pub const fn is_payload(&self) -> bool {
1928 matches!(self, Self::Payload(_))
1929 }
1930
1931 pub const fn is_block(&self) -> bool {
1933 matches!(self, Self::Block(_))
1934 }
1935
1936 pub fn try_decoded_access_list(&self) -> Result<Option<DecodedBal>, alloy_rlp::Error> {
1938 match self {
1939 Self::Payload(payload) => payload
1940 .block_access_list()
1941 .map(|block_access_list| DecodedBal::from_rlp_bytes(block_access_list.clone()))
1942 .transpose(),
1943 Self::Block(_) => Ok(None),
1944 }
1945 }
1946
1947 pub fn transaction_count(&self) -> usize
1949 where
1950 T::ExecutionData: ExecutionPayload,
1951 {
1952 match self {
1953 Self::Payload(payload) => payload.transaction_count(),
1954 Self::Block(block) => block.transaction_count(),
1955 }
1956 }
1957
1958 pub fn withdrawals(&self) -> Option<&[Withdrawal]>
1960 where
1961 T::ExecutionData: ExecutionPayload,
1962 {
1963 match self {
1964 Self::Payload(payload) => payload.withdrawals().map(|w| w.as_slice()),
1965 Self::Block(block) => block.body().withdrawals().map(|w| w.as_slice()),
1966 }
1967 }
1968
1969 pub fn gas_used(&self) -> u64
1971 where
1972 T::ExecutionData: ExecutionPayload,
1973 {
1974 match self {
1975 Self::Payload(payload) => payload.gas_used(),
1976 Self::Block(block) => block.gas_used(),
1977 }
1978 }
1979
1980 pub fn gas_limit(&self) -> u64
1982 where
1983 T::ExecutionData: ExecutionPayload,
1984 {
1985 match self {
1986 Self::Payload(payload) => payload.gas_limit(),
1987 Self::Block(block) => block.gas_limit(),
1988 }
1989 }
1990}