1use crate::{
2 backfill::{BackfillAction, BackfillSyncState},
3 chain::FromOrchestrator,
4 engine::{DownloadRequest, EngineApiEvent, EngineApiKind, EngineApiRequest, FromEngine},
5 persistence::PersistenceHandle,
6 tree::{error::InsertPayloadError, payload_validator::TreeCtx},
7};
8use alloy_consensus::BlockHeader;
9use alloy_eips::{eip1898::BlockWithParent, merge::EPOCH_SLOTS, BlockNumHash, NumHash};
10use alloy_primitives::{map::B256Map, B256};
11use alloy_rpc_types_engine::{
12 ForkchoiceState, PayloadStatus, PayloadStatusEnum, PayloadValidationError,
13};
14use error::{InsertBlockError, InsertBlockFatalError, InsertBlockValidationError};
15use reth_chain_state::{
16 CanonicalInMemoryState, ExecutedBlock, ExecutionTimingStats, MemoryOverlayStateProvider,
17 NewCanonicalChain,
18};
19use reth_consensus::{Consensus, FullConsensus};
20use reth_engine_primitives::{
21 BeaconEngineMessage, ConsensusEngineEvent, ExecutionPayload, ForkchoiceStateTracker,
22 NewPayloadTimings, OnForkChoiceUpdated, SlowBlockInfo,
23};
24use reth_errors::{ConsensusError, ProviderResult};
25use reth_evm::ConfigureEvm;
26use reth_payload_builder::{BuildNewPayload, PayloadBuilderHandle, PayloadBuilderLease};
27use reth_payload_primitives::{BuiltPayload, NewPayloadError, PayloadAttributes, PayloadTypes};
28use reth_primitives_traits::{
29 FastInstant as Instant, NodePrimitives, RecoveredBlock, SealedBlock, SealedHeader,
30};
31use reth_provider::{
32 BalProvider, BlockExecutionOutput, BlockExecutionResult, BlockNumReader, BlockReader,
33 ChangeSetReader, DatabaseProviderFactory, LatestStateProvider, ProviderError,
34 PruneCheckpointReader, SaveBlocksInput, StageCheckpointReader, StateProviderBox,
35 StateProviderFactory, StateReader, StorageChangeSetReader, StorageSettingsCache,
36 TransactionVariant, TryIntoHistoricalStateProvider,
37};
38use reth_revm::database::StateProviderDatabase;
39use reth_stages_api::ControlFlow;
40use reth_storage_overlay::OverlayManager;
41use reth_tasks::{spawn_os_thread, utils::increase_thread_priority};
42use reth_trie::ComputedTrieData;
43use revm::interpreter::debug_unreachable;
44use state::TreeState;
45use std::{
46 fmt::Debug,
47 ops,
48 sync::{
49 atomic::{AtomicUsize, Ordering},
50 Arc,
51 },
52 time::Duration,
53};
54
55use crossbeam_channel::{Receiver, Sender};
56use tokio::sync::{
57 mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
58 oneshot,
59};
60use tracing::*;
61
62mod block_buffer;
63pub mod error;
64pub mod instrumented_state;
65mod invalid_headers;
66mod metrics;
67pub mod payload_processor;
68pub mod payload_validator;
69mod persistence_state;
70pub mod precompile_cache;
71pub mod state_root_strategy;
72#[cfg(test)]
73mod tests;
74mod trie_updates;
75mod txpool_prewarm;
76pub mod types;
77
78use crate::{persistence::PersistenceResult, tree::error::AdvancePersistenceError};
79pub use block_buffer::BlockBuffer;
80pub use invalid_headers::InvalidHeaderCache;
81pub use metrics::EngineApiMetrics;
82pub use payload_processor::*;
83pub use payload_validator::{BasicEngineValidator, EngineValidator};
84pub use persistence_state::PersistenceState;
85pub use reth_engine_primitives::TreeConfig;
86pub use reth_execution_cache::{
87 CachedStateCacheMetrics, CachedStateMetrics, CachedStateMetricsSource, CachedStateProvider,
88 ExecutionCache, PayloadExecutionCache, SavedCache, TxPoolPrewarmCacheSnapshot,
89};
90pub use txpool_prewarm::{
91 Source as TxPoolPrewarmSource, Transaction as TxPoolPrewarmTransaction,
92 Transactions as TxPoolPrewarmTransactions,
93};
94pub use types::{ExecutionEnv, ValidationOutcome, ValidationOutput};
95
96pub mod state;
97
98pub(crate) const MIN_BLOCKS_FOR_PIPELINE_RUN: u64 = EPOCH_SLOTS;
108
109const CHANGESET_CACHE_RETENTION_BLOCKS: u64 = 64;
114
115#[derive(Clone, Debug)]
117pub struct StateProviderBuilder<N: NodePrimitives, P> {
118 provider_factory: P,
120 parent_hash: B256,
122 overlay_manager: OverlayManager<N>,
124}
125
126impl<N: NodePrimitives, P> StateProviderBuilder<N, P> {
127 pub const fn new(
129 provider_factory: P,
130 parent_hash: B256,
131 overlay_manager: OverlayManager<N>,
132 ) -> Self {
133 Self { provider_factory, parent_hash, overlay_manager }
134 }
135}
136
137impl<N: NodePrimitives, P> StateProviderBuilder<N, P>
138where
139 P: DatabaseProviderFactory,
140 P::Provider: BlockNumReader
141 + PruneCheckpointReader
142 + StageCheckpointReader
143 + StorageSettingsCache
144 + TryIntoHistoricalStateProvider
145 + 'static,
146{
147 pub fn build(&self) -> ProviderResult<StateProviderBox> {
149 let overlay_builder = self.overlay_manager.overlay_builder(self.parent_hash);
150 let provider = self.provider_factory.database_provider_ro()?;
151 let anchor = overlay_builder.anchor_at_parent(&provider)?;
152 let (provider, overlay): (StateProviderBox, _) = match anchor {
153 reth_storage_overlay::AnchorForParent::NoReverts { anchor, overlay } => {
154 debug!(
155 target: "engine::tree",
156 parent_hash = %self.parent_hash,
157 ?anchor,
158 "creating state provider from latest state"
159 );
160 (Box::new(LatestStateProvider::new(provider)), overlay)
161 }
162 reth_storage_overlay::AnchorForParent::RevertsRequired { anchor, overlay, .. } => {
163 debug!(
164 target: "engine::tree",
165 parent_hash = %self.parent_hash,
166 ?anchor,
167 "creating state provider from historical state"
168 );
169 (provider.try_into_history_at_block(anchor.number)?, overlay)
170 }
171 };
172 Ok(Box::new(MemoryOverlayStateProvider::new(provider, overlay)))
173 }
174}
175
176#[derive(Debug)]
180pub struct EngineApiTreeState<N: NodePrimitives> {
181 tree_state: TreeState<N>,
183 pending_sparse_trie_prune: bool,
185 forkchoice_state_tracker: ForkchoiceStateTracker,
187 buffer: BlockBuffer<N::Block>,
189 invalid_headers: InvalidHeaderCache,
192}
193
194impl<N: NodePrimitives> EngineApiTreeState<N> {
195 fn new(
196 block_buffer_limit: u32,
197 max_invalid_header_cache_length: u32,
198 invalid_header_hit_eviction_threshold: u8,
199 canonical_block: BlockNumHash,
200 engine_kind: EngineApiKind,
201 overlay_manager: OverlayManager<N>,
202 ) -> Self {
203 Self {
204 invalid_headers: InvalidHeaderCache::new(
205 max_invalid_header_cache_length,
206 invalid_header_hit_eviction_threshold,
207 ),
208 buffer: BlockBuffer::new(block_buffer_limit),
209 tree_state: TreeState::new(canonical_block, engine_kind, overlay_manager),
210 pending_sparse_trie_prune: false,
211 forkchoice_state_tracker: ForkchoiceStateTracker::default(),
212 }
213 }
214
215 pub const fn tree_state(&self) -> &TreeState<N> {
217 &self.tree_state
218 }
219
220 pub const fn pending_sparse_trie_prune(&self) -> bool {
222 self.pending_sparse_trie_prune
223 }
224
225 pub const fn set_pending_sparse_trie_prune(&mut self, pending: bool) {
227 self.pending_sparse_trie_prune = pending;
228 }
229
230 pub fn take_sparse_trie_prune_blocks(
237 &mut self,
238 parent_hash: B256,
239 ) -> Option<Vec<ExecutedBlock<N>>> {
240 if !self.pending_sparse_trie_prune {
241 return None
242 }
243
244 self.pending_sparse_trie_prune = false;
245 Some(
246 self.tree_state
247 .blocks_by_hash(parent_hash)
248 .map(|(_, blocks)| blocks)
249 .unwrap_or_default(),
250 )
251 }
252
253 pub fn has_invalid_header(&mut self, hash: &B256) -> bool {
255 self.invalid_headers.get(hash).is_some()
256 }
257}
258
259#[derive(Debug)]
261pub struct TreeOutcome<T> {
262 pub outcome: T,
264 pub event: Option<TreeEvent>,
266 pub already_seen: bool,
269}
270
271impl<T> TreeOutcome<T> {
272 pub const fn new(outcome: T) -> Self {
274 Self { outcome, event: None, already_seen: false }
275 }
276
277 pub fn with_event(mut self, event: TreeEvent) -> Self {
279 self.event = Some(event);
280 self
281 }
282
283 pub const fn with_already_seen(mut self, value: bool) -> Self {
285 self.already_seen = value;
286 self
287 }
288}
289
290#[derive(Debug)]
292pub struct TryInsertPayloadResult {
293 pub status: PayloadStatus,
297 pub already_seen: bool,
299}
300
301impl TryInsertPayloadResult {
302 #[inline]
304 pub fn into_outcome(self) -> TreeOutcome<PayloadStatus> {
305 TreeOutcome::new(self.status).with_already_seen(self.already_seen)
306 }
307}
308
309#[derive(Debug)]
311pub enum TreeEvent {
312 TreeAction(TreeAction),
314 BackfillAction(BackfillAction),
316 Download(DownloadRequest),
318}
319
320impl TreeEvent {
321 const fn is_backfill_action(&self) -> bool {
323 matches!(self, Self::BackfillAction(_))
324 }
325}
326
327#[derive(Debug)]
329pub enum TreeAction {
330 MakeCanonical {
332 sync_target_head: B256,
334 },
335}
336
337pub struct EngineApiTreeHandler<N, P, T, V, C>
342where
343 N: NodePrimitives,
344 T: PayloadTypes,
345 C: ConfigureEvm<Primitives = N> + 'static,
346{
347 provider: P,
348 consensus: Arc<dyn FullConsensus<N>>,
349 payload_validator: V,
350 state: EngineApiTreeState<N>,
352 incoming_tx: Sender<FromEngine<EngineApiRequest<T, N>, N::Block>>,
361 incoming: Receiver<FromEngine<EngineApiRequest<T, N>, N::Block>>,
363 outgoing: UnboundedSender<EngineApiEvent<N>>,
365 persistence: PersistenceHandle<N>,
367 persistence_state: PersistenceState,
369 backfill_sync_state: BackfillSyncState,
371 canonical_in_memory_state: CanonicalInMemoryState<N>,
374 payload_builder: PayloadBuilderHandle<T>,
377 config: TreeConfig,
379 metrics: EngineApiMetrics,
381 engine_kind: EngineApiKind,
383 evm_config: C,
385 execution_timing_stats: B256Map<Box<ExecutionTimingStats>>,
389 payload_builds: PayloadBuildTracker,
391 payload_build_finished: Receiver<()>,
393 pending_persisted_handoff: Option<PersistenceResult>,
396 runtime: reth_tasks::Runtime,
398}
399
400impl<N, P: Debug, T: PayloadTypes + Debug, V: Debug, C> std::fmt::Debug
401 for EngineApiTreeHandler<N, P, T, V, C>
402where
403 N: NodePrimitives,
404 C: Debug + ConfigureEvm<Primitives = N>,
405{
406 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
407 f.debug_struct("EngineApiTreeHandler")
408 .field("provider", &self.provider)
409 .field("consensus", &self.consensus)
410 .field("payload_validator", &self.payload_validator)
411 .field("state", &self.state)
412 .field("incoming_tx", &self.incoming_tx)
413 .field("persistence", &self.persistence)
414 .field("persistence_state", &self.persistence_state)
415 .field("backfill_sync_state", &self.backfill_sync_state)
416 .field("canonical_in_memory_state", &self.canonical_in_memory_state)
417 .field("payload_builder", &self.payload_builder)
418 .field("config", &self.config)
419 .field("metrics", &self.metrics)
420 .field("engine_kind", &self.engine_kind)
421 .field("evm_config", &self.evm_config)
422 .field("execution_timing_stats", &self.execution_timing_stats.len())
423 .field("payload_builds_active", &self.payload_builds.is_active())
424 .field("pending_persisted_handoff", &self.pending_persisted_handoff)
425 .field("runtime", &self.runtime)
426 .finish()
427 }
428}
429
430impl<N, P, T, V, C> EngineApiTreeHandler<N, P, T, V, C>
431where
432 N: NodePrimitives,
433 P: DatabaseProviderFactory
434 + BlockReader<Block = N::Block, Header = N::BlockHeader>
435 + StateProviderFactory
436 + StateReader<Receipt = N::Receipt>
437 + BalProvider
438 + Clone
439 + 'static,
440 P::Provider: BlockReader<Block = N::Block, Header = N::BlockHeader>
441 + PruneCheckpointReader
442 + StageCheckpointReader
443 + ChangeSetReader
444 + StorageChangeSetReader
445 + StorageSettingsCache
446 + TryIntoHistoricalStateProvider
447 + 'static,
448 C: ConfigureEvm<Primitives = N> + 'static,
449 T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>,
450 V: EngineValidator<T> + WaitForCaches,
451{
452 #[expect(clippy::too_many_arguments)]
454 pub fn new(
455 provider: P,
456 consensus: Arc<dyn FullConsensus<N>>,
457 payload_validator: V,
458 outgoing: UnboundedSender<EngineApiEvent<N>>,
459 state: EngineApiTreeState<N>,
460 canonical_in_memory_state: CanonicalInMemoryState<N>,
461 persistence: PersistenceHandle<N>,
462 persistence_state: PersistenceState,
463 payload_builder: PayloadBuilderHandle<T>,
464 config: TreeConfig,
465 engine_kind: EngineApiKind,
466 evm_config: C,
467 runtime: reth_tasks::Runtime,
468 ) -> Self {
469 let (incoming_tx, incoming) = crossbeam_channel::unbounded();
470
471 let (payload_builds, payload_build_finished) = PayloadBuildTracker::new();
472
473 Self {
474 provider,
475 consensus,
476 payload_validator,
477 incoming,
478 outgoing,
479 persistence,
480 persistence_state,
481 backfill_sync_state: BackfillSyncState::Idle,
482 state,
483 canonical_in_memory_state,
484 payload_builder,
485 config,
486 metrics: Default::default(),
487 incoming_tx,
488 engine_kind,
489 evm_config,
490 execution_timing_stats: B256Map::default(),
491 payload_builds,
492 payload_build_finished,
493 pending_persisted_handoff: None,
494 runtime,
495 }
496 }
497
498 #[expect(clippy::complexity)]
504 pub fn spawn_new(
505 provider: P,
506 consensus: Arc<dyn FullConsensus<N>>,
507 payload_validator: V,
508 persistence: PersistenceHandle<N>,
509 payload_builder: PayloadBuilderHandle<T>,
510 canonical_in_memory_state: CanonicalInMemoryState<N>,
511 overlay_manager: OverlayManager<N>,
512 config: TreeConfig,
513 kind: EngineApiKind,
514 evm_config: C,
515 runtime: reth_tasks::Runtime,
516 ) -> (Sender<FromEngine<EngineApiRequest<T, N>, N::Block>>, UnboundedReceiver<EngineApiEvent<N>>)
517 {
518 let best_block_number = provider.best_block_number().unwrap_or(0);
519 let header = provider.sealed_header(best_block_number).ok().flatten().unwrap_or_default();
520
521 let persistence_state = PersistenceState {
522 last_persisted_block: BlockNumHash::new(best_block_number, header.hash()),
523 last_state_trie_persisted_block: BlockNumHash::new(best_block_number, header.hash()),
524 rx: None,
525 };
526
527 let (tx, outgoing) = unbounded_channel();
528 let state = EngineApiTreeState::new(
529 config.block_buffer_limit(),
530 config.max_invalid_header_cache_length(),
531 config.invalid_header_hit_eviction_threshold(),
532 header.num_hash(),
533 kind,
534 overlay_manager,
535 );
536
537 let task = Self::new(
538 provider,
539 consensus,
540 payload_validator,
541 tx,
542 state,
543 canonical_in_memory_state,
544 persistence,
545 persistence_state,
546 payload_builder,
547 config,
548 kind,
549 evm_config,
550 runtime,
551 );
552 let incoming = task.incoming_tx.clone();
553 spawn_os_thread("engine", || {
554 increase_thread_priority();
555 task.run()
556 });
557 (incoming, outgoing)
558 }
559
560 fn valid_outcome(state: ForkchoiceState) -> TreeOutcome<OnForkChoiceUpdated> {
562 TreeOutcome::new(OnForkChoiceUpdated::valid(PayloadStatus::new(
563 PayloadStatusEnum::Valid,
564 Some(state.head_block_hash),
565 )))
566 }
567
568 pub fn sender(&self) -> Sender<FromEngine<EngineApiRequest<T, N>, N::Block>> {
570 self.incoming_tx.clone()
571 }
572
573 const fn persistence_gap(&self) -> u64 {
576 self.state
577 .tree_state
578 .canonical_block_number()
579 .saturating_sub(self.persistence_state.last_persisted_block.number)
580 }
581
582 const fn persistence_backpressure_gap(&self) -> u64 {
584 self.persistence_gap().saturating_sub(self.config.memory_block_buffer_target())
585 }
586
587 const fn should_backpressure(&self) -> bool {
592 self.persistence_state.in_progress() &&
593 self.persistence_backpressure_gap() >=
594 self.config.persistence_backpressure_threshold()
595 }
596
597 pub fn run(mut self) {
601 loop {
602 match self.try_poll_persistence() {
627 Ok(true) => {
628 if let Err(err) = self.advance_persistence() {
629 error!(target: "engine::tree", %err, "Advancing persistence failed");
630 return
631 }
632 continue;
633 }
634 Ok(false) => {}
635 Err(err) => {
636 error!(target: "engine::tree", %err, "Polling persistence failed");
637 return
638 }
639 }
640
641 let event = if self.should_backpressure() {
642 self.metrics.engine.backpressure_active.set(1.0);
643 let stall_start = Instant::now();
644 let event = self.wait_for_persistence_event();
645 self.metrics.engine.backpressure_stall_duration.record(stall_start.elapsed());
646 event
647 } else {
648 self.metrics.engine.backpressure_active.set(0.0);
649 self.wait_for_event()
650 };
651
652 match event {
653 LoopEvent::EngineMessage(msg) => {
654 debug!(target: "engine::tree", %msg, "received new engine message");
655 match self.on_engine_message(msg) {
656 Ok(ops::ControlFlow::Break(())) => return,
657 Ok(ops::ControlFlow::Continue(())) => {}
658 Err(fatal) => {
659 error!(target: "engine::tree", %fatal, "insert block fatal error");
660 return
661 }
662 }
663 }
664 LoopEvent::PersistenceComplete { result, start_time } => {
665 if let Err(err) = self.on_persistence_complete(result, start_time) {
666 error!(target: "engine::tree", %err, "Persistence complete handling failed");
667 return
668 }
669 }
670 LoopEvent::PayloadBuildFinished => {
671 if let Err(err) = self.on_payload_build_finished() {
672 error!(target: "engine::tree", %err, "Payload build completion handling failed");
673 return
674 }
675 }
676 LoopEvent::Disconnected => {
677 error!(target: "engine::tree", "Channel disconnected");
678 return
679 }
680 }
681
682 if let Err(err) = self.advance_persistence() {
687 error!(target: "engine::tree", %err, "Advancing persistence failed");
688 return
689 }
690 }
691 }
692
693 fn wait_for_persistence_event(&mut self) -> LoopEvent<T, N> {
699 let maybe_persistence = self.persistence_state.rx.take();
700
701 if let Some((persistence_rx, start_time, _action)) = maybe_persistence {
702 match persistence_rx.recv() {
703 Ok(result) => LoopEvent::PersistenceComplete { result, start_time },
704 Err(_) => LoopEvent::Disconnected,
705 }
706 } else {
707 self.wait_for_event()
708 }
709 }
710
711 fn wait_for_event(&mut self) -> LoopEvent<T, N> {
719 if self.pending_persisted_handoff.is_some() {
720 self.metrics.engine.backpressure_active.set(0.0);
721 return crossbeam_channel::select_biased! {
722 recv(self.payload_build_finished) -> result => match result {
723 Ok(()) => LoopEvent::PayloadBuildFinished,
724 Err(_) => LoopEvent::Disconnected,
725 },
726 recv(self.incoming) -> msg => match msg {
727 Ok(m) => LoopEvent::EngineMessage(m),
728 Err(_) => LoopEvent::Disconnected,
729 },
730 }
731 }
732
733 let maybe_persistence = self.persistence_state.rx.take();
735
736 if let Some((persistence_rx, start_time, action)) = maybe_persistence {
737 crossbeam_channel::select_biased! {
740 recv(persistence_rx) -> result => {
741 match result {
743 Ok(result) => LoopEvent::PersistenceComplete {
744 result,
745 start_time,
746 },
747 Err(_) => LoopEvent::Disconnected,
748 }
749 },
750 recv(self.payload_build_finished) -> result => {
751 self.persistence_state.rx = Some((persistence_rx, start_time, action));
753 match result {
754 Ok(()) => LoopEvent::PayloadBuildFinished,
755 Err(_) => LoopEvent::Disconnected,
756 }
757 },
758 recv(self.incoming) -> msg => {
759 self.persistence_state.rx = Some((persistence_rx, start_time, action));
761 match msg {
762 Ok(m) => LoopEvent::EngineMessage(m),
763 Err(_) => LoopEvent::Disconnected,
764 }
765 },
766 }
767 } else {
768 crossbeam_channel::select_biased! {
770 recv(self.payload_build_finished) -> result => match result {
771 Ok(()) => LoopEvent::PayloadBuildFinished,
772 Err(_) => LoopEvent::Disconnected,
773 },
774 recv(self.incoming) -> msg => match msg {
775 Ok(m) => LoopEvent::EngineMessage(m),
776 Err(_) => LoopEvent::Disconnected,
777 },
778 }
779 }
780 }
781
782 fn on_downloaded(
788 &mut self,
789 mut blocks: Vec<SealedBlock<N::Block>>,
790 ) -> Result<Option<TreeEvent>, InsertBlockFatalError> {
791 if blocks.is_empty() {
792 return Ok(None)
794 }
795
796 trace!(target: "engine::tree", block_count = %blocks.len(), "received downloaded blocks");
797 let batch = self.config.max_execute_block_batch_size().min(blocks.len());
798 for block in blocks.drain(..batch) {
799 if let Some(event) = self.on_downloaded_block(block)? {
800 let needs_backfill = event.is_backfill_action();
801 self.on_tree_event(event)?;
802 if needs_backfill {
803 return Ok(None)
805 }
806 }
807 }
808
809 if !blocks.is_empty() {
811 let _ = self.incoming_tx.send(FromEngine::DownloadedBlocks(blocks));
812 }
813
814 Ok(None)
815 }
816
817 #[instrument(
832 level = "debug",
833 target = "engine::tree",
834 skip_all,
835 fields(block_hash = %payload.block_hash(), block_num = %payload.block_number()),
836 )]
837 fn on_new_payload(
838 &mut self,
839 payload: T::ExecutionData,
840 ) -> Result<TreeOutcome<PayloadStatus>, InsertBlockFatalError> {
841 let _thread_resource_usage =
842 self.metrics.engine.new_payload.measure_thread_resource_usage();
843 trace!(target: "engine::tree", "invoked new payload");
844
845 let start = Instant::now();
847
848 let num_hash = payload.num_hash();
875 let engine_event = ConsensusEngineEvent::BlockReceived(num_hash);
876 self.emit_event(EngineApiEvent::BeaconConsensus(engine_event));
877
878 let block_hash = num_hash.hash;
879
880 if let Some(invalid) = self.find_invalid_ancestor(&payload) {
882 let status = self.handle_invalid_ancestor_payload(payload, invalid)?;
883 return Ok(TreeOutcome::new(status));
884 }
885
886 self.metrics.block_validation.record_payload_validation(start.elapsed().as_secs_f64());
888
889 let mut outcome = if self.backfill_sync_state.is_idle() {
890 self.try_insert_payload(payload)?.into_outcome()
891 } else {
892 TreeOutcome::new(self.try_buffer_payload(payload)?)
893 };
894
895 if outcome.outcome.is_valid() && self.is_sync_target_head(block_hash) {
897 if self.state.tree_state.canonical_block_hash() != block_hash {
899 outcome = outcome.with_event(TreeEvent::TreeAction(TreeAction::MakeCanonical {
900 sync_target_head: block_hash,
901 }));
902 }
903 }
904
905 self.metrics.block_validation.total_duration.record(start.elapsed().as_secs_f64());
907
908 Ok(outcome)
909 }
910
911 #[instrument(level = "debug", target = "engine::tree", skip_all)]
913 fn try_insert_payload(
914 &mut self,
915 payload: T::ExecutionData,
916 ) -> Result<TryInsertPayloadResult, InsertBlockFatalError> {
917 let block_hash = payload.block_hash();
918 let num_hash = payload.num_hash();
919 let parent_hash = payload.parent_hash();
920 let mut latest_valid_hash = None;
921
922 match self.insert_payload(payload) {
923 Ok(status) => {
924 let (status, already_seen) = match status {
925 InsertPayloadOk::Inserted(BlockStatus::Valid) => {
926 latest_valid_hash = Some(block_hash);
927 self.try_connect_buffered_blocks(num_hash)?;
928 (PayloadStatusEnum::Valid, false)
929 }
930 InsertPayloadOk::AlreadySeen(BlockStatus::Valid) => {
931 latest_valid_hash = Some(block_hash);
932 (PayloadStatusEnum::Valid, true)
933 }
934 InsertPayloadOk::Inserted(BlockStatus::Disconnected { .. }) => {
935 (PayloadStatusEnum::Syncing, false)
936 }
937 InsertPayloadOk::AlreadySeen(BlockStatus::Disconnected { .. }) => {
938 (PayloadStatusEnum::Syncing, true)
940 }
941 };
942
943 Ok(TryInsertPayloadResult {
944 status: PayloadStatus::new(status, latest_valid_hash),
945 already_seen,
946 })
947 }
948 Err(error) => {
949 let status = match error {
950 InsertPayloadError::Block(error) => self.on_insert_block_error(error)?,
951 InsertPayloadError::Payload(error) => {
952 self.on_new_payload_error(error, num_hash, parent_hash)?
953 }
954 };
955
956 Ok(TryInsertPayloadResult { status, already_seen: false })
957 }
958 }
959 }
960
961 fn try_buffer_payload(
970 &mut self,
971 payload: T::ExecutionData,
972 ) -> Result<PayloadStatus, InsertBlockFatalError> {
973 let parent_hash = payload.parent_hash();
974 let num_hash = payload.num_hash();
975
976 match self.payload_validator.convert_payload_to_block(payload) {
977 Ok(block) => {
979 if let Err(error) = self.buffer_block(block) {
980 Ok(self.on_insert_block_error(error)?)
981 } else {
982 Ok(PayloadStatus::from_status(PayloadStatusEnum::Syncing))
983 }
984 }
985 Err(error) => Ok(self.on_new_payload_error(error, num_hash, parent_hash)?),
986 }
987 }
988
989 fn on_new_head(&self, new_head: B256) -> ProviderResult<Option<NewCanonicalChain<N>>> {
996 let Some(new_head_block) = self.state.tree_state.blocks_by_hash.get(&new_head) else {
998 debug!(target: "engine::tree", new_head=?new_head, "New head block not found in inmemory tree state");
999 self.metrics.engine.executed_new_block_cache_miss.increment(1);
1000 return Ok(None)
1001 };
1002
1003 let new_head_number = new_head_block.recovered_block().number();
1004 let mut current_canonical_number = self.state.tree_state.current_canonical_head.number;
1005
1006 let mut new_chain = vec![new_head_block.clone()];
1007 let mut current_hash = new_head_block.recovered_block().parent_hash();
1008 let mut current_number = new_head_number - 1;
1009
1010 while current_number > current_canonical_number {
1015 if let Some(block) = self.state.tree_state.executed_block_by_hash(current_hash).cloned()
1016 {
1017 current_hash = block.recovered_block().parent_hash();
1018 current_number -= 1;
1019 new_chain.push(block);
1020 } else {
1021 warn!(target: "engine::tree", current_hash=?current_hash, "Sidechain block not found in TreeState");
1022 return Ok(None)
1025 }
1026 }
1027
1028 if current_hash == self.state.tree_state.current_canonical_head.hash {
1031 new_chain.reverse();
1032
1033 return Ok(Some(NewCanonicalChain::Commit { new: new_chain }))
1035 }
1036
1037 let mut old_chain = Vec::new();
1039 let mut old_hash = self.state.tree_state.current_canonical_head.hash;
1040
1041 while current_canonical_number > current_number {
1044 let block = self.canonical_block_by_hash(old_hash)?;
1045 old_hash = block.recovered_block().parent_hash();
1046 old_chain.push(block);
1047 current_canonical_number -= 1;
1048 }
1049
1050 debug_assert_eq!(current_number, current_canonical_number);
1052
1053 while old_hash != current_hash {
1056 let block = self.canonical_block_by_hash(old_hash)?;
1057 old_hash = block.recovered_block().parent_hash();
1058 old_chain.push(block);
1059
1060 if let Some(block) = self.state.tree_state.executed_block_by_hash(current_hash).cloned()
1061 {
1062 current_hash = block.recovered_block().parent_hash();
1063 new_chain.push(block);
1064 } else {
1065 warn!(target: "engine::tree", invalid_hash=?current_hash, "New chain block not found in TreeState");
1067 return Ok(None)
1068 }
1069 }
1070 new_chain.reverse();
1071 old_chain.reverse();
1072
1073 Ok(Some(NewCanonicalChain::Reorg { new: new_chain, old: old_chain }))
1074 }
1075
1076 fn update_latest_block_to_canonical_ancestor(
1088 &mut self,
1089 canonical_header: &SealedHeader<N::BlockHeader>,
1090 ) -> ProviderResult<()> {
1091 debug!(target: "engine::tree", head = ?canonical_header.num_hash(), "Update latest block to canonical ancestor");
1092 let current_head_number = self.state.tree_state.canonical_block_number();
1093 let new_head_number = canonical_header.number();
1094 let new_head_hash = canonical_header.hash();
1095
1096 self.state.tree_state.set_canonical_head(canonical_header.num_hash());
1098
1099 if new_head_number < current_head_number {
1101 debug!(
1102 target: "engine::tree",
1103 current_head = current_head_number,
1104 new_head = new_head_number,
1105 new_head_hash = ?new_head_hash,
1106 "FCU unwind detected: reverting to canonical ancestor"
1107 );
1108
1109 self.handle_canonical_chain_unwind(current_head_number, canonical_header)
1110 } else {
1111 debug!(
1112 target: "engine::tree",
1113 previous_head = current_head_number,
1114 new_head = new_head_number,
1115 new_head_hash = ?new_head_hash,
1116 "Advancing latest block to canonical ancestor"
1117 );
1118 self.handle_chain_advance_or_same_height(canonical_header)
1119 }
1120 }
1121
1122 fn handle_canonical_chain_unwind(
1125 &self,
1126 current_head_number: u64,
1127 canonical_header: &SealedHeader<N::BlockHeader>,
1128 ) -> ProviderResult<()> {
1129 let new_head_number = canonical_header.number();
1130 debug!(
1131 target: "engine::tree",
1132 from = current_head_number,
1133 to = new_head_number,
1134 "Handling unwind: collecting blocks to remove from in-memory state"
1135 );
1136
1137 let old_blocks =
1139 self.collect_blocks_for_canonical_unwind(new_head_number, current_head_number);
1140
1141 self.apply_canonical_ancestor_via_reorg(canonical_header, old_blocks)
1143 }
1144
1145 fn collect_blocks_for_canonical_unwind(
1147 &self,
1148 new_head_number: u64,
1149 current_head_number: u64,
1150 ) -> Vec<ExecutedBlock<N>> {
1151 let mut old_blocks =
1152 Vec::with_capacity((current_head_number.saturating_sub(new_head_number)) as usize);
1153
1154 for block_num in (new_head_number + 1)..=current_head_number {
1155 if let Some(block_state) = self.canonical_in_memory_state.state_by_number(block_num) {
1156 let executed_block = block_state.block_ref().clone();
1157 old_blocks.push(executed_block);
1158 debug!(
1159 target: "engine::tree",
1160 block_number = block_num,
1161 "Collected block for removal from in-memory state"
1162 );
1163 }
1164 }
1165
1166 if old_blocks.is_empty() {
1167 debug!(
1168 target: "engine::tree",
1169 "No blocks found in memory to remove, will clear and reset state"
1170 );
1171 }
1172
1173 old_blocks
1174 }
1175
1176 fn apply_canonical_ancestor_via_reorg(
1178 &self,
1179 canonical_header: &SealedHeader<N::BlockHeader>,
1180 old_blocks: Vec<ExecutedBlock<N>>,
1181 ) -> ProviderResult<()> {
1182 let new_head_hash = canonical_header.hash();
1183 let new_head_number = canonical_header.number();
1184
1185 let executed_block = self.canonical_block_by_hash(new_head_hash)?;
1187 self.canonical_in_memory_state
1189 .update_chain(NewCanonicalChain::Reorg { new: vec![executed_block], old: old_blocks });
1190
1191 self.canonical_in_memory_state.set_canonical_head(canonical_header.clone());
1194
1195 debug!(
1196 target: "engine::tree",
1197 block_number = new_head_number,
1198 block_hash = ?new_head_hash,
1199 "Successfully loaded canonical ancestor into memory via reorg"
1200 );
1201
1202 Ok(())
1203 }
1204
1205 fn handle_chain_advance_or_same_height(
1207 &self,
1208 canonical_header: &SealedHeader<N::BlockHeader>,
1209 ) -> ProviderResult<()> {
1210 self.ensure_block_in_memory(canonical_header.number(), canonical_header.hash())?;
1212
1213 self.canonical_in_memory_state.set_canonical_head(canonical_header.clone());
1215
1216 Ok(())
1217 }
1218
1219 fn ensure_block_in_memory(&self, block_number: u64, block_hash: B256) -> ProviderResult<()> {
1221 if self.canonical_in_memory_state.state_by_number(block_number).is_some() {
1223 return Ok(());
1224 }
1225
1226 let executed_block = self.canonical_block_by_hash(block_hash)?;
1228 self.canonical_in_memory_state
1229 .update_chain(NewCanonicalChain::Commit { new: vec![executed_block] });
1230
1231 debug!(
1232 target: "engine::tree",
1233 block_number,
1234 block_hash = ?block_hash,
1235 "Added canonical block to in-memory state"
1236 );
1237
1238 Ok(())
1239 }
1240
1241 #[instrument(level = "debug", target = "engine::tree", skip_all, fields(head = % state.head_block_hash, safe = % state.safe_block_hash,finalized = % state.finalized_block_hash))]
1250 fn on_forkchoice_updated(
1251 &mut self,
1252 state: ForkchoiceState,
1253 attrs: Option<T::PayloadAttributes>,
1254 ) -> ProviderResult<TreeOutcome<OnForkChoiceUpdated>> {
1255 trace!(target: "engine::tree", ?attrs, "invoked forkchoice update");
1256
1257 self.record_forkchoice_metrics();
1259
1260 if let Some(early_result) = self.validate_forkchoice_state(state)? {
1262 return Ok(TreeOutcome::new(early_result));
1263 }
1264
1265 if let Some(result) = self.handle_canonical_head(state, &attrs)? {
1267 return Ok(result);
1268 }
1269
1270 if let Some(result) = self.apply_chain_update(state, &attrs)? {
1273 return Ok(result);
1274 }
1275
1276 self.handle_missing_block(state)
1278 }
1279
1280 fn record_forkchoice_metrics(&self) {
1282 self.canonical_in_memory_state.on_forkchoice_update_received();
1283 }
1284
1285 fn validate_forkchoice_state(
1290 &mut self,
1291 state: ForkchoiceState,
1292 ) -> ProviderResult<Option<OnForkChoiceUpdated>> {
1293 if state.head_block_hash.is_zero() {
1294 return Ok(Some(OnForkChoiceUpdated::invalid_state()));
1295 }
1296
1297 let lowest_buffered_ancestor_fcu = self.lowest_buffered_ancestor_or(state.head_block_hash);
1300 if let Some(status) = self.check_invalid_ancestor(lowest_buffered_ancestor_fcu)? {
1301 return Ok(Some(OnForkChoiceUpdated::with_invalid(status)));
1302 }
1303
1304 if !self.backfill_sync_state.is_idle() {
1305 trace!(target: "engine::tree", "Pipeline is syncing, skipping forkchoice update");
1308 return Ok(Some(OnForkChoiceUpdated::syncing()));
1309 }
1310
1311 Ok(None)
1312 }
1313
1314 fn handle_canonical_head(
1320 &mut self,
1321 state: ForkchoiceState,
1322 attrs: &Option<T::PayloadAttributes>, ) -> ProviderResult<Option<TreeOutcome<OnForkChoiceUpdated>>> {
1324 if self.state.tree_state.canonical_block_hash() != state.head_block_hash {
1339 return Ok(None);
1340 }
1341
1342 trace!(target: "engine::tree", "fcu head hash is already canonical");
1343
1344 if let Err(outcome) = self.ensure_consistent_forkchoice_state(state) {
1346 return Ok(Some(TreeOutcome::new(outcome)));
1348 }
1349
1350 self.payload_validator.on_canonical_head_changed(state.head_block_hash, &self.state);
1351
1352 if let Some(attr) = attrs {
1354 let tip = self
1355 .sealed_header_by_hash(self.state.tree_state.canonical_block_hash())?
1356 .ok_or_else(|| {
1357 ProviderError::HeaderNotFound(state.head_block_hash.into())
1360 })?;
1361 let updated = self.process_payload_attributes(attr.clone(), &tip, state);
1363 return Ok(Some(TreeOutcome::new(updated)));
1364 }
1365
1366 Ok(Some(Self::valid_outcome(state)))
1368 }
1369
1370 fn apply_chain_update(
1382 &mut self,
1383 state: ForkchoiceState,
1384 attrs: &Option<T::PayloadAttributes>,
1385 ) -> ProviderResult<Option<TreeOutcome<OnForkChoiceUpdated>>> {
1386 if let Ok(Some(canonical_header)) = self.find_canonical_header(state.head_block_hash) {
1388 debug!(target: "engine::tree", head = canonical_header.number(), "fcu head block is already canonical");
1389
1390 let always_trigger_payload_job = self.engine_kind.is_opstack() ||
1393 self.config.always_process_payload_attributes_on_canonical_head();
1394
1395 if !always_trigger_payload_job &&
1404 self.canonical_in_memory_state
1405 .get_finalized_num_hash()
1406 .is_some_and(|finalized| canonical_header.number() < finalized.number)
1407 {
1408 debug!(target: "engine::tree", head = canonical_header.number(), "rejecting canonical ancestor fcu below the finalized block");
1409 return Ok(Some(TreeOutcome::new(OnForkChoiceUpdated::too_deep_reorg())));
1410 }
1411
1412 if always_trigger_payload_job && self.config.unwind_canonical_header() {
1418 self.update_latest_block_to_canonical_ancestor(&canonical_header)?;
1419 }
1420
1421 if let Some(attr) = attrs {
1426 debug!(target: "engine::tree", head = canonical_header.number(), "handling payload attributes for canonical head");
1427 let updated =
1429 self.process_payload_attributes(attr.clone(), &canonical_header, state);
1430 return Ok(Some(TreeOutcome::new(updated)));
1431 }
1432
1433 return Ok(Some(Self::valid_outcome(state)));
1436 }
1437
1438 if let Some(chain_update) = self.on_new_head(state.head_block_hash)? {
1440 let tip = chain_update.tip().clone_sealed_header();
1441 self.on_canonical_chain_update(chain_update);
1442
1443 if let Err(outcome) = self.ensure_consistent_forkchoice_state(state) {
1445 return Ok(Some(TreeOutcome::new(outcome)));
1447 }
1448
1449 if let Some(attr) = attrs {
1450 let updated = self.process_payload_attributes(attr.clone(), &tip, state);
1452 return Ok(Some(TreeOutcome::new(updated)));
1453 }
1454
1455 return Ok(Some(Self::valid_outcome(state)));
1456 }
1457
1458 Ok(None)
1459 }
1460
1461 fn handle_missing_block(
1466 &self,
1467 state: ForkchoiceState,
1468 ) -> ProviderResult<TreeOutcome<OnForkChoiceUpdated>> {
1469 let target = if self.state.forkchoice_state_tracker.is_empty() &&
1476 !state.safe_block_hash.is_zero() &&
1478 self.find_canonical_header(state.safe_block_hash).ok().flatten().is_none()
1479 {
1480 debug!(target: "engine::tree", "missing safe block on initial FCU, downloading safe block");
1481 state.safe_block_hash
1482 } else {
1483 state.head_block_hash
1484 };
1485
1486 let target = self.lowest_buffered_ancestor_or(target);
1487 trace!(target: "engine::tree", %target, "downloading missing block");
1488
1489 Ok(TreeOutcome::new(OnForkChoiceUpdated::valid(PayloadStatus::from_status(
1490 PayloadStatusEnum::Syncing,
1491 )))
1492 .with_event(TreeEvent::Download(DownloadRequest::single_block(target))))
1493 }
1494
1495 fn remove_blocks(&mut self, new_tip_num: u64) {
1498 debug!(target: "engine::tree", ?new_tip_num, last_persisted_block_number=?self.persistence_state.last_persisted_block.number, "Removing blocks using persistence task");
1499 if new_tip_num < self.persistence_state.last_persisted_block.number {
1500 debug!(target: "engine::tree", ?new_tip_num, "Starting remove blocks job");
1501 self.state.set_pending_sparse_trie_prune(false);
1502 let (tx, rx) = crossbeam_channel::bounded(1);
1503 let _ = self.persistence.remove_blocks_above(new_tip_num, tx);
1504 self.persistence_state.start_remove(new_tip_num, rx);
1505 }
1506 }
1507
1508 fn persist_blocks(&mut self, input: SaveBlocksInput<N>) {
1511 let highest_num_hash = input.last_block();
1512 debug!(target: "engine::tree", count=input.persist_rest_blocks().len(), blocks = ?input.persist_rest_blocks().iter().map(|block| block.recovered_block().num_hash()).collect::<Vec<_>>(), "Persisting blocks");
1513
1514 let (tx, rx) = crossbeam_channel::bounded(1);
1515 let _ = self.persistence.save_blocks(input, tx);
1516
1517 self.persistence_state.start_save(highest_num_hash, rx);
1518 }
1519
1520 fn advance_persistence(&mut self) -> Result<(), AdvancePersistenceError> {
1525 if self.pending_persisted_handoff.is_some() {
1528 return Ok(())
1529 }
1530
1531 if !self.persistence_state.in_progress() {
1532 if let Some(new_tip_num) = self.find_disk_reorg()? {
1533 self.remove_blocks(new_tip_num)
1534 } else if let Some(input) = self.get_save_blocks_input(PersistTarget::Threshold) {
1535 self.persist_blocks(input);
1536 }
1537 }
1538
1539 Ok(())
1540 }
1541
1542 fn finish_termination(
1547 &mut self,
1548 pending_termination: oneshot::Sender<()>,
1549 ) -> Result<(), AdvancePersistenceError> {
1550 trace!(target: "engine::tree", "finishing termination, persisting remaining blocks");
1551 let result = self.persist_until_complete();
1552 let _ = pending_termination.send(());
1553 result
1554 }
1555
1556 fn persist_until_complete(&mut self) -> Result<(), AdvancePersistenceError> {
1558 loop {
1559 if let Some((rx, start_time, action)) = self.persistence_state.rx.take() {
1561 debug!(target: "engine::tree", ?action, "waiting for in-flight persistence");
1562 let result = rx.recv().map_err(|_| AdvancePersistenceError::ChannelClosed)?;
1563 self.finish_persistence(result, start_time);
1564 continue
1565 }
1566
1567 if let Some(new_tip_num) = self.find_disk_reorg()? {
1571 self.remove_blocks(new_tip_num);
1572 continue
1573 }
1574
1575 let Some(input) = self.get_save_blocks_input(PersistTarget::Head) else {
1576 debug!(target: "engine::tree", "persistence complete, signaling termination");
1577 return Ok(())
1578 };
1579
1580 debug!(target: "engine::tree", count = input.persist_rest_blocks().len(), "persisting remaining blocks before shutdown");
1581 self.persist_blocks(input);
1582 }
1583 }
1584
1585 fn try_poll_persistence(&mut self) -> Result<bool, AdvancePersistenceError> {
1589 let Some((rx, start_time, action)) = self.persistence_state.rx.take() else {
1590 return Ok(false);
1591 };
1592
1593 match rx.try_recv() {
1594 Ok(result) => {
1595 self.on_persistence_complete(result, start_time)?;
1596 Ok(true)
1597 }
1598 Err(crossbeam_channel::TryRecvError::Empty) => {
1599 self.persistence_state.rx = Some((rx, start_time, action));
1601 Ok(false)
1602 }
1603 Err(crossbeam_channel::TryRecvError::Disconnected) => {
1604 Err(AdvancePersistenceError::ChannelClosed)
1605 }
1606 }
1607 }
1608
1609 fn on_persistence_complete(
1611 &mut self,
1612 result: PersistenceResult,
1613 start_time: Instant,
1614 ) -> Result<(), AdvancePersistenceError> {
1615 let handoff = self.finish_persistence(result, start_time);
1616
1617 if self.payload_builds.is_active() {
1618 debug_assert!(
1619 self.pending_persisted_handoff.is_none(),
1620 "a new persistence task must not start while its predecessor handoff is pending"
1621 );
1622 debug!(target: "engine::tree", "Deferring persisted in-memory handoff until payload jobs finish");
1623 self.pending_persisted_handoff = Some(handoff);
1624 return Ok(())
1625 }
1626
1627 self.on_persisted_handoff(handoff)
1628 }
1629
1630 fn finish_persistence(
1632 &mut self,
1633 result: PersistenceResult,
1634 start_time: Instant,
1635 ) -> PersistenceResult {
1636 self.metrics.engine.persistence_duration.record(start_time.elapsed());
1637
1638 let last_block = result.last_block;
1639 let last_state_trie_block = result.last_state_trie_block;
1640 debug_assert!(
1641 last_state_trie_block.number <= last_block.number,
1642 "state/trie frontier cannot exceed the last persisted block"
1643 );
1644
1645 debug!(target: "engine::tree", ?last_block, ?last_state_trie_block, elapsed=?start_time.elapsed(), "Finished persisting, calling finish");
1646 self.persistence_state.finish(last_block, last_state_trie_block);
1647
1648 result
1649 }
1650
1651 fn on_persisted_handoff(
1653 &mut self,
1654 handoff: PersistenceResult,
1655 ) -> Result<(), AdvancePersistenceError> {
1656 if handoff.last_block != self.persistence_state.last_persisted_block ||
1657 handoff.last_state_trie_block !=
1658 self.persistence_state.last_state_trie_persisted_block
1659 {
1660 debug!(
1661 target: "engine::tree",
1662 handoff_last_block = ?handoff.last_block,
1663 current_last_block = ?self.persistence_state.last_persisted_block,
1664 "Discarding stale persisted handoff"
1665 );
1666 return Ok(())
1667 }
1668
1669 let PersistenceResult { last_block, commit_duration, .. } = handoff;
1670 let last_block_number = last_block.number;
1671
1672 let min_threshold = last_block_number.saturating_sub(CHANGESET_CACHE_RETENTION_BLOCKS);
1676 let eviction_threshold =
1677 if let Some(finalized) = self.canonical_in_memory_state.get_finalized_num_hash() {
1678 finalized.number.min(min_threshold)
1680 } else {
1681 min_threshold
1683 };
1684 debug!(
1685 target: "engine::tree",
1686 last_persisted = last_block_number,
1687 finalized_number = ?self.canonical_in_memory_state.get_finalized_num_hash().map(|f| f.number),
1688 eviction_threshold,
1689 "Evicting changesets below threshold"
1690 );
1691 self.state.tree_state.overlay_manager.evict_cached_changesets(eviction_threshold);
1692
1693 self.on_new_persisted_block()?;
1694
1695 self.purge_timing_stats(last_block_number, commit_duration);
1696
1697 Ok(())
1698 }
1699
1700 fn on_payload_build_finished(&mut self) -> Result<(), AdvancePersistenceError> {
1702 if self.payload_builds.is_active() {
1703 return Ok(())
1704 }
1705
1706 if let Some(handoff) = self.pending_persisted_handoff.take() {
1707 self.on_persisted_handoff(handoff)?;
1708 }
1709
1710 Ok(())
1711 }
1712
1713 fn on_engine_message(
1717 &mut self,
1718 msg: FromEngine<EngineApiRequest<T, N>, N::Block>,
1719 ) -> Result<ops::ControlFlow<()>, InsertBlockFatalError> {
1720 match msg {
1721 FromEngine::Event(event) => match event {
1722 FromOrchestrator::BackfillSyncStarted => {
1723 debug!(target: "engine::tree", "received backfill sync started event");
1724 self.backfill_sync_state = BackfillSyncState::Active;
1725 }
1726 FromOrchestrator::BackfillSyncFinished(ctrl) => {
1727 self.on_backfill_sync_finished(ctrl)?;
1728 }
1729 FromOrchestrator::Terminate { tx } => {
1730 debug!(target: "engine::tree", "received terminate request");
1731 if let Err(err) = self.finish_termination(tx) {
1732 error!(target: "engine::tree", %err, "Termination failed");
1733 }
1734 return Ok(ops::ControlFlow::Break(()))
1735 }
1736 },
1737 FromEngine::Request(request) => {
1738 match request {
1739 EngineApiRequest::InsertExecutedBlock(payload) => {
1740 let block_num_hash = payload.recovered_block.num_hash();
1741 if block_num_hash.number <= self.state.tree_state.canonical_block_number() {
1742 return Ok(ops::ControlFlow::Continue(()))
1744 }
1745
1746 if self.state.tree_state.contains_hash(&block_num_hash.hash) {
1747 return Ok(ops::ControlFlow::Continue(()))
1749 }
1750
1751 debug!(target: "engine::tree", block=?block_num_hash, "inserting already executed block");
1752 let now = Instant::now();
1753
1754 let block = match self.payload_validator.on_inserted_executed_block(payload)
1755 {
1756 Ok(block) => block,
1757 Err(err) => {
1758 warn!(target: "engine::tree", %err, block=?block_num_hash, "Failed to insert already executed block");
1759 return Ok(ops::ControlFlow::Continue(()))
1760 }
1761 };
1762
1763 if self.state.tree_state.canonical_block_hash() ==
1766 block.recovered_block().parent_hash()
1767 {
1768 debug!(target: "engine::tree", pending=?block_num_hash, "updating pending block");
1769 self.canonical_in_memory_state.set_pending_block(block.clone());
1770 }
1771
1772 self.state.tree_state.insert_executed(block.clone());
1773 self.metrics.engine.inserted_already_executed_blocks.increment(1);
1774 self.emit_event(EngineApiEvent::BeaconConsensus(
1775 ConsensusEngineEvent::CanonicalBlockAdded(block, now.elapsed()),
1776 ));
1777 }
1778 EngineApiRequest::Beacon(request) => {
1779 match request {
1780 BeaconEngineMessage::ForkchoiceUpdated { state, payload_attrs, tx } => {
1781 let has_attrs = payload_attrs.is_some();
1782
1783 let start = Instant::now();
1784 let mut output = self.on_forkchoice_updated(state, payload_attrs);
1785
1786 if let Ok(res) = &mut output {
1787 self.state
1789 .forkchoice_state_tracker
1790 .set_latest(state, res.outcome.forkchoice_status());
1791
1792 self.emit_event(ConsensusEngineEvent::ForkchoiceUpdated(
1794 state,
1795 res.outcome.forkchoice_status(),
1796 ));
1797
1798 self.on_maybe_tree_event(res.event.take())?;
1800 }
1801
1802 if let Err(ref err) = output {
1803 error!(target: "engine::tree", %err, ?state, "Error processing forkchoice update");
1804 }
1805
1806 self.metrics.engine.forkchoice_updated.update_response_metrics(
1807 start,
1808 &mut self.metrics.engine.new_payload.latest_finish_at,
1809 has_attrs,
1810 &output,
1811 );
1812
1813 if let Err(err) =
1814 tx.send(output.map(|o| o.outcome).map_err(Into::into))
1815 {
1816 self.metrics
1817 .engine
1818 .failed_forkchoice_updated_response_deliveries
1819 .increment(1);
1820 warn!(target: "engine::tree", ?state, elapsed=?start.elapsed(), "Failed to deliver forkchoiceUpdated response, receiver dropped (request cancelled): {err:?}");
1821 }
1822 }
1823 BeaconEngineMessage::NewPayload { payload, tx } => {
1824 let start = Instant::now();
1825 let gas_used = payload.gas_used();
1826 let num_hash = payload.num_hash();
1827 let mut output = self.on_new_payload(payload);
1828 self.metrics.engine.new_payload.update_response_metrics(
1829 start,
1830 &mut self.metrics.engine.forkchoice_updated.latest_finish_at,
1831 &output,
1832 gas_used,
1833 );
1834
1835 let maybe_event =
1836 output.as_mut().ok().and_then(|out| out.event.take());
1837
1838 if let Err(err) =
1840 tx.send(output.map(|o| o.outcome).map_err(Into::into))
1841 {
1842 warn!(target: "engine::tree", payload=?num_hash, elapsed=?start.elapsed(), "Failed to deliver newPayload response, receiver dropped (request cancelled): {err:?}");
1843 self.metrics
1844 .engine
1845 .failed_new_payload_response_deliveries
1846 .increment(1);
1847 }
1848
1849 self.on_maybe_tree_event(maybe_event)?;
1851 }
1852 BeaconEngineMessage::RethNewPayload {
1853 payload,
1854 wait_for_persistence,
1855 wait_for_caches,
1856 tx,
1857 enqueued_at,
1858 } => {
1859 debug!(
1860 target: "engine::tree",
1861 wait_for_persistence,
1862 wait_for_caches,
1863 "Processing reth_newPayload"
1864 );
1865
1866 let backpressure_wait = enqueued_at.elapsed();
1867
1868 let explicit_persistence_wait = if wait_for_persistence {
1869 let pending_persistence = self.persistence_state.rx.take();
1870 if let Some((rx, start_time, _action)) = pending_persistence {
1871 let (persistence_tx, persistence_rx) =
1872 std::sync::mpsc::channel();
1873 self.runtime.spawn_blocking_named(
1874 "wait-persist",
1875 move || {
1876 let start = Instant::now();
1877 let result = rx
1878 .recv()
1879 .expect("persistence state channel closed");
1880 let _ = persistence_tx.send((
1881 result,
1882 start_time,
1883 start.elapsed(),
1884 ));
1885 },
1886 );
1887 let (result, start_time, wait_duration) = persistence_rx
1888 .recv()
1889 .expect("persistence result channel closed");
1890 let _ = self.on_persistence_complete(result, start_time);
1891 wait_duration
1892 } else {
1893 Duration::ZERO
1894 }
1895 } else {
1896 Duration::ZERO
1897 };
1898
1899 let cache_wait = wait_for_caches
1900 .then(|| self.payload_validator.wait_for_caches());
1901
1902 let start = Instant::now();
1903 let gas_used = payload.gas_used();
1904 let num_hash = payload.num_hash();
1905 let mut output = self.on_new_payload(payload);
1906 let latency = start.elapsed();
1907 self.metrics.engine.new_payload.update_response_metrics(
1908 start,
1909 &mut self.metrics.engine.forkchoice_updated.latest_finish_at,
1910 &output,
1911 gas_used,
1912 );
1913
1914 let maybe_event =
1915 output.as_mut().ok().and_then(|out| out.event.take());
1916
1917 let timings = NewPayloadTimings {
1918 latency,
1919 persistence_wait: backpressure_wait + explicit_persistence_wait,
1920 execution_cache_wait: cache_wait
1921 .map(|wait| wait.execution_cache),
1922 sparse_trie_wait: cache_wait.map(|wait| wait.sparse_trie),
1923 };
1924 if let Err(err) = tx
1925 .send(output.map(|o| (o.outcome, timings)).map_err(Into::into))
1926 {
1927 error!(
1928 target: "engine::tree",
1929 payload=?num_hash,
1930 elapsed=?latency,
1931 "Failed to send event: {err:?}"
1932 );
1933 self.metrics
1934 .engine
1935 .failed_new_payload_response_deliveries
1936 .increment(1);
1937 }
1938
1939 self.on_maybe_tree_event(maybe_event)?;
1940 }
1941 }
1942 }
1943 }
1944 }
1945 FromEngine::DownloadedBlocks(blocks) => {
1946 if let Some(event) = self.on_downloaded(blocks)? {
1947 self.on_tree_event(event)?;
1948 }
1949 }
1950 }
1951 Ok(ops::ControlFlow::Continue(()))
1952 }
1953
1954 fn on_backfill_sync_finished(
1968 &mut self,
1969 ctrl: ControlFlow,
1970 ) -> Result<(), InsertBlockFatalError> {
1971 debug!(target: "engine::tree", "received backfill sync finished event");
1972 self.backfill_sync_state = BackfillSyncState::Idle;
1973
1974 let backfill_height = if let ControlFlow::Unwind { bad_block, target } = &ctrl {
1976 warn!(target: "engine::tree", invalid_block=?bad_block, "Bad block detected in unwind");
1977 self.state.invalid_headers.insert(**bad_block);
1979
1980 Some(*target)
1982 } else {
1983 ctrl.block_number()
1985 };
1986
1987 let Some(backfill_height) = backfill_height else { return Ok(()) };
1989
1990 let Some(backfill_num_hash) = self
1996 .provider
1997 .block_hash(backfill_height)?
1998 .map(|hash| BlockNumHash { hash, number: backfill_height })
1999 else {
2000 debug!(target: "engine::tree", ?ctrl, "Backfill block not found");
2001 return Ok(())
2002 };
2003
2004 if ctrl.is_unwind() {
2005 self.state.set_pending_sparse_trie_prune(false);
2008 self.state.tree_state.reset(backfill_num_hash)
2009 } else {
2010 self.state.tree_state.remove_until(
2011 backfill_num_hash,
2012 self.persistence_state.last_persisted_block.hash,
2013 Some(backfill_num_hash),
2014 );
2015 }
2016
2017 self.metrics.engine.executed_blocks.set(self.state.tree_state.block_count() as f64);
2018 self.metrics.tree.canonical_chain_height.set(backfill_height as f64);
2019
2020 self.state.buffer.remove_old_blocks(backfill_height);
2022 self.purge_timing_stats(backfill_height, None);
2023 self.canonical_in_memory_state.clear_state();
2026
2027 if let Ok(Some(new_head)) = self.provider.sealed_header(backfill_height) {
2028 self.state.tree_state.set_canonical_head(new_head.num_hash());
2031 self.persistence_state.finish(new_head.num_hash(), new_head.num_hash());
2032
2033 self.canonical_in_memory_state.set_canonical_head(new_head);
2035 }
2036
2037 let Some(sync_target_state) = self.state.forkchoice_state_tracker.sync_target_state()
2040 else {
2041 return Ok(())
2042 };
2043 if !self.engine_kind.is_opstack() && sync_target_state.finalized_block_hash.is_zero() {
2044 return Ok(())
2046 }
2047 let target_hash = self.backfill_target_hash(sync_target_state);
2048 if target_hash.is_zero() {
2049 return Ok(())
2050 }
2051 let newest_target = self.state.buffer.block(&target_hash).map(|block| block.number());
2053
2054 if let Some(backfill_target) =
2060 ctrl.block_number().zip(newest_target).and_then(|(progress, target_number)| {
2061 self.backfill_sync_target(progress, target_number, None)
2064 })
2065 {
2066 self.emit_event(EngineApiEvent::BackfillAction(BackfillAction::Start(
2068 backfill_target.into(),
2069 )));
2070 return Ok(())
2071 };
2072
2073 if let Some(lowest_buffered) =
2075 self.state.buffer.lowest_ancestor(&sync_target_state.head_block_hash)
2076 {
2077 let current_head_num = self.state.tree_state.current_canonical_head.number;
2078 let target_head_num = lowest_buffered.number();
2079
2080 if let Some(distance) = self.distance_from_local_tip(current_head_num, target_head_num)
2081 {
2082 debug!(
2084 target: "engine::tree",
2085 %current_head_num,
2086 %target_head_num,
2087 %distance,
2088 "Backfill complete, downloading remaining blocks to reach FCU target"
2089 );
2090
2091 self.emit_event(EngineApiEvent::Download(DownloadRequest::BlockRange(
2092 lowest_buffered.parent_hash(),
2093 distance,
2094 )));
2095 return Ok(());
2096 }
2097 } else {
2098 debug!(
2101 target: "engine::tree",
2102 head_hash = %sync_target_state.head_block_hash,
2103 "Backfill complete but head block not buffered, requesting download"
2104 );
2105 self.emit_event(EngineApiEvent::Download(DownloadRequest::single_block(
2106 sync_target_state.head_block_hash,
2107 )));
2108 return Ok(());
2109 }
2110
2111 self.try_connect_buffered_blocks(self.state.tree_state.current_canonical_head)
2113 }
2114
2115 fn make_canonical(&mut self, target: B256) -> ProviderResult<()> {
2119 if let Some(chain_update) = self.on_new_head(target)? {
2120 self.on_canonical_chain_update(chain_update);
2121 }
2122
2123 self.on_canonicalized_sync_target(target);
2124
2125 Ok(())
2126 }
2127
2128 fn on_canonicalized_sync_target(&mut self, target: B256) {
2130 let Some(sync_target_state) = self
2131 .state
2132 .forkchoice_state_tracker
2133 .sync_target_state()
2134 .filter(|state| state.head_block_hash == target)
2135 else {
2136 return;
2137 };
2138
2139 if let Err(outcome) = self.ensure_consistent_forkchoice_state(sync_target_state) {
2140 debug!(
2141 target: "engine::tree",
2142 head = %sync_target_state.head_block_hash,
2143 safe = %sync_target_state.safe_block_hash,
2144 finalized = %sync_target_state.finalized_block_hash,
2145 ?outcome,
2146 "Canonicalized sync target head before safe/finalized could be applied"
2147 );
2148 return;
2149 }
2150
2151 self.state.forkchoice_state_tracker.promote_sync_target_to_valid(sync_target_state);
2152 }
2153
2154 fn on_maybe_tree_event(&mut self, event: Option<TreeEvent>) -> ProviderResult<()> {
2156 if let Some(event) = event {
2157 self.on_tree_event(event)?;
2158 }
2159
2160 Ok(())
2161 }
2162
2163 fn on_tree_event(&mut self, event: TreeEvent) -> ProviderResult<()> {
2167 match event {
2168 TreeEvent::TreeAction(action) => match action {
2169 TreeAction::MakeCanonical { sync_target_head } => {
2170 self.make_canonical(sync_target_head)?;
2171 }
2172 },
2173 TreeEvent::BackfillAction(action) => {
2174 self.emit_event(EngineApiEvent::BackfillAction(action));
2175 }
2176 TreeEvent::Download(action) => {
2177 self.emit_event(EngineApiEvent::Download(action));
2178 }
2179 }
2180
2181 Ok(())
2182 }
2183
2184 fn purge_timing_stats(&mut self, below_number: u64, commit_duration: Option<Duration>) {
2191 let threshold = self.config.slow_block_threshold();
2192 let check_slow = commit_duration.is_some() && threshold.is_some();
2193
2194 let keys_to_remove: Vec<B256> = self
2196 .execution_timing_stats
2197 .iter()
2198 .filter(|(_, stats)| stats.block_number <= below_number)
2199 .map(|(k, _)| *k)
2200 .collect();
2201
2202 for key in keys_to_remove {
2203 let stats = self.execution_timing_stats.remove(&key).expect("key just found");
2204 if check_slow {
2205 let commit_dur = commit_duration.expect("checked above");
2206 let total_duration =
2208 stats.execution_duration + stats.state_hash_duration + commit_dur;
2209
2210 if total_duration > threshold.expect("checked above") {
2211 self.emit_event(ConsensusEngineEvent::SlowBlock(SlowBlockInfo {
2212 stats,
2213 commit_duration: Some(commit_dur),
2214 total_duration,
2215 }));
2216 }
2217 }
2218 }
2219 }
2220
2221 fn emit_event(&mut self, event: impl Into<EngineApiEvent<N>>) {
2223 let event = event.into();
2224
2225 if event.is_backfill_action() {
2226 debug_assert_eq!(
2227 self.backfill_sync_state,
2228 BackfillSyncState::Idle,
2229 "backfill action should only be emitted when backfill is idle"
2230 );
2231
2232 if self.payload_builds.is_active() ||
2233 self.persistence_state.in_progress() ||
2234 self.pending_persisted_handoff.is_some()
2235 {
2236 debug!(target: "engine::tree", "skipping backfill while in-memory overlay is in use");
2239 return
2240 }
2241
2242 self.backfill_sync_state = BackfillSyncState::Pending;
2243 self.metrics.engine.pipeline_runs.increment(1);
2244 debug!(target: "engine::tree", "emitting backfill action event");
2245 }
2246
2247 let _ = self.outgoing.send(event).inspect_err(
2248 |err| error!(target: "engine::tree", "Failed to send internal event: {err:?}"),
2249 );
2250 }
2251
2252 fn get_save_blocks_input(&self, target: PersistTarget) -> Option<SaveBlocksInput<N>> {
2258 debug_assert!(!self.persistence_state.in_progress());
2261
2262 let prev_partial_state_trie = self.persistence_state.last_state_trie_persisted_block.number;
2263 let prev_db_tip = self.persistence_state.last_persisted_block.number;
2264 let canonical_head_number = self.state.tree_state.canonical_block_number();
2265
2266 let (new_db_tip, new_partial_state_trie) = match target {
2267 PersistTarget::Head => (canonical_head_number, canonical_head_number),
2268 PersistTarget::Threshold => {
2269 if (self.config.suppress_persistence_during_build() &&
2270 self.payload_builds.is_active()) ||
2271 !self.backfill_sync_state.is_idle()
2272 {
2273 return None
2274 }
2275
2276 if canonical_head_number.saturating_sub(prev_db_tip) <=
2277 self.config.persistence_threshold()
2278 {
2279 return None
2280 }
2281
2282 let new_db_tip =
2283 canonical_head_number.saturating_sub(self.config.memory_block_buffer_target());
2284 if new_db_tip <= prev_db_tip {
2285 return None
2286 }
2287
2288 let new_partial_state_trie = new_db_tip
2289 .saturating_sub(self.config.num_state_masking_blocks())
2290 .max(prev_partial_state_trie);
2291 (new_db_tip, new_partial_state_trie)
2292 }
2293 };
2294
2295 debug_assert!(
2296 new_db_tip >= prev_db_tip,
2297 "disk reorg must be resolved before saving blocks"
2298 );
2299 debug_assert!(
2300 new_partial_state_trie >= prev_partial_state_trie,
2301 "disk reorg must be resolved before saving state/trie"
2302 );
2303
2304 if new_db_tip == prev_db_tip && new_partial_state_trie == prev_partial_state_trie {
2305 return None
2306 }
2307
2308 let mut blocks = Vec::new();
2309 let mut current_hash = self.state.tree_state.canonical_block_hash();
2310
2311 debug!(
2312 target: "engine::tree",
2313 ?current_hash,
2314 ?prev_partial_state_trie,
2315 ?prev_db_tip,
2316 ?canonical_head_number,
2317 ?new_partial_state_trie,
2318 ?new_db_tip,
2319 target = ?target,
2320 "Returning save input"
2321 );
2322 while let Some(block) = self.state.tree_state.blocks_by_hash.get(¤t_hash) {
2323 if block.recovered_block().number() <= prev_partial_state_trie {
2324 break;
2325 }
2326
2327 if block.recovered_block().number() <= new_db_tip {
2328 blocks.push(block.clone());
2329 }
2330
2331 current_hash = block.recovered_block().parent_hash();
2332 }
2333
2334 blocks.reverse();
2336
2337 Some(SaveBlocksInput::new(
2338 blocks,
2339 prev_db_tip,
2340 prev_partial_state_trie,
2341 new_db_tip,
2342 new_partial_state_trie,
2343 ))
2344 }
2345
2346 fn on_new_persisted_block(&mut self) -> ProviderResult<()> {
2354 let in_memory_persisted_block = self.persistence_state.last_state_trie_persisted_block;
2355
2356 if let Some(remove_above) = self.find_disk_reorg()? {
2359 self.remove_blocks(remove_above);
2360 return Ok(())
2361 }
2362
2363 let finalized = self.state.forkchoice_state_tracker.last_valid_finalized();
2364 self.remove_before(in_memory_persisted_block, finalized)?;
2365 self.canonical_in_memory_state.remove_persisted_blocks_until(
2366 self.persistence_state.last_persisted_block,
2367 in_memory_persisted_block.number,
2368 );
2369 self.state.set_pending_sparse_trie_prune(self.should_prune_sparse_trie());
2370 Ok(())
2371 }
2372
2373 const fn should_prune_sparse_trie(&self) -> bool {
2375 self.config.use_state_root_task()
2376 }
2377
2378 #[instrument(level = "debug", target = "engine::tree", skip(self))]
2385 fn canonical_block_by_hash(&self, hash: B256) -> ProviderResult<ExecutedBlock<N>> {
2386 trace!(target: "engine::tree", ?hash, "Fetching executed block by hash");
2387 if let Some(block) = self.state.tree_state.executed_block_by_hash(hash) {
2389 return Ok(block.clone())
2390 }
2391
2392 let (block, senders) = self
2393 .provider
2394 .sealed_block_with_senders(hash.into(), TransactionVariant::WithHash)?
2395 .ok_or_else(|| ProviderError::HeaderNotFound(hash.into()))?
2396 .split_sealed();
2397 let mut execution_output = self
2398 .provider
2399 .get_state(block.header().number())?
2400 .ok_or_else(|| ProviderError::StateForNumberNotFound(block.header().number()))?;
2401 let bundle_state = execution_output.state();
2402 let hashed_state = self
2406 .provider
2407 .state_by_block_hash(block.parent_hash())?
2408 .hashed_post_state(bundle_state)?;
2409
2410 debug!(
2411 target: "engine::tree",
2412 number = ?block.number(),
2413 "computing block trie updates",
2414 );
2415 let db_provider = self.provider.database_provider_ro()?;
2416 let trie_updates = self
2417 .state
2418 .tree_state
2419 .overlay_manager
2420 .compute_block_trie_updates(&db_provider, block.number())?;
2421
2422 let sorted_hashed_state = Arc::new(hashed_state.into_sorted());
2423 let sorted_trie_updates = Arc::new(trie_updates);
2424 let trie_data = ComputedTrieData::new(sorted_hashed_state, sorted_trie_updates);
2425
2426 let execution_output = Arc::new(BlockExecutionOutput {
2427 state: execution_output.bundle,
2428 result: BlockExecutionResult {
2429 receipts: execution_output.receipts.pop().unwrap_or_default(),
2430 requests: execution_output.requests.pop().unwrap_or_default(),
2431 gas_used: block.gas_used(),
2432 blob_gas_used: block.blob_gas_used().unwrap_or_default(),
2433 },
2434 });
2435
2436 Ok(ExecutedBlock::new(
2437 Arc::new(RecoveredBlock::new_sealed(block, senders)),
2438 execution_output,
2439 trie_data,
2440 ))
2441 }
2442
2443 fn has_block_by_hash(&self, hash: B256) -> ProviderResult<bool> {
2447 if self.state.tree_state.contains_hash(&hash) {
2448 Ok(true)
2449 } else {
2450 self.provider.is_known(hash)
2451 }
2452 }
2453
2454 fn sealed_header_by_hash(
2456 &self,
2457 hash: B256,
2458 ) -> ProviderResult<Option<SealedHeader<N::BlockHeader>>> {
2459 let header = self.state.tree_state.sealed_header_by_hash(&hash);
2461
2462 if header.is_some() {
2463 Ok(header)
2464 } else {
2465 self.provider.sealed_header_by_hash(hash)
2466 }
2467 }
2468
2469 fn lowest_buffered_ancestor_or(&self, hash: B256) -> B256 {
2476 self.state
2477 .buffer
2478 .lowest_ancestor(&hash)
2479 .map(|block| block.parent_hash())
2480 .unwrap_or_else(|| hash)
2481 }
2482
2483 fn latest_valid_hash_for_invalid_payload(
2494 &mut self,
2495 parent_hash: B256,
2496 ) -> ProviderResult<Option<B256>> {
2497 if self.has_block_by_hash(parent_hash)? {
2499 return Ok(Some(parent_hash))
2500 }
2501
2502 let mut current_hash = parent_hash;
2505 let mut current_block = self.state.invalid_headers.get(¤t_hash);
2506 while let Some(block_with_parent) = current_block {
2507 current_hash = block_with_parent.parent;
2508 current_block = self.state.invalid_headers.get(¤t_hash);
2509
2510 if current_block.is_none() && self.has_block_by_hash(current_hash)? {
2513 return Ok(Some(current_hash))
2514 }
2515 }
2516 Ok(None)
2517 }
2518
2519 fn prepare_invalid_response(&mut self, parent_hash: B256) -> ProviderResult<PayloadStatus> {
2523 let valid_parent_hash = match self.sealed_header_by_hash(parent_hash)? {
2524 Some(parent) if !parent.difficulty().is_zero() => Some(B256::ZERO),
2528 Some(_) => Some(parent_hash),
2529 None => self.latest_valid_hash_for_invalid_payload(parent_hash)?,
2530 };
2531
2532 Ok(PayloadStatus::from_status(PayloadStatusEnum::Invalid {
2533 validation_error: PayloadValidationError::LinksToRejectedPayload.to_string(),
2534 })
2535 .with_latest_valid_hash(valid_parent_hash.unwrap_or_default()))
2536 }
2537
2538 fn is_sync_target_head(&self, block_hash: B256) -> bool {
2542 if let Some(target) = self.state.forkchoice_state_tracker.sync_target_state() {
2543 return target.head_block_hash == block_hash
2544 }
2545 false
2546 }
2547
2548 fn is_any_sync_target(&self, block_hash: B256) -> bool {
2552 if let Some(target) = self.state.forkchoice_state_tracker.sync_target_state() {
2553 return target.contains(block_hash)
2554 }
2555 false
2556 }
2557
2558 fn check_invalid_ancestor_with_head(
2564 &mut self,
2565 check: B256,
2566 head: &SealedBlock<N::Block>,
2567 ) -> ProviderResult<Option<PayloadStatus>> {
2568 let Some(header) = self.state.invalid_headers.get(&check) else { return Ok(None) };
2570
2571 Ok(Some(self.on_invalid_new_payload(head.clone(), header)?))
2572 }
2573
2574 fn on_invalid_new_payload(
2576 &mut self,
2577 head: SealedBlock<N::Block>,
2578 invalid: BlockWithParent,
2579 ) -> ProviderResult<PayloadStatus> {
2580 let status = self.prepare_invalid_response(invalid.parent)?;
2582
2583 self.state.invalid_headers.insert_with_invalid_ancestor(head.hash(), invalid);
2585 self.emit_event(ConsensusEngineEvent::InvalidBlock {
2586 block: Box::new(head),
2587 error: PayloadValidationError::LinksToRejectedPayload.to_string(),
2588 });
2589
2590 Ok(status)
2591 }
2592
2593 fn find_invalid_ancestor(&mut self, payload: &T::ExecutionData) -> Option<BlockWithParent> {
2607 let parent_hash = payload.parent_hash();
2608 let block_hash = payload.block_hash();
2609
2610 if let Some(entry) = self.state.invalid_headers.get(&block_hash) {
2612 return Some(entry);
2613 }
2614
2615 let mut lowest_buffered_ancestor = self.lowest_buffered_ancestor_or(block_hash);
2616 if lowest_buffered_ancestor == block_hash {
2617 lowest_buffered_ancestor = parent_hash;
2618 }
2619
2620 self.state.invalid_headers.get(&lowest_buffered_ancestor)
2622 }
2623
2624 fn handle_invalid_ancestor_payload(
2633 &mut self,
2634 payload: T::ExecutionData,
2635 invalid: BlockWithParent,
2636 ) -> Result<PayloadStatus, InsertBlockFatalError> {
2637 let parent_hash = payload.parent_hash();
2638 let num_hash = payload.num_hash();
2639
2640 let block = match self.payload_validator.convert_payload_to_block(payload) {
2646 Ok(block) => block,
2647 Err(error) => return Ok(self.on_new_payload_error(error, num_hash, parent_hash)?),
2648 };
2649
2650 Ok(self.on_invalid_new_payload(block, invalid)?)
2651 }
2652
2653 fn check_invalid_ancestor(&mut self, head: B256) -> ProviderResult<Option<PayloadStatus>> {
2656 let Some(header) = self.state.invalid_headers.get(&head) else { return Ok(None) };
2658
2659 match self.prepare_invalid_response(header.parent) {
2661 Ok(status) => Ok(Some(status)),
2662 Err(err) => {
2663 debug!(target: "engine::tree", %err, "Failed to prepare invalid response for ancestor check");
2664 Ok(Some(PayloadStatus::from_status(PayloadStatusEnum::Invalid {
2666 validation_error: PayloadValidationError::LinksToRejectedPayload.to_string(),
2667 })))
2668 }
2669 }
2670 }
2671
2672 fn validate_block(&self, block: &SealedBlock<N::Block>) -> Result<(), ConsensusError> {
2675 if let Err(e) = self.consensus.validate_header(block.sealed_header()) {
2676 error!(target: "engine::tree", ?block, "Failed to validate header {}: {e}", block.hash());
2677 return Err(e)
2678 }
2679
2680 if let Err(e) = self.consensus.validate_block_pre_execution(block) {
2681 error!(target: "engine::tree", ?block, "Failed to validate block {}: {e}", block.hash());
2682 return Err(e)
2683 }
2684
2685 Ok(())
2686 }
2687
2688 #[instrument(level = "debug", target = "engine::tree", skip(self))]
2690 fn try_connect_buffered_blocks(
2691 &mut self,
2692 parent: BlockNumHash,
2693 ) -> Result<(), InsertBlockFatalError> {
2694 let blocks = self.state.buffer.remove_block_with_children(&parent.hash);
2695
2696 if blocks.is_empty() {
2697 return Ok(())
2699 }
2700
2701 let now = Instant::now();
2702 let block_count = blocks.len();
2703 for child in blocks {
2704 let child_num_hash = child.num_hash();
2705 match self.insert_block(child) {
2706 Ok(res) => {
2707 debug!(target: "engine::tree", child =?child_num_hash, ?res, "connected buffered block");
2708 if self.is_any_sync_target(child_num_hash.hash) &&
2709 matches!(res, InsertPayloadOk::Inserted(BlockStatus::Valid))
2710 {
2711 debug!(target: "engine::tree", child =?child_num_hash, "connected sync target block");
2712 self.make_canonical(child_num_hash.hash)?;
2715 }
2716 }
2717 Err(err) => {
2718 if let InsertPayloadError::Block(err) = err {
2719 debug!(target: "engine::tree", ?err, "failed to connect buffered block to tree");
2720 if let Err(fatal) = self.on_insert_block_error(err) {
2721 warn!(target: "engine::tree", %fatal, "fatal error occurred while connecting buffered blocks");
2722 return Err(fatal)
2723 }
2724 }
2725 }
2726 }
2727 }
2728
2729 debug!(target: "engine::tree", elapsed = ?now.elapsed(), %block_count, "connected buffered blocks");
2730 Ok(())
2731 }
2732
2733 fn buffer_block(
2735 &mut self,
2736 block: SealedBlock<N::Block>,
2737 ) -> Result<(), InsertBlockError<N::Block>> {
2738 if let Err(err) = self.validate_block(&block) {
2739 return Err(InsertBlockError::consensus_error(err, block))
2740 }
2741 self.state.buffer.insert_block(block);
2742 Ok(())
2743 }
2744
2745 #[inline]
2750 const fn exceeds_backfill_run_threshold(&self, local_tip: u64, block: u64) -> bool {
2751 block > local_tip && block - local_tip > MIN_BLOCKS_FOR_PIPELINE_RUN
2752 }
2753
2754 #[inline]
2757 const fn distance_from_local_tip(&self, local_tip: u64, block: u64) -> Option<u64> {
2758 if block > local_tip {
2759 Some(block - local_tip)
2760 } else {
2761 None
2762 }
2763 }
2764
2765 const fn backfill_target_hash(&self, state: ForkchoiceState) -> B256 {
2773 if self.engine_kind.is_opstack() {
2774 state.head_block_hash
2775 } else {
2776 state.finalized_block_hash
2777 }
2778 }
2779
2780 fn backfill_sync_target(
2787 &self,
2788 canonical_tip_num: u64,
2789 target_block_number: u64,
2790 downloaded_block: Option<BlockNumHash>,
2791 ) -> Option<B256> {
2792 let state = self.state.forkchoice_state_tracker.sync_target_state()?;
2793 let target_hash = self.backfill_target_hash(state);
2794
2795 let exceeds_backfill_threshold = match downloaded_block.as_ref() {
2797 Some(downloaded_block) if downloaded_block.hash == target_hash => {
2799 self.exceeds_backfill_run_threshold(canonical_tip_num, downloaded_block.number)
2800 }
2801 _ => match self.state.buffer.block(&target_hash) {
2802 Some(buffered_target) => {
2804 self.exceeds_backfill_run_threshold(canonical_tip_num, buffered_target.number())
2805 }
2806 None => self.exceeds_backfill_run_threshold(canonical_tip_num, target_block_number),
2808 },
2809 };
2810
2811 if !exceeds_backfill_threshold {
2812 return None
2813 }
2814
2815 match self.provider.header_by_hash_or_number(target_hash.into()) {
2817 Err(err) => {
2818 warn!(target: "engine::tree", %err, "Failed to get backfill target block header");
2819 None
2820 }
2821 Ok(None) if !target_hash.is_zero() => Some(target_hash),
2823 Ok(None) => {
2824 debug!(target: "engine::tree", hash=?state.head_block_hash, "Setting head hash as an optimistic backfill target.");
2837 Some(state.head_block_hash)
2838 }
2839 Ok(Some(_)) => None,
2841 }
2842 }
2843
2844 fn find_disk_reorg(&self) -> ProviderResult<Option<u64>> {
2847 let mut canonical = self.state.tree_state.current_canonical_head;
2848 let mut persisted = self.persistence_state.last_persisted_block;
2849
2850 let parent_num_hash = |num_hash: NumHash| -> ProviderResult<NumHash> {
2851 Ok(self
2852 .sealed_header_by_hash(num_hash.hash)?
2853 .ok_or(ProviderError::BlockHashNotFound(num_hash.hash))?
2854 .parent_num_hash())
2855 };
2856
2857 while canonical.number > persisted.number {
2860 canonical = parent_num_hash(canonical)?;
2861 }
2862
2863 if canonical == persisted {
2865 return Ok(None);
2866 }
2867
2868 while persisted.number > canonical.number {
2874 persisted = parent_num_hash(persisted)?;
2875 }
2876
2877 debug_assert_eq!(persisted.number, canonical.number);
2878
2879 while persisted.hash != canonical.hash {
2881 canonical = parent_num_hash(canonical)?;
2882 persisted = parent_num_hash(persisted)?;
2883 }
2884
2885 debug!(target: "engine::tree", remove_above=persisted.number, "on-disk reorg detected");
2886
2887 Ok(Some(persisted.number))
2888 }
2889
2890 fn on_canonical_chain_update(&mut self, chain_update: NewCanonicalChain<N>) {
2894 trace!(target: "engine::tree", new_blocks = %chain_update.new_block_count(), reorged_blocks = %chain_update.reorged_block_count(), "applying new chain update");
2895 let start = Instant::now();
2896
2897 self.state.tree_state.set_canonical_head(chain_update.tip().num_hash());
2899
2900 let tip = chain_update.tip().clone_sealed_header();
2901 let notification = chain_update.to_chain_notification();
2902
2903 if let NewCanonicalChain::Reorg { new, old } = &chain_update {
2905 let new_first = new.first().map(|first| first.recovered_block().num_hash());
2906 let old_first = old.first().map(|first| first.recovered_block().num_hash());
2907 trace!(target: "engine::tree", ?new_first, ?old_first, "Reorg detected, new and old first blocks");
2908
2909 self.state.set_pending_sparse_trie_prune(false);
2910 self.update_reorg_metrics(old.len(), old_first);
2911 self.reinsert_reorged_blocks(new.clone());
2912 self.reinsert_reorged_blocks(old.clone());
2913 }
2914
2915 self.canonical_in_memory_state.update_chain(chain_update);
2917 self.canonical_in_memory_state.set_canonical_head(tip.clone());
2918 self.payload_validator.on_canonical_head_changed(tip.hash(), &self.state);
2919
2920 self.metrics.tree.canonical_chain_height.set(tip.number() as f64);
2922
2923 self.canonical_in_memory_state.notify_canon_state(notification);
2925
2926 self.emit_event(ConsensusEngineEvent::CanonicalChainCommitted(
2928 Box::new(tip),
2929 start.elapsed(),
2930 ));
2931 }
2932
2933 fn update_reorg_metrics(&self, old_chain_length: usize, first_reorged_block: Option<NumHash>) {
2935 if let Some(first_reorged_block) = first_reorged_block.map(|block| block.number) {
2936 if let Some(finalized) = self.canonical_in_memory_state.get_finalized_num_hash() &&
2937 first_reorged_block <= finalized.number
2938 {
2939 self.metrics.tree.reorgs.finalized.increment(1);
2940 } else if let Some(safe) = self.canonical_in_memory_state.get_safe_num_hash() &&
2941 first_reorged_block <= safe.number
2942 {
2943 self.metrics.tree.reorgs.safe.increment(1);
2944 } else {
2945 self.metrics.tree.reorgs.head.increment(1);
2946 }
2947 } else {
2948 debug_unreachable!("Reorged chain doesn't have any blocks");
2949 }
2950 self.metrics.tree.latest_reorg_depth.set(old_chain_length as f64);
2951 }
2952
2953 fn reinsert_reorged_blocks(&mut self, new_chain: Vec<ExecutedBlock<N>>) {
2955 for block in new_chain {
2956 if self
2957 .state
2958 .tree_state
2959 .executed_block_by_hash(block.recovered_block().hash())
2960 .is_none()
2961 {
2962 trace!(target: "engine::tree", num=?block.recovered_block().number(), hash=?block.recovered_block().hash(), "Reinserting block into tree state");
2963 self.state.tree_state.insert_executed(block);
2964 }
2965 }
2966 }
2967
2968 fn on_disconnected_downloaded_block(
2973 &self,
2974 downloaded_block: BlockNumHash,
2975 missing_parent: BlockNumHash,
2976 head: BlockNumHash,
2977 ) -> Option<TreeEvent> {
2978 if let Some(target) =
2980 self.backfill_sync_target(head.number, missing_parent.number, Some(downloaded_block))
2981 {
2982 trace!(target: "engine::tree", %target, "triggering backfill on downloaded block");
2983 return Some(TreeEvent::BackfillAction(BackfillAction::Start(target.into())));
2984 }
2985
2986 let request = if let Some(distance) =
2996 self.distance_from_local_tip(head.number, missing_parent.number)
2997 {
2998 trace!(target: "engine::tree", %distance, missing=?missing_parent, "downloading missing parent block range");
2999 DownloadRequest::BlockRange(missing_parent.hash, distance)
3000 } else {
3001 trace!(target: "engine::tree", missing=?missing_parent, "downloading missing parent block");
3002 DownloadRequest::single_block(missing_parent.hash)
3005 };
3006
3007 Some(TreeEvent::Download(request))
3008 }
3009
3010 fn on_valid_downloaded_block(
3017 &mut self,
3018 block_num_hash: BlockNumHash,
3019 ) -> Result<Option<TreeEvent>, InsertBlockFatalError> {
3020 if let Some(sync_target) = self.state.forkchoice_state_tracker.sync_target_state() &&
3023 sync_target.contains(block_num_hash.hash)
3024 {
3025 debug!(target: "engine::tree", ?sync_target, "appended downloaded sync target block");
3026
3027 if sync_target.head_block_hash == block_num_hash.hash {
3028 return Ok(Some(TreeEvent::TreeAction(TreeAction::MakeCanonical {
3030 sync_target_head: block_num_hash.hash,
3031 })))
3032 }
3033
3034 self.make_canonical(block_num_hash.hash)?;
3038 self.try_connect_buffered_blocks(block_num_hash)?;
3039
3040 if self.state.tree_state.canonical_block_hash() != sync_target.head_block_hash {
3043 let target = self.lowest_buffered_ancestor_or(sync_target.head_block_hash);
3044 trace!(target: "engine::tree", %target, "sync target head not yet reached, downloading head block");
3045 return Ok(Some(TreeEvent::Download(DownloadRequest::single_block(target))))
3046 }
3047
3048 return Ok(None)
3049 }
3050 trace!(target: "engine::tree", "appended downloaded block");
3051 self.try_connect_buffered_blocks(block_num_hash)?;
3052 Ok(None)
3053 }
3054
3055 #[instrument(level = "debug", target = "engine::tree", skip_all, fields(block_hash = %block.hash(), block_num = %block.number()))]
3061 fn on_downloaded_block(
3062 &mut self,
3063 block: SealedBlock<N::Block>,
3064 ) -> Result<Option<TreeEvent>, InsertBlockFatalError> {
3065 let block_num_hash = block.num_hash();
3066 let lowest_buffered_ancestor = self.lowest_buffered_ancestor_or(block_num_hash.hash);
3067 if self.check_invalid_ancestor_with_head(lowest_buffered_ancestor, &block)?.is_some() {
3068 return Ok(None)
3069 }
3070
3071 if !self.backfill_sync_state.is_idle() {
3072 return Ok(None)
3073 }
3074
3075 match self.insert_block(block) {
3077 Ok(InsertPayloadOk::Inserted(BlockStatus::Valid)) => {
3078 return self.on_valid_downloaded_block(block_num_hash);
3079 }
3080 Ok(InsertPayloadOk::Inserted(BlockStatus::Disconnected { head, missing_ancestor })) => {
3081 return Ok(self.on_disconnected_downloaded_block(
3084 block_num_hash,
3085 missing_ancestor,
3086 head,
3087 ))
3088 }
3089 Ok(InsertPayloadOk::AlreadySeen(_)) => {
3090 trace!(target: "engine::tree", "downloaded block already executed");
3091 }
3092 Err(err) => {
3093 if let InsertPayloadError::Block(err) = err {
3094 debug!(target: "engine::tree", err=%err.kind(), "failed to insert downloaded block");
3095 if let Err(fatal) = self.on_insert_block_error(err) {
3096 warn!(target: "engine::tree", %fatal, "fatal error occurred while inserting downloaded block");
3097 return Err(fatal)
3098 }
3099 }
3100 }
3101 }
3102 Ok(None)
3103 }
3104
3105 fn insert_payload(
3114 &mut self,
3115 payload: T::ExecutionData,
3116 ) -> Result<InsertPayloadOk, InsertPayloadError<N::Block>> {
3117 self.insert_block_or_payload(
3118 payload.block_with_parent(),
3119 payload,
3120 |validator, payload, ctx| validator.validate_payload(payload, ctx),
3121 |this, payload| Ok(this.payload_validator.convert_payload_to_block(payload)?),
3122 )
3123 }
3124
3125 fn insert_block(
3126 &mut self,
3127 block: SealedBlock<N::Block>,
3128 ) -> Result<InsertPayloadOk, InsertPayloadError<N::Block>> {
3129 self.insert_block_or_payload(
3130 block.block_with_parent(),
3131 block,
3132 |validator, block, ctx| validator.validate_block(block, ctx),
3133 |_, block| Ok(block),
3134 )
3135 }
3136
3137 #[instrument(level = "debug", target = "engine::tree", skip_all, fields(?block_id))]
3154 fn insert_block_or_payload<Input, Err>(
3155 &mut self,
3156 block_id: BlockWithParent,
3157 input: Input,
3158 execute: impl FnOnce(&mut V, Input, TreeCtx<'_, N>) -> Result<ValidationOutput<N>, Err>,
3159 convert_to_block: impl FnOnce(&mut Self, Input) -> Result<SealedBlock<N::Block>, Err>,
3160 ) -> Result<InsertPayloadOk, Err>
3161 where
3162 Err: From<InsertBlockError<N::Block>>,
3163 {
3164 let block_insert_start = Instant::now();
3165 let block_num_hash = block_id.block;
3166 debug!(target: "engine::tree", block=?block_num_hash, parent = ?block_id.parent, "Inserting new block into tree");
3167
3168 if self.state.tree_state.contains_hash(&block_num_hash.hash) {
3170 convert_to_block(self, input)?;
3171 return Ok(InsertPayloadOk::AlreadySeen(BlockStatus::Valid));
3172 }
3173
3174 if block_num_hash.number <= self.persistence_state.last_persisted_block.number {
3177 match self.provider.sealed_header_by_hash(block_num_hash.hash) {
3178 Err(err) => {
3179 let block = convert_to_block(self, input)?;
3180 return Err(InsertBlockError::new(block, err.into()).into());
3181 }
3182 Ok(Some(_)) => {
3183 convert_to_block(self, input)?;
3184 return Ok(InsertPayloadOk::AlreadySeen(BlockStatus::Valid));
3185 }
3186 Ok(None) => {}
3187 }
3188 }
3189
3190 match self.state_provider_builder(block_id.parent) {
3192 Err(err) => {
3193 let block = convert_to_block(self, input)?;
3194 return Err(InsertBlockError::new(block, err.into()).into());
3195 }
3196 Ok(None) => {
3197 let block = convert_to_block(self, input)?;
3198
3199 let missing_ancestor = self
3202 .state
3203 .buffer
3204 .lowest_ancestor(&block.parent_hash())
3205 .map(|block| block.parent_num_hash())
3206 .unwrap_or_else(|| block.parent_num_hash());
3207
3208 self.state.buffer.insert_block(block);
3209
3210 return Ok(InsertPayloadOk::Inserted(BlockStatus::Disconnected {
3211 head: self.state.tree_state.current_canonical_head,
3212 missing_ancestor,
3213 }))
3214 }
3215 Ok(Some(_)) => {}
3216 }
3217
3218 let is_fork = block_id.block.number <= self.state.tree_state.current_canonical_head.number;
3223
3224 let ctx = TreeCtx::new(&mut self.state, &self.canonical_in_memory_state);
3225
3226 let start = Instant::now();
3227
3228 let ValidationOutput {
3229 executed_block: executed,
3230 execution_timing_stats: timing_stats,
3231 raw_bal,
3232 } = execute(&mut self.payload_validator, input, ctx)?;
3233
3234 if let Some(raw_bal) = raw_bal {
3235 let num_hash = executed.recovered_block().num_hash();
3236 if let Err(err) = self.provider.bal_store().insert(num_hash, raw_bal) {
3237 warn!(
3238 target: "engine::tree",
3239 ?num_hash,
3240 %err,
3241 "Failed to store validated block access list"
3242 );
3243 }
3244 }
3245
3246 if let Some(stats) = timing_stats {
3249 if let Some(threshold) = self.config.slow_block_threshold() {
3250 let total_duration = stats.execution_duration + stats.state_hash_duration;
3251 if total_duration > threshold {
3252 self.emit_event(ConsensusEngineEvent::SlowBlock(SlowBlockInfo {
3253 stats: stats.clone(),
3254 commit_duration: None,
3255 total_duration,
3256 }));
3257 }
3258 }
3259 self.execution_timing_stats.insert(executed.recovered_block().hash(), stats);
3260 }
3261
3262 if self.state.tree_state.canonical_block_hash() == executed.recovered_block().parent_hash()
3264 {
3265 debug!(target: "engine::tree", pending=?block_num_hash, "updating pending block");
3266 self.canonical_in_memory_state.set_pending_block(executed.clone());
3267 }
3268
3269 self.state.tree_state.insert_executed(executed.clone());
3270 self.metrics.engine.executed_blocks.set(self.state.tree_state.block_count() as f64);
3271
3272 let elapsed = start.elapsed();
3274 let engine_event = if is_fork {
3275 ConsensusEngineEvent::ForkBlockAdded(executed, elapsed)
3276 } else {
3277 ConsensusEngineEvent::CanonicalBlockAdded(executed, elapsed)
3278 };
3279 self.emit_event(EngineApiEvent::BeaconConsensus(engine_event));
3280
3281 self.metrics
3282 .engine
3283 .block_insert_total_duration
3284 .record(block_insert_start.elapsed().as_secs_f64());
3285 debug!(target: "engine::tree", block=?block_num_hash, "Finished inserting block");
3286 Ok(InsertPayloadOk::Inserted(BlockStatus::Valid))
3287 }
3288
3289 fn on_insert_block_error(
3295 &mut self,
3296 error: InsertBlockError<N::Block>,
3297 ) -> Result<PayloadStatus, InsertBlockFatalError> {
3298 let (block, error) = error.split();
3299
3300 let validation_err = error.ensure_validation_error()?;
3303
3304 warn!(
3308 target: "engine::tree",
3309 invalid_hash=%block.hash(),
3310 invalid_number=block.number(),
3311 %validation_err,
3312 "Invalid block error on new payload",
3313 );
3314 let latest_valid_hash = self.latest_valid_hash_for_invalid_payload(block.parent_hash())?;
3315
3316 let is_transient = match &validation_err {
3318 InsertBlockValidationError::Consensus(err) => self.consensus.is_transient_error(err),
3319 _ => false,
3320 };
3321 if is_transient {
3322 warn!(
3323 target: "engine::tree",
3324 invalid_hash=%block.hash(),
3325 invalid_number=block.number(),
3326 %validation_err,
3327 "Skipping invalid header cache insert for transient validation error",
3328 );
3329 } else {
3330 self.state.invalid_headers.insert(block.block_with_parent());
3331 }
3332 self.emit_event(EngineApiEvent::BeaconConsensus(ConsensusEngineEvent::InvalidBlock {
3333 block: Box::new(block),
3334 error: validation_err.to_string(),
3335 }));
3336
3337 Ok(PayloadStatus::new(
3338 PayloadStatusEnum::Invalid { validation_error: validation_err.to_string() },
3339 latest_valid_hash,
3340 ))
3341 }
3342
3343 fn on_new_payload_error(
3345 &mut self,
3346 error: NewPayloadError,
3347 payload_num_hash: NumHash,
3348 parent_hash: B256,
3349 ) -> ProviderResult<PayloadStatus> {
3350 error!(target: "engine::tree", payload=?payload_num_hash, %error, "Invalid payload");
3351 let latest_valid_hash =
3354 if error.is_block_hash_mismatch() || error.is_invalid_versioned_hashes() {
3355 None
3359 } else {
3360 self.latest_valid_hash_for_invalid_payload(parent_hash)?
3361 };
3362
3363 let status = PayloadStatusEnum::from(error);
3364 Ok(PayloadStatus::new(status, latest_valid_hash))
3365 }
3366
3367 pub fn find_canonical_header(
3369 &self,
3370 hash: B256,
3371 ) -> Result<Option<SealedHeader<N::BlockHeader>>, ProviderError> {
3372 let mut canonical = self.canonical_in_memory_state.header_by_hash(hash);
3373
3374 if canonical.is_none() {
3375 canonical = self.provider.header(hash)?.map(|header| SealedHeader::new(header, hash));
3376 }
3377
3378 Ok(canonical)
3379 }
3380
3381 fn update_finalized_block(
3383 &self,
3384 finalized_block_hash: B256,
3385 ) -> Result<(), OnForkChoiceUpdated> {
3386 if finalized_block_hash.is_zero() {
3387 return Ok(())
3388 }
3389
3390 match self.find_canonical_header(finalized_block_hash) {
3391 Ok(None) => {
3392 debug!(target: "engine::tree", "Finalized block not found in canonical chain");
3393 return Err(OnForkChoiceUpdated::invalid_state())
3395 }
3396 Ok(Some(finalized)) => {
3397 if Some(finalized.num_hash()) !=
3398 self.canonical_in_memory_state.get_finalized_num_hash()
3399 {
3400 let _ = self.persistence.save_finalized_block_number(finalized.number());
3403 self.canonical_in_memory_state.set_finalized(finalized.clone());
3404 self.metrics.tree.finalized_block_height.set(finalized.number() as f64);
3406 }
3407 }
3408 Err(err) => {
3409 error!(target: "engine::tree", %err, "Failed to fetch finalized block header");
3410 }
3411 }
3412
3413 Ok(())
3414 }
3415
3416 fn update_safe_block(&self, safe_block_hash: B256) -> Result<(), OnForkChoiceUpdated> {
3418 if safe_block_hash.is_zero() {
3419 return Ok(())
3420 }
3421
3422 match self.find_canonical_header(safe_block_hash) {
3423 Ok(None) => {
3424 debug!(target: "engine::tree", "Safe block not found in canonical chain");
3425 return Err(OnForkChoiceUpdated::invalid_state())
3427 }
3428 Ok(Some(safe)) => {
3429 if Some(safe.num_hash()) != self.canonical_in_memory_state.get_safe_num_hash() {
3430 let _ = self.persistence.save_safe_block_number(safe.number());
3433 self.canonical_in_memory_state.set_safe(safe.clone());
3434 self.metrics.tree.safe_block_height.set(safe.number() as f64);
3436 }
3437 }
3438 Err(err) => {
3439 error!(target: "engine::tree", %err, "Failed to fetch safe block header");
3440 }
3441 }
3442
3443 Ok(())
3444 }
3445
3446 fn ensure_consistent_forkchoice_state(
3455 &self,
3456 state: ForkchoiceState,
3457 ) -> Result<(), OnForkChoiceUpdated> {
3458 self.update_finalized_block(state.finalized_block_hash)?;
3464
3465 self.update_safe_block(state.safe_block_hash)
3471 }
3472
3473 fn process_payload_attributes(
3488 &mut self,
3489 attributes: T::PayloadAttributes,
3490 head: &N::BlockHeader,
3491 state: ForkchoiceState,
3492 ) -> OnForkChoiceUpdated {
3493 if let Err(err) =
3494 self.payload_validator.validate_payload_attributes_against_header(&attributes, head)
3495 {
3496 warn!(target: "engine::tree", %err, ?head, "Invalid payload attributes");
3497 return OnForkChoiceUpdated::invalid_payload_attributes()
3498 }
3499
3500 let payload_build = self.payload_builds.acquire();
3508
3509 let resources = self
3510 .payload_validator
3511 .payload_builder_resources(
3512 state.head_block_hash,
3513 head,
3514 attributes.timestamp(),
3515 &mut self.state,
3516 )
3517 .with_lease(PayloadBuilderLease::new(payload_build));
3518
3519 let pending_payload_id = self.payload_builder.send_new_payload(BuildNewPayload {
3522 parent_hash: state.head_block_hash,
3523 attributes,
3524 resources,
3525 });
3526
3527 OnForkChoiceUpdated::updated_with_pending_payload_id(
3539 PayloadStatus::new(PayloadStatusEnum::Valid, Some(state.head_block_hash)),
3540 pending_payload_id,
3541 )
3542 }
3543
3544 pub(crate) fn remove_before(
3551 &mut self,
3552 upper_bound: BlockNumHash,
3553 finalized_hash: Option<B256>,
3554 ) -> ProviderResult<()> {
3555 let num = if let Some(hash) = finalized_hash {
3558 self.provider.block_number(hash)?.map(|number| BlockNumHash { number, hash })
3559 } else {
3560 None
3561 };
3562
3563 self.state.tree_state.remove_until(
3564 upper_bound,
3565 self.persistence_state.last_persisted_block.hash,
3566 num,
3567 );
3568 Ok(())
3569 }
3570
3571 pub fn state_provider_builder(
3576 &self,
3577 hash: B256,
3578 ) -> ProviderResult<Option<StateProviderBuilder<N, P>>>
3579 where
3580 P: BlockReader + StateProviderFactory + StateReader + Clone,
3581 {
3582 if !self.state.tree_state.contains_hash(&hash) && self.provider.header(hash)?.is_none() {
3583 debug!(target: "engine::tree", %hash, "no canonical state found for block");
3584 return Ok(None)
3585 }
3586
3587 Ok(Some(StateProviderBuilder::new(
3588 self.provider.clone(),
3589 hash,
3590 self.state.tree_state.overlay_manager.clone(),
3591 )))
3592 }
3593}
3594
3595#[derive(Debug)]
3597enum LoopEvent<T, N>
3598where
3599 N: NodePrimitives,
3600 T: PayloadTypes,
3601{
3602 EngineMessage(FromEngine<EngineApiRequest<T, N>, N::Block>),
3604 PersistenceComplete {
3606 result: PersistenceResult,
3608 start_time: Instant,
3610 },
3611 PayloadBuildFinished,
3613 Disconnected,
3615}
3616
3617#[derive(Clone, Debug)]
3619struct PayloadBuildTracker {
3620 active: Arc<AtomicUsize>,
3621 finished_tx: Sender<()>,
3622}
3623
3624impl PayloadBuildTracker {
3625 fn new() -> (Self, Receiver<()>) {
3627 let (finished_tx, finished_rx) = crossbeam_channel::bounded(1);
3628 (Self { active: Arc::new(AtomicUsize::new(0)), finished_tx }, finished_rx)
3629 }
3630
3631 fn acquire(&self) -> PayloadBuildLease {
3633 self.active.fetch_add(1, Ordering::AcqRel);
3634 PayloadBuildLease {
3635 active: Arc::clone(&self.active),
3636 finished_tx: self.finished_tx.clone(),
3637 }
3638 }
3639
3640 fn is_active(&self) -> bool {
3642 self.active.load(Ordering::Acquire) != 0
3643 }
3644}
3645
3646#[derive(Debug)]
3648struct PayloadBuildLease {
3649 active: Arc<AtomicUsize>,
3650 finished_tx: Sender<()>,
3651}
3652
3653impl Drop for PayloadBuildLease {
3654 fn drop(&mut self) {
3655 let previous = self.active.fetch_sub(1, Ordering::AcqRel);
3656 debug_assert!(previous > 0, "payload build lease count underflow");
3657
3658 if previous == 1 {
3659 let _ = self.finished_tx.try_send(());
3662 }
3663 }
3664}
3665
3666#[derive(Clone, Copy, Debug, Eq, PartialEq)]
3672pub enum BlockStatus {
3673 Valid,
3679 Disconnected {
3681 head: BlockNumHash,
3683 missing_ancestor: BlockNumHash,
3685 },
3686}
3687
3688#[derive(Clone, Copy, Debug, Eq, PartialEq)]
3693pub enum InsertPayloadOk {
3694 AlreadySeen(BlockStatus),
3696 Inserted(BlockStatus),
3698}
3699
3700#[derive(Debug, Clone, Copy)]
3702enum PersistTarget {
3703 Threshold,
3705 Head,
3707}
3708
3709#[derive(Debug, Clone, Copy, Default)]
3711pub struct CacheWaitDurations {
3712 pub execution_cache: Duration,
3714 pub sparse_trie: Duration,
3716}
3717
3718pub trait WaitForCaches {
3723 fn wait_for_caches(&self) -> CacheWaitDurations;
3727}