Skip to main content

reth_engine_tree/tree/
mod.rs

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
98/// The largest gap for which the tree will be used to sync individual blocks by downloading them.
99///
100/// This is the default threshold, and represents the distance (gap) from the local head to a
101/// new (canonical) block, e.g. the forkchoice head block. If the block distance from the local head
102/// exceeds this threshold, the pipeline will be used to backfill the gap more efficiently.
103///
104/// E.g.: Local head `block.number` is 100 and the forkchoice head `block.number` is 133 (more than
105/// an epoch has slots), then this exceeds the threshold at which the pipeline should be used to
106/// backfill this gap.
107pub(crate) const MIN_BLOCKS_FOR_PIPELINE_RUN: u64 = EPOCH_SLOTS;
108
109/// The minimum number of blocks to retain in the changeset cache after eviction.
110///
111/// This ensures that recent changesets are kept in memory for potential reorgs,
112/// even when the finalized block is not set (e.g., on L2s like Optimism).
113const CHANGESET_CACHE_RETENTION_BLOCKS: u64 = 64;
114
115/// A builder for creating state providers that can be used across threads.
116#[derive(Clone, Debug)]
117pub struct StateProviderBuilder<N: NodePrimitives, P> {
118    /// The provider factory used to create providers.
119    provider_factory: P,
120    /// Hash of the block whose state to provide.
121    parent_hash: B256,
122    /// Tracks the in-memory parent chain and its overlays.
123    overlay_manager: OverlayManager<N>,
124}
125
126impl<N: NodePrimitives, P> StateProviderBuilder<N, P> {
127    /// Creates a new state provider builder for `parent_hash`.
128    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    /// Creates a new state provider from this builder.
148    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/// Tracks the state of the engine api internals.
177///
178/// This type is not shareable.
179#[derive(Debug)]
180pub struct EngineApiTreeState<N: NodePrimitives> {
181    /// Tracks the state of the blockchain tree.
182    tree_state: TreeState<N>,
183    /// Whether the next sparse trie task should attempt cache pruning during trie preservation.
184    pending_sparse_trie_prune: bool,
185    /// Tracks the forkchoice state updates received by the CL.
186    forkchoice_state_tracker: ForkchoiceStateTracker,
187    /// Buffer of detached blocks.
188    buffer: BlockBuffer<N::Block>,
189    /// Tracks the header of invalid payloads that were rejected by the engine because they're
190    /// invalid.
191    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    /// Returns a reference to the tree state.
216    pub const fn tree_state(&self) -> &TreeState<N> {
217        &self.tree_state
218    }
219
220    /// Returns whether sparse trie pruning is pending.
221    pub const fn pending_sparse_trie_prune(&self) -> bool {
222        self.pending_sparse_trie_prune
223    }
224
225    /// Sets whether sparse trie pruning is pending for the next sparse trie task.
226    pub const fn set_pending_sparse_trie_prune(&mut self, pending: bool) {
227        self.pending_sparse_trie_prune = pending;
228    }
229
230    /// Takes a pending sparse trie prune request, if any, and snapshots the in-memory parent chain
231    /// ending at `parent_hash`.
232    ///
233    /// `None` means no prune request is pending. `Some(Vec::new())` means a prune was requested,
234    /// but no in-memory parent-chain blocks were found for the parent hash; the sparse trie task
235    /// should still prune nodes cached before the current block's epoch.
236    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    /// Returns true if the block has been marked as invalid.
254    pub fn has_invalid_header(&mut self, hash: &B256) -> bool {
255        self.invalid_headers.get(hash).is_some()
256    }
257}
258
259/// The outcome of a tree operation.
260#[derive(Debug)]
261pub struct TreeOutcome<T> {
262    /// The outcome of the operation.
263    pub outcome: T,
264    /// An optional event to tell the caller to do something.
265    pub event: Option<TreeEvent>,
266    /// Whether the block was already seen, meaning no real execution happened during this
267    /// `newPayload` call.
268    pub already_seen: bool,
269}
270
271impl<T> TreeOutcome<T> {
272    /// Create new tree outcome.
273    pub const fn new(outcome: T) -> Self {
274        Self { outcome, event: None, already_seen: false }
275    }
276
277    /// Set event on the outcome.
278    pub fn with_event(mut self, event: TreeEvent) -> Self {
279        self.event = Some(event);
280        self
281    }
282
283    /// Set the `already_seen` flag on the outcome.
284    pub const fn with_already_seen(mut self, value: bool) -> Self {
285        self.already_seen = value;
286        self
287    }
288}
289
290/// Result of trying to insert a new payload in [`EngineApiTreeHandler`].
291#[derive(Debug)]
292pub struct TryInsertPayloadResult {
293    /// - `Valid`: Payload successfully validated and inserted
294    /// - `Syncing`: Parent missing, payload buffered for later
295    /// - Error status: Payload is invalid
296    pub status: PayloadStatus,
297    /// Whether the block was already seen
298    pub already_seen: bool,
299}
300
301impl TryInsertPayloadResult {
302    /// Convert the result into a [`TreeOutcome`].
303    #[inline]
304    pub fn into_outcome(self) -> TreeOutcome<PayloadStatus> {
305        TreeOutcome::new(self.status).with_already_seen(self.already_seen)
306    }
307}
308
309/// Events that are triggered by Tree Chain
310#[derive(Debug)]
311pub enum TreeEvent {
312    /// Tree action is needed.
313    TreeAction(TreeAction),
314    /// Backfill action is needed.
315    BackfillAction(BackfillAction),
316    /// Block download is needed.
317    Download(DownloadRequest),
318}
319
320impl TreeEvent {
321    /// Returns true if the event is a backfill action.
322    const fn is_backfill_action(&self) -> bool {
323        matches!(self, Self::BackfillAction(_))
324    }
325}
326
327/// The actions that can be performed on the tree.
328#[derive(Debug)]
329pub enum TreeAction {
330    /// Make target canonical.
331    MakeCanonical {
332        /// The sync target head hash
333        sync_target_head: B256,
334    },
335}
336
337/// The engine API tree handler implementation.
338///
339/// This type is responsible for processing engine API requests, maintaining the canonical state and
340/// emitting events.
341pub 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    /// Keeps track of internals such as executed and buffered blocks.
351    state: EngineApiTreeState<N>,
352    /// The half for sending messages to the engine.
353    ///
354    /// This is kept so that we can queue in messages to ourself that we can process later, for
355    /// example distributing workload across multiple messages that would otherwise take too long
356    /// to process. E.g. we might receive a range of downloaded blocks and we want to process
357    /// them one by one so that we can handle incoming engine API in between and don't become
358    /// unresponsive. This can happen during live sync transition where we're trying to close the
359    /// gap (up to 3 epochs of blocks in the worst case).
360    incoming_tx: Sender<FromEngine<EngineApiRequest<T, N>, N::Block>>,
361    /// Incoming engine API requests.
362    incoming: Receiver<FromEngine<EngineApiRequest<T, N>, N::Block>>,
363    /// Outgoing events that are emitted to the handler.
364    outgoing: UnboundedSender<EngineApiEvent<N>>,
365    /// Channels to the persistence layer.
366    persistence: PersistenceHandle<N>,
367    /// Tracks the state changes of the persistence task.
368    persistence_state: PersistenceState,
369    /// Flag indicating the state of the node's backfill synchronization process.
370    backfill_sync_state: BackfillSyncState,
371    /// Keeps track of the state of the canonical chain that isn't persisted yet.
372    /// This is intended to be accessed from external sources, such as rpc.
373    canonical_in_memory_state: CanonicalInMemoryState<N>,
374    /// Handle to the payload builder that will receive payload attributes for valid forkchoice
375    /// updates
376    payload_builder: PayloadBuilderHandle<T>,
377    /// Configuration settings.
378    config: TreeConfig,
379    /// Metrics for the engine api.
380    metrics: EngineApiMetrics,
381    /// The engine API variant of this handler
382    engine_kind: EngineApiKind,
383    /// The EVM configuration.
384    evm_config: C,
385    /// Timing statistics for executed blocks, keyed by block hash.
386    /// Stored here (not in `ExecutedBlock`) to avoid leaking observability concerns into the block
387    /// type. Entries are removed when blocks are persisted or invalidated.
388    execution_timing_stats: B256Map<Box<ExecutionTimingStats>>,
389    /// Tracks payload jobs that may still access in-memory overlay state.
390    payload_builds: PayloadBuildTracker,
391    /// Notifies the engine when the final active payload job finishes.
392    payload_build_finished: Receiver<()>,
393    /// A durable persistence completion whose destructive in-memory handoff is deferred until
394    /// payload jobs finish.
395    pending_persisted_handoff: Option<PersistenceResult>,
396    /// Task runtime for spawning blocking work on named, reusable threads.
397    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    /// Creates a new [`EngineApiTreeHandler`].
453    #[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    /// Creates a new [`EngineApiTreeHandler`] instance and spawns it in its
499    /// own thread.
500    ///
501    /// Returns the sender through which incoming requests can be sent to the task and the receiver
502    /// end of a [`EngineApiEvent`] unbounded channel to receive events from the engine.
503    #[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    /// Returns a [`TreeOutcome`] indicating the forkchoice head is valid and canonical.
561    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    /// Returns a new [`Sender`] to send messages to this type.
569    pub fn sender(&self) -> Sender<FromEngine<EngineApiRequest<T, N>, N::Block>> {
570        self.incoming_tx.clone()
571    }
572
573    /// How many blocks the canonical tip is ahead of the last persisted block. A large gap means
574    /// persistence is falling behind execution.
575    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    /// How many blocks beyond the configured in-memory buffer are awaiting persistence.
583    const fn persistence_backpressure_gap(&self) -> u64 {
584        self.persistence_gap().saturating_sub(self.config.memory_block_buffer_target())
585    }
586
587    /// Returns `true` when the main loop should stop draining the tree input channel.
588    ///
589    /// This is the case when persistence is already running and the number of blocks beyond the
590    /// configured in-memory buffer has reached the configured threshold.
591    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    /// Run the engine API handler.
598    ///
599    /// This will block the current thread and process incoming messages.
600    pub fn run(mut self) {
601        loop {
602            // Each iteration has three phases:
603            //
604            // 1. Non-blocking poll for persistence completion. If the background flush already
605            //    landed, absorb the result now so the gap calculation below is fresh.
606            // 2. Decide how to wait for the next event. When the canonical-to-persisted gap beyond
607            //    the in-memory buffer reaches the backpressure threshold we only block on the
608            //    persistence receiver, leaving new engine requests sitting in the unbounded
609            //    upstream channel.
610            // 3. Handle the event (engine message or persistence completion) and kick off a new
611            //    persistence cycle if the threshold is met again.
612            //
613            // The net effect: when the unbuffered persistence gap reaches the threshold, we stop
614            // processing incoming messages and let them queue in the channel. This is only a soft
615            // form of backpressure: it delays replies and, more importantly, prevents executing
616            // further blocks that would pile up in the persistence queue - where each block
617            // carries heavier state (eg. trie updates) than the raw payload sitting in the engine
618            // channel.
619            //
620            // Standard Ethereum CLs won't truly back off - the engine API has no
621            // backpressure semantics, and CLs typically timeout after ≈8s and resend - so
622            // this cannot prevent the incoming channel from growing under sustained load.
623            // But it shifts the bottleneck to the lighter-weight incoming queue rather than
624            // the costlier persistence pipeline. Other clients that respect reply latency
625            // can treat the delayed responses as a signal to chill out.
626            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            // Always check if we need to trigger new persistence after any event:
683            // - After engine messages: new blocks may have been inserted that exceed the
684            //   persistence threshold
685            // - After persistence completion: we can now persist more blocks if needed
686            if let Err(err) = self.advance_persistence() {
687                error!(target: "engine::tree", %err, "Advancing persistence failed");
688                return
689            }
690        }
691    }
692
693    /// Blocks until the in-flight persistence task completes, used when we are under
694    /// backpressure.
695    ///
696    /// Unlike `wait_for_event`, this deliberately does not read from the tree input channel. Any
697    /// requests sent to the tree remain queued upstream until persistence catches up.
698    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    /// Blocks until the next event that can safely be processed is ready.
712    ///
713    /// A pending persisted handoff keeps processing engine messages while prioritizing the
714    /// notification that all active payload builds have finished. This keeps the engine responsive
715    /// without reclaiming the in-memory overlay while a payload job may still access it. Otherwise,
716    /// uses biased selection to prioritize persistence completion to update in-memory state and
717    /// unblock further writes.
718    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        // Take ownership of persistence rx if present
734        let maybe_persistence = self.persistence_state.rx.take();
735
736        if let Some((persistence_rx, start_time, action)) = maybe_persistence {
737            // Biased select prioritizes persistence completion to update in memory state and
738            // unblock further writes
739            crossbeam_channel::select_biased! {
740                recv(persistence_rx) -> result => {
741                    // Don't put it back - consumed (oneshot-like behavior)
742                    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                    // Put the persistence rx back - we didn't consume it.
752                    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                    // Put the persistence rx back - we didn't consume it
760                    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            // No persistence in progress - wait on an incoming message or payload job completion.
769            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    /// Invoked when previously requested blocks were downloaded.
783    ///
784    /// If the block count exceeds the configured batch size we're allowed to execute at once, this
785    /// will execute the first batch and send the remaining blocks back through the channel so that
786    /// block request processing isn't blocked for a long time.
787    fn on_downloaded(
788        &mut self,
789        mut blocks: Vec<SealedBlock<N::Block>>,
790    ) -> Result<Option<TreeEvent>, InsertBlockFatalError> {
791        if blocks.is_empty() {
792            // nothing to execute
793            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                    // can exit early if backfill is needed
804                    return Ok(None)
805                }
806            }
807        }
808
809        // if we still have blocks to execute, send them as a followup request
810        if !blocks.is_empty() {
811            let _ = self.incoming_tx.send(FromEngine::DownloadedBlocks(blocks));
812        }
813
814        Ok(None)
815    }
816
817    /// When the Consensus layer receives a new block via the consensus gossip protocol,
818    /// the transactions in the block are sent to the execution layer in the form of a
819    /// [`PayloadTypes::ExecutionData`], for example
820    /// [`ExecutionData`](reth_payload_primitives::PayloadTypes::ExecutionData). The
821    /// Execution layer executes the transactions and validates the state in the block header,
822    /// then passes validation data back to Consensus layer, that adds the block to the head of
823    /// its own blockchain and attests to it. The block is then broadcast over the consensus p2p
824    /// network in the form of a "Beacon block".
825    ///
826    /// These responses should adhere to the [Engine API Spec for
827    /// `engine_newPayload`](https://github.com/ethereum/execution-apis/blob/main/src/engine/paris.md#specification).
828    ///
829    /// This returns a [`PayloadStatus`] that represents the outcome of a processed new payload and
830    /// returns an error if an internal error occurred.
831    #[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        // start timing for the new payload process
846        let start = Instant::now();
847
848        // Ensures that the given payload does not violate any consensus rules that concern the
849        // block's layout, like:
850        //    - missing or invalid base fee
851        //    - invalid extra data
852        //    - invalid transactions
853        //    - incorrect hash
854        //    - the versioned hashes passed with the payload do not exactly match transaction
855        //      versioned hashes
856        //    - the block does not contain blob transactions if it is pre-cancun
857        //
858        // This validates the following engine API rule:
859        //
860        // 3. Given the expected array of blob versioned hashes client software **MUST** run its
861        //    validation by taking the following steps:
862        //
863        //   1. Obtain the actual array by concatenating blob versioned hashes lists
864        //      (`tx.blob_versioned_hashes`) of each [blob
865        //      transaction](https://eips.ethereum.org/EIPS/eip-4844#new-transaction-type) included
866        //      in the payload, respecting the order of inclusion. If the payload has no blob
867        //      transactions the expected array **MUST** be `[]`.
868        //
869        //   2. Return `{status: INVALID, latestValidHash: null, validationError: errorMessage |
870        //      null}` if the expected and the actual arrays don't match.
871        //
872        // This validation **MUST** be instantly run in all cases even during active sync process.
873
874        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        // Check for invalid ancestors
881        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        // record pre-execution phase duration
887        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 the block is valid and it is the current sync target head, make it canonical
896        if outcome.outcome.is_valid() && self.is_sync_target_head(block_hash) {
897            // Only create the canonical event if this block isn't already the canonical head
898            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        // record total newPayload duration
906        self.metrics.block_validation.total_duration.record(start.elapsed().as_secs_f64());
907
908        Ok(outcome)
909    }
910
911    /// Processes a payload during normal sync operation.
912    #[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                        // not known to be invalid, but we don't know anything else
939                        (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    /// Stores a payload for later processing during backfill sync.
962    ///
963    /// During backfill, the node lacks the state needed to validate payloads,
964    /// so they are buffered (stored in memory) until their parent blocks are synced.
965    ///
966    /// Returns:
967    /// - `Syncing`: Payload successfully buffered
968    /// - Error status: Payload is malformed or invalid
969    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            // if the block is well-formed, buffer it for later
978            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    /// Returns the new chain for the given head.
990    ///
991    /// This also handles reorgs.
992    ///
993    /// Note: This does not update the tracked state and instead returns the new chain based on the
994    /// given head.
995    fn on_new_head(&self, new_head: B256) -> ProviderResult<Option<NewCanonicalChain<N>>> {
996        // get the executed new head block
997        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        // Walk back the new chain until we reach a block we know about
1011        //
1012        // This is only done for in-memory blocks, because we should not have persisted any blocks
1013        // that are _above_ the current canonical head.
1014        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                // This should never happen as we're walking back a chain that should connect to
1023                // the canonical chain
1024                return Ok(None)
1025            }
1026        }
1027
1028        // If we have reached the current canonical head by walking back from the target, then we
1029        // know this represents an extension of the canonical chain.
1030        if current_hash == self.state.tree_state.current_canonical_head.hash {
1031            new_chain.reverse();
1032
1033            // Simple extension of the current chain
1034            return Ok(Some(NewCanonicalChain::Commit { new: new_chain }))
1035        }
1036
1037        // We have a reorg. Walk back both chains to find the fork point.
1038        let mut old_chain = Vec::new();
1039        let mut old_hash = self.state.tree_state.current_canonical_head.hash;
1040
1041        // If the canonical chain is ahead of the new chain,
1042        // gather all blocks until new head number.
1043        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        // Both new and old chain pointers are now at the same height.
1051        debug_assert_eq!(current_number, current_canonical_number);
1052
1053        // Walk both chains from specified hashes at same height until
1054        // a common ancestor (fork block) is reached.
1055        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                // This shouldn't happen as we've already walked this path
1066                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    /// Updates the latest block state to the specified canonical ancestor.
1077    ///
1078    /// This method ensures that the latest block tracks the given canonical header by resetting
1079    ///
1080    /// # Arguments
1081    /// * `canonical_header` - The canonical header to set as the new head
1082    ///
1083    /// # Returns
1084    /// * `ProviderResult<()>` - Ok(()) on success, error if state update fails
1085    ///
1086    /// Caution: This unwinds the canonical chain
1087    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        // Update tree state with the new canonical head
1097        self.state.tree_state.set_canonical_head(canonical_header.num_hash());
1098
1099        // Handle the state update based on whether this is an unwind scenario
1100        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    /// Handles chain unwind scenarios by collecting blocks to remove and performing an unwind back
1123    /// to the canonical header
1124    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        // Collect blocks that need to be removed from memory
1138        let old_blocks =
1139            self.collect_blocks_for_canonical_unwind(new_head_number, current_head_number);
1140
1141        // Load and apply the canonical ancestor block
1142        self.apply_canonical_ancestor_via_reorg(canonical_header, old_blocks)
1143    }
1144
1145    /// Collects blocks from memory that need to be removed during an unwind to a canonical block.
1146    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    /// Applies the canonical ancestor block via a reorg operation.
1177    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        // Load the canonical ancestor's block
1186        let executed_block = self.canonical_block_by_hash(new_head_hash)?;
1187        // Perform the reorg to properly handle the unwind
1188        self.canonical_in_memory_state
1189            .update_chain(NewCanonicalChain::Reorg { new: vec![executed_block], old: old_blocks });
1190
1191        // CRITICAL: Update the canonical head after the reorg
1192        // This ensures get_canonical_head() returns the correct block
1193        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    /// Handles chain advance or same height scenarios.
1206    fn handle_chain_advance_or_same_height(
1207        &self,
1208        canonical_header: &SealedHeader<N::BlockHeader>,
1209    ) -> ProviderResult<()> {
1210        // Load the block into memory if it's not already present
1211        self.ensure_block_in_memory(canonical_header.number(), canonical_header.hash())?;
1212
1213        // Update the canonical head header
1214        self.canonical_in_memory_state.set_canonical_head(canonical_header.clone());
1215
1216        Ok(())
1217    }
1218
1219    /// Ensures a block is loaded into memory if not already present.
1220    fn ensure_block_in_memory(&self, block_number: u64, block_hash: B256) -> ProviderResult<()> {
1221        // Check if block is already in memory
1222        if self.canonical_in_memory_state.state_by_number(block_number).is_some() {
1223            return Ok(());
1224        }
1225
1226        // Load the block from storage
1227        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    /// Invoked when we receive a new forkchoice update message. Calls into the blockchain tree
1242    /// to resolve chain forks and ensure that the Execution Layer is working with the latest valid
1243    /// chain.
1244    ///
1245    /// These responses should adhere to the [Engine API Spec for
1246    /// `engine_forkchoiceUpdated`](https://github.com/ethereum/execution-apis/blob/main/src/engine/paris.md#specification-1).
1247    ///
1248    /// Returns an error if an internal error occurred like a database error.
1249    #[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        // Record metrics
1258        self.record_forkchoice_metrics();
1259
1260        // Pre-validation of forkchoice state
1261        if let Some(early_result) = self.validate_forkchoice_state(state)? {
1262            return Ok(TreeOutcome::new(early_result));
1263        }
1264
1265        // Return early if we are on the correct fork
1266        if let Some(result) = self.handle_canonical_head(state, &attrs)? {
1267            return Ok(result);
1268        }
1269
1270        // Attempt to apply a chain update when the head differs from our canonical chain.
1271        // This handles reorgs and chain extensions by making the specified head canonical.
1272        if let Some(result) = self.apply_chain_update(state, &attrs)? {
1273            return Ok(result);
1274        }
1275
1276        // Fallback that ensures to catch up to the network's state.
1277        self.handle_missing_block(state)
1278    }
1279
1280    /// Records metrics for forkchoice updated calls
1281    fn record_forkchoice_metrics(&self) {
1282        self.canonical_in_memory_state.on_forkchoice_update_received();
1283    }
1284
1285    /// Pre-validates the forkchoice state and returns early if validation fails.
1286    ///
1287    /// Returns `Some(OnForkChoiceUpdated)` if validation fails and an early response should be
1288    /// returned. Returns `None` if validation passes and processing should continue.
1289    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        // Check if the new head hash is connected to any ancestor that we previously marked as
1298        // invalid
1299        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            // We can only process new forkchoice updates if the pipeline is idle, since it requires
1306            // exclusive access to the database
1307            trace!(target: "engine::tree", "Pipeline is syncing, skipping forkchoice update");
1308            return Ok(Some(OnForkChoiceUpdated::syncing()));
1309        }
1310
1311        Ok(None)
1312    }
1313
1314    /// Handles the case where the forkchoice head is already canonical.
1315    ///
1316    /// Returns `Some(TreeOutcome<OnForkChoiceUpdated>)` if the head is already canonical and
1317    /// processing is complete. Returns `None` if the head is not canonical and processing
1318    /// should continue.
1319    fn handle_canonical_head(
1320        &mut self,
1321        state: ForkchoiceState,
1322        attrs: &Option<T::PayloadAttributes>, // Changed to reference
1323    ) -> ProviderResult<Option<TreeOutcome<OnForkChoiceUpdated>>> {
1324        // Process the forkchoice update by trying to make the head block canonical
1325        //
1326        // We can only process this forkchoice update if:
1327        // - we have the `head` block
1328        // - the head block is part of a chain that is connected to the canonical chain. This
1329        //   includes reorgs.
1330        //
1331        // Performing a FCU involves:
1332        // - marking the FCU's head block as canonical
1333        // - updating in memory state to reflect the new canonical chain
1334        // - updating canonical state trackers
1335        // - emitting a canonicalization event for the new chain (including reorg)
1336        // - if we have payload attributes, delegate them to the payload service
1337
1338        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        // Update the safe and finalized blocks and ensure their values are valid
1345        if let Err(outcome) = self.ensure_consistent_forkchoice_state(state) {
1346            // safe or finalized hashes are invalid
1347            return Ok(Some(TreeOutcome::new(outcome)));
1348        }
1349
1350        self.payload_validator.on_canonical_head_changed(state.head_block_hash, &self.state);
1351
1352        // Process payload attributes if the head is already canonical
1353        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                    // If we can't find the canonical block, then something is wrong and we need
1358                    // to return an error
1359                    ProviderError::HeaderNotFound(state.head_block_hash.into())
1360                })?;
1361            // Clone only when we actually need to process the attributes
1362            let updated = self.process_payload_attributes(attr.clone(), &tip, state);
1363            return Ok(Some(TreeOutcome::new(updated)));
1364        }
1365
1366        // The head block is already canonical
1367        Ok(Some(Self::valid_outcome(state)))
1368    }
1369
1370    /// Applies chain update for the new head block and processes payload attributes.
1371    ///
1372    /// This method handles the case where the forkchoice head differs from our current canonical
1373    /// head. It attempts to make the specified head block canonical by:
1374    /// - Checking if the head is already part of the canonical chain
1375    /// - Applying chain reorganizations (reorgs) if necessary
1376    /// - Processing payload attributes if provided
1377    /// - Returning the appropriate forkchoice update response
1378    ///
1379    /// Returns `Some(TreeOutcome<OnForkChoiceUpdated>)` if a chain update was successfully applied.
1380    /// Returns `None` if no chain update was needed or possible.
1381    fn apply_chain_update(
1382        &mut self,
1383        state: ForkchoiceState,
1384        attrs: &Option<T::PayloadAttributes>,
1385    ) -> ProviderResult<Option<TreeOutcome<OnForkChoiceUpdated>>> {
1386        // Check if the head is already part of the canonical chain
1387        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            // For OpStack, or if explicitly configured, the proposers are allowed to reorg their
1391            // own chain at will, so we need to always trigger a new payload job if requested.
1392            let always_trigger_payload_job = self.engine_kind.is_opstack() ||
1393                self.config.always_process_payload_attributes_on_canonical_head();
1394
1395            // A canonical ancestor below the latest known finalized block can never become the
1396            // head again, because this would reorg out the finalized block. Such a forkchoice
1397            // update exceeds the supported reorg depth and is rejected regardless of the payload
1398            // attributes:
1399            // <https://github.com/ethereum/execution-apis/blob/bf20b4083284e677db19e7f3871bd669b88354a6/src/engine/paris.md?plain=1#L221>
1400            //
1401            // The stored finalized block is used because a forkchoice update MAY carry a zero
1402            // finalized hash without clearing previously established finality.
1403            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            // We need to effectively unwind the _canonical_ chain to the FCU's head, which is
1413            // part of the canonical chain. We need to update the latest block state to reflect
1414            // the canonical ancestor. This ensures that state providers and the transaction
1415            // pool operate with the correct chain state after forkchoice update processing, and
1416            // new payloads built on the reorg'd head will be added to the tree immediately.
1417            if always_trigger_payload_job && self.config.unwind_canonical_header() {
1418                self.update_latest_block_to_canonical_ancestor(&canonical_header)?;
1419            }
1420
1421            // A canonical ancestor at or above the latest known finalized block can become the
1422            // parent of the next block, e.g. when the CL wants to reorg out the current head.
1423            // The canonical chain remains untouched here; the block built on the ancestor
1424            // triggers the actual reorg once it is inserted via newPayload and FCU'd.
1425            if let Some(attr) = attrs {
1426                debug!(target: "engine::tree", head = canonical_header.number(), "handling payload attributes for canonical head");
1427                // Clone only when we actually need to process the attributes
1428                let updated =
1429                    self.process_payload_attributes(attr.clone(), &canonical_header, state);
1430                return Ok(Some(TreeOutcome::new(updated)));
1431            }
1432
1433            // The head block is already canonical and we're not processing payload attributes,
1434            // so we're not triggering a payload job and can return right away
1435            return Ok(Some(Self::valid_outcome(state)));
1436        }
1437
1438        // Ensure we can apply a new chain update for the head block
1439        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            // Update the safe and finalized blocks and ensure their values are valid
1444            if let Err(outcome) = self.ensure_consistent_forkchoice_state(state) {
1445                // safe or finalized hashes are invalid
1446                return Ok(Some(TreeOutcome::new(outcome)));
1447            }
1448
1449            if let Some(attr) = attrs {
1450                // Clone only when we actually need to process the attributes
1451                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    /// Handles the case where the head block is missing and needs to be downloaded.
1462    ///
1463    /// This is the fallback case when all other forkchoice update scenarios have been exhausted.
1464    /// Returns a `TreeOutcome` with syncing status and download event.
1465    fn handle_missing_block(
1466        &self,
1467        state: ForkchoiceState,
1468    ) -> ProviderResult<TreeOutcome<OnForkChoiceUpdated>> {
1469        // We don't have the block to perform the forkchoice update
1470        // We assume the FCU is valid and at least the head is missing,
1471        // so we need to start syncing to it
1472        //
1473        // find the appropriate target to sync to, if we don't have the safe block hash then we
1474        // start syncing to the safe block via backfill first
1475        let target = if self.state.forkchoice_state_tracker.is_empty() &&
1476        // check that safe block is valid and missing
1477        !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    /// Helper method to remove blocks and set the persistence state. This ensures we keep track of
1496    /// the current persistence action while we're removing blocks.
1497    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    /// Helper method to save blocks and set the persistence state. This ensures we keep track of
1509    /// the current persistence action while we're saving blocks.
1510    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    /// Triggers new persistence actions if no persistence task is currently in progress.
1521    ///
1522    /// This checks if we need to remove blocks (disk reorg) or save new blocks to disk.
1523    /// Persistence completion is handled separately via the `wait_for_event` method.
1524    fn advance_persistence(&mut self) -> Result<(), AdvancePersistenceError> {
1525        // A second persistence completion would overwrite the frontier needed by the deferred
1526        // handoff. Wait until the payload jobs using the old overlay have finished.
1527        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    /// Finishes termination by persisting all remaining blocks and signaling completion.
1543    ///
1544    /// This blocks until all persistence is complete. Always signals completion,
1545    /// even if an error occurs.
1546    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    /// Persists all remaining blocks until none are left.
1557    fn persist_until_complete(&mut self) -> Result<(), AdvancePersistenceError> {
1558        loop {
1559            // Wait for any in-progress persistence to complete (blocking)
1560            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            // Persistence can finish against a branch that the in-memory canonical chain has
1568            // reorged away from. Unwind that stale disk branch before building a head-targeted
1569            // save.
1570            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    /// Tries to poll for a completed persistence task (non-blocking).
1586    ///
1587    /// Returns `true` if a persistence task was completed, `false` otherwise.
1588    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                // Not ready yet, put it back
1600                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    /// Handles a completed persistence task.
1610    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    /// Records a completed persistence operation and returns its deferred in-memory handoff.
1631    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    /// Applies the destructive in-memory portion of a completed persistence operation.
1652    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        // Evict cached changesets for blocks below the eviction threshold.
1673        // Keep at least CHANGESET_CACHE_RETENTION_BLOCKS from the persisted tip, and also respect
1674        // the finalized block if set.
1675        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                // Use the minimum of finalized block and retention threshold to be conservative
1679                finalized.number.min(min_threshold)
1680            } else {
1681                // When finalized is not set (e.g., on L2s), use the retention threshold
1682                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    /// Releases work deferred until all payload jobs have finished.
1701    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    /// Handles a message from the engine.
1714    ///
1715    /// Returns `ControlFlow::Break(())` if the engine should terminate.
1716    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                            // outdated block that can be skipped
1743                            return Ok(ops::ControlFlow::Continue(()))
1744                        }
1745
1746                        if self.state.tree_state.contains_hash(&block_num_hash.hash) {
1747                            // block already known to the tree (e.g. delivered via newPayload first)
1748                            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 the parent is the canonical head, we can insert the block as the
1764                        // pending block
1765                        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                                    // track last received forkchoice state
1788                                    self.state
1789                                        .forkchoice_state_tracker
1790                                        .set_latest(state, res.outcome.forkchoice_status());
1791
1792                                    // emit an event about the handled FCU
1793                                    self.emit_event(ConsensusEngineEvent::ForkchoiceUpdated(
1794                                        state,
1795                                        res.outcome.forkchoice_status(),
1796                                    ));
1797
1798                                    // handle the event if any
1799                                    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                                // emit response
1839                                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                                // handle the event if any
1850                                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    /// Invoked if the backfill sync has finished to target.
1955    ///
1956    /// At this point we consider the block synced to the backfill target.
1957    ///
1958    /// Checks the tracked finalized block against the block on disk and requests another backfill
1959    /// run if the distance to the tip exceeds the threshold for another backfill run.
1960    ///
1961    /// This will also do the necessary housekeeping of the tree state, this includes:
1962    ///  - removing all blocks below the backfill height
1963    ///  - resetting the canonical in-memory state
1964    ///
1965    /// In case backfill resulted in an unwind, this will clear the tree state above the unwind
1966    /// target block.
1967    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        // Pipeline unwound, memorize the invalid block and wait for CL for next sync target.
1975        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            // update the `invalid_headers` cache with the new invalid header
1978            self.state.invalid_headers.insert(**bad_block);
1979
1980            // if this was an unwind then the target is the new height
1981            Some(*target)
1982        } else {
1983            // backfill height is the block number that the backfill finished at
1984            ctrl.block_number()
1985        };
1986
1987        // backfill height is the block number that the backfill finished at
1988        let Some(backfill_height) = backfill_height else { return Ok(()) };
1989
1990        // state house keeping after backfill sync
1991        // remove all executed blocks below the backfill height
1992        //
1993        // We set the `finalized_num` to `Some(backfill_height)` to ensure we remove all state
1994        // before that
1995        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            // the node reset so we need to clear everything above that height so that backfill
2006            // height is the new canonical block.
2007            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        // remove all buffered blocks below the backfill height
2021        self.state.buffer.remove_old_blocks(backfill_height);
2022        self.purge_timing_stats(backfill_height, None);
2023        // we remove all entries because now we're synced to the backfill target and consider this
2024        // the canonical chain
2025        self.canonical_in_memory_state.clear_state();
2026
2027        if let Ok(Some(new_head)) = self.provider.sealed_header(backfill_height) {
2028            // update the tracked chain height, after backfill sync both the canonical height and
2029            // persisted height are the same
2030            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            // update the tracked canonical head
2034            self.canonical_in_memory_state.set_canonical_head(new_head);
2035        }
2036
2037        // check if we need to run backfill again by comparing the most recent backfill target
2038        // height to the backfill height
2039        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            // no finalized block, can't check distance on non-OP Stack chains
2045            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        // get the block number of the backfill target block, if we have it buffered
2052        let newest_target = self.state.buffer.block(&target_hash).map(|block| block.number());
2053
2054        // The block number that the backfill finished at - if the progress or newest target is
2055        // None then we can't check the distance anyways.
2056        //
2057        // If both are Some, we perform another distance check and return the desired backfill
2058        // target
2059        if let Some(backfill_target) =
2060            ctrl.block_number().zip(newest_target).and_then(|(progress, target_number)| {
2061                // Determines whether or not we should run backfill again, in case
2062                // the new gap is still large enough and requires running backfill again
2063                self.backfill_sync_target(progress, target_number, None)
2064            })
2065        {
2066            // request another backfill run
2067            self.emit_event(EngineApiEvent::BackfillAction(BackfillAction::Start(
2068                backfill_target.into(),
2069            )));
2070            return Ok(())
2071        };
2072
2073        // Check if there are more blocks to sync between current head and FCU target
2074        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                // There are blocks between current head and FCU target, download them
2083                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            // We don't have the head block or any of its ancestors buffered. Request
2099            // a download for the head block which will then trigger further sync.
2100            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        // try to close the gap by executing buffered blocks that are child blocks of the new head
2112        self.try_connect_buffered_blocks(self.state.tree_state.current_canonical_head)
2113    }
2114
2115    /// Attempts to make the given target canonical.
2116    ///
2117    /// This will update the tracked canonical in memory state and do the necessary housekeeping.
2118    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    /// Applies the tracked forkchoice state once its sync target head becomes canonical.
2129    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    /// Convenience function to handle an optional tree event.
2155    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    /// Handles a tree event.
2164    ///
2165    /// Returns an error if a [`TreeAction::MakeCanonical`] results in a fatal error.
2166    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    /// Removes timing stats for blocks at or below `below_number`.
2185    ///
2186    /// No-op when detailed block logging is disabled (no stats are recorded in that case).
2187    /// When `commit_duration` is provided and a slow block threshold is configured, checks
2188    /// each removed block against the threshold and emits a [`ConsensusEngineEvent::SlowBlock`]
2189    /// event for blocks that exceed it.
2190    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        // Two-pass: collect keys first because emit_event borrows &mut self.
2195        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                // state_read_duration is already included in execution_duration
2207                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    /// Emits an outgoing event to the engine.
2222    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                // Backfill can remove the same in-memory blocks as an active payload job or a
2237                // persisted handoff, so it must not start until the next sync trigger.
2238                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    /// Returns the blocks and frontiers for the next persistence cycle, if one should start.
2253    ///
2254    /// Threshold persistence honors the normal scheduling gates and retains the configured
2255    /// in-memory block buffer. Head persistence bypasses those gates during shutdown and returns
2256    /// `None` once both persistence frontiers have reached the canonical head.
2257    fn get_save_blocks_input(&self, target: PersistTarget) -> Option<SaveBlocksInput<N>> {
2258        // We will calculate the state root using the database, so we need to be sure there are no
2259        // changes
2260        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(&current_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        // Reverse the order so that the oldest block comes first
2335        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    /// This clears the blocks from the in-memory tree state that have been persisted to the
2347    /// database.
2348    ///
2349    /// This also updates the canonical in-memory state to reflect the newest persisted block
2350    /// height.
2351    ///
2352    /// Assumes that `finish` has been called on the `persistence_state` at least once
2353    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 we have an on-disk reorg, we need to handle it first before touching the in-memory
2357        // state.
2358        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    /// Returns whether sparse trie pruning should be attempted by the next sparse trie task.
2374    const fn should_prune_sparse_trie(&self) -> bool {
2375        self.config.use_state_root_task()
2376    }
2377
2378    /// Return an [`ExecutedBlock`] from database or in-memory state by hash.
2379    ///
2380    /// Note: This function attempts to fetch the `ExecutedBlock` from either in-memory state
2381    /// or the database. If the required historical data (such as trie change sets) has been
2382    /// pruned for a given block, this operation will return an error. On archive nodes, it
2383    /// can retrieve any block.
2384    #[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        // check memory first
2388        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        // `get_state` can return an in-memory execution outcome that retains destruction statuses.
2403        // Hashing it requires the parent provider to expand a pre-existing destroyed account's
2404        // storage into zero-valued slots.
2405        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    /// Returns `true` if a block with the given hash is known, either in memory or in the
2444    /// database. This is a lightweight existence check that avoids constructing a full
2445    /// [`SealedHeader`].
2446    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    /// Return sealed block header from in-memory state or database by hash.
2455    fn sealed_header_by_hash(
2456        &self,
2457        hash: B256,
2458    ) -> ProviderResult<Option<SealedHeader<N::BlockHeader>>> {
2459        // check memory first
2460        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    /// Return the parent hash of the lowest buffered ancestor for the requested block, if there
2470    /// are any buffered ancestors. If there are no buffered ancestors, and the block itself does
2471    /// not exist in the buffer, this returns the hash that is passed in.
2472    ///
2473    /// Returns the parent hash of the block itself if the block is buffered and has no other
2474    /// buffered ancestors.
2475    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    /// If validation fails, the response MUST contain the latest valid hash:
2484    ///
2485    ///   - The block hash of the ancestor of the invalid payload satisfying the following two
2486    ///     conditions:
2487    ///     - It is fully validated and deemed VALID
2488    ///     - Any other ancestor of the invalid payload with a higher blockNumber is INVALID
2489    ///   - 0x0000000000000000000000000000000000000000000000000000000000000000 if the above
2490    ///     conditions are satisfied by a `PoW` block.
2491    ///   - null if client software cannot determine the ancestor of the invalid payload satisfying
2492    ///     the above conditions.
2493    fn latest_valid_hash_for_invalid_payload(
2494        &mut self,
2495        parent_hash: B256,
2496    ) -> ProviderResult<Option<B256>> {
2497        // Check if parent exists in side chain or in canonical chain.
2498        if self.has_block_by_hash(parent_hash)? {
2499            return Ok(Some(parent_hash))
2500        }
2501
2502        // iterate over ancestors in the invalid cache
2503        // until we encounter the first valid ancestor
2504        let mut current_hash = parent_hash;
2505        let mut current_block = self.state.invalid_headers.get(&current_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(&current_hash);
2509
2510            // If current_header is None, then the current_hash does not have an invalid
2511            // ancestor in the cache, check its presence in blockchain tree
2512            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    /// Prepares the invalid payload response for the given hash, checking the
2520    /// database for the parent hash and populating the payload status with the latest valid hash
2521    /// according to the engine api spec.
2522    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            // Edge case: the `latestValid` field is the zero hash if the parent block is the
2525            // terminal PoW block, which we need to identify by looking at the parent's block
2526            // difficulty
2527            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    /// Returns true if the given hash is the last received sync target block.
2539    ///
2540    /// See [`ForkchoiceStateTracker::sync_target_state`]
2541    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    /// Returns true if the given hash is part of the last received sync target fork choice update.
2549    ///
2550    /// See [`ForkchoiceStateTracker::sync_target_state`]
2551    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    /// Checks if the given `check` hash points to an invalid header, inserting the given `head`
2559    /// block into the invalid header cache if the `check` hash has a known invalid ancestor.
2560    ///
2561    /// Returns a payload status response according to the engine API spec if the block is known to
2562    /// be invalid.
2563    fn check_invalid_ancestor_with_head(
2564        &mut self,
2565        check: B256,
2566        head: &SealedBlock<N::Block>,
2567    ) -> ProviderResult<Option<PayloadStatus>> {
2568        // check if the check hash was previously marked as invalid
2569        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    /// Invoked when a new payload received is invalid.
2575    fn on_invalid_new_payload(
2576        &mut self,
2577        head: SealedBlock<N::Block>,
2578        invalid: BlockWithParent,
2579    ) -> ProviderResult<PayloadStatus> {
2580        // populate the latest valid hash field
2581        let status = self.prepare_invalid_response(invalid.parent)?;
2582
2583        // insert the head block into the invalid header cache
2584        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    /// Finds any invalid ancestor for the given payload.
2594    ///
2595    /// This function first checks if the block itself is in the invalid headers cache (to
2596    /// avoid re-executing a known-invalid block). Then it walks up the chain of buffered
2597    /// ancestors and checks if any ancestor is marked as invalid.
2598    ///
2599    /// The check works by:
2600    /// 1. Checking if the block hash itself is in the `invalid_headers` map
2601    /// 2. Finding the lowest buffered ancestor for the given block hash
2602    /// 3. If the ancestor is the same as the block hash itself, using the parent hash instead
2603    /// 4. Checking if this ancestor is in the `invalid_headers` map
2604    ///
2605    /// Returns the invalid ancestor block info if found, or None if no invalid ancestor exists.
2606    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        // Check if the block itself is already known to be invalid, avoiding re-execution
2611        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        // Check if the block has an invalid ancestor
2621        self.state.invalid_headers.get(&lowest_buffered_ancestor)
2622    }
2623
2624    /// Handles a payload that has an invalid ancestor.
2625    ///
2626    /// This function validates the payload and processes it according to whether it's
2627    /// well-formed or malformed:
2628    /// 1. **Well-formed payload**: The payload is marked as invalid since it descends from a
2629    ///    known-bad block, which violates consensus rules
2630    /// 2. **Malformed payload**: Returns an appropriate error status since the payload cannot be
2631    ///    validated due to its own structural issues
2632    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        // Here we might have 2 cases
2641        // 1. the block is well formed and indeed links to an invalid header, meaning we should
2642        //    remember it as invalid
2643        // 2. the block is not well formed (i.e block hash is incorrect), and we should just return
2644        //    an error and forget it
2645        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    /// Checks if the given `head` points to an invalid header, which requires a specific response
2654    /// to a forkchoice update.
2655    fn check_invalid_ancestor(&mut self, head: B256) -> ProviderResult<Option<PayloadStatus>> {
2656        // check if the head was previously marked as invalid
2657        let Some(header) = self.state.invalid_headers.get(&head) else { return Ok(None) };
2658
2659        // Try to prepare invalid response, but handle errors gracefully
2660        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                // Return a basic invalid status without latest valid hash
2665                Ok(Some(PayloadStatus::from_status(PayloadStatusEnum::Invalid {
2666                    validation_error: PayloadValidationError::LinksToRejectedPayload.to_string(),
2667                })))
2668            }
2669        }
2670    }
2671
2672    /// Validate if block is correct and satisfies all the consensus rules that concern the header
2673    /// and block body itself.
2674    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    /// Attempts to connect any buffered blocks that are connected to the given parent hash.
2689    #[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            // nothing to append
2698            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                        // we just inserted a block that we know is part of the canonical chain, so
2713                        // we can make it canonical
2714                        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    /// Pre-validates the block and inserts it into the buffer.
2734    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    /// Returns true if the distance from the local tip to the block is greater than the configured
2746    /// threshold.
2747    ///
2748    /// If the `local_tip` is greater than the `block`, then this will return false.
2749    #[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    /// Returns how far the local tip is from the given block. If the local tip is at the same
2755    /// height or its block number is greater than the given block, this returns None.
2756    #[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    /// Returns the block hash that backfill should target.
2766    ///
2767    /// Defaults to the finalized block hash. On OP Stack, the CL finalizes in large batches and the
2768    /// finalized hash can lag the canonical tip by a wide margin, so backfill targets the head.
2769    ///
2770    /// The zero-finalized optimistic-sync fallback for non-OP Stack chains is handled by
2771    /// [`Self::backfill_sync_target`].
2772    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    /// Returns the target hash to sync to if the distance from the local tip is greater than the
2781    /// threshold and we're not yet synced to the backfill target (see
2782    /// [`Self::backfill_target_hash`]).
2783    ///
2784    /// If this is invoked after a new block has been downloaded, the downloaded block could be
2785    /// the (missing) target block.
2786    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        // check if the downloaded block is the tracked backfill target
2796        let exceeds_backfill_threshold = match downloaded_block.as_ref() {
2797            // if we downloaded the target block we can now check how far we're off
2798            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                // if we have buffered the target block, we should check how far we're off
2803                Some(buffered_target) => {
2804                    self.exceeds_backfill_run_threshold(canonical_tip_num, buffered_target.number())
2805                }
2806                // check if the distance exceeds the threshold for backfill sync
2807                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        // if we have already canonicalized the target block, we should skip backfill
2816        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            // we don't have the block yet and the distance exceeds the allowed threshold
2822            Ok(None) if !target_hash.is_zero() => Some(target_hash),
2823            Ok(None) => {
2824                // OPTIMISTIC SYNCING
2825                //
2826                // It can happen when the node is doing an
2827                // optimistic sync, where the CL has no knowledge of the finalized hash,
2828                // but is expecting the EL to sync as high
2829                // as possible before finalizing.
2830                //
2831                // This usually doesn't happen on ETH mainnet since CLs use the more
2832                // secure checkpoint syncing.
2833                //
2834                // However, optimism chains will do this. The risk of a reorg is however
2835                // low.
2836                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            // we're fully synced to the target block
2840            Ok(Some(_)) => None,
2841        }
2842    }
2843
2844    /// This method tries to detect whether on-disk and in-memory states have diverged. It might
2845    /// happen if a reorg is happening while we are persisting a block.
2846    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        // Happy path, canonical chain is ahead or equal to persisted chain.
2858        // Walk canonical chain back to make sure that it connects to persisted chain.
2859        while canonical.number > persisted.number {
2860            canonical = parent_num_hash(canonical)?;
2861        }
2862
2863        // If we've reached persisted tip by walking the canonical chain back, everything is fine.
2864        if canonical == persisted {
2865            return Ok(None);
2866        }
2867
2868        // At this point, we know that `persisted` block can't be reached by walking the canonical
2869        // chain back. In this case we need to truncate it to the first canonical block it connects
2870        // to.
2871
2872        // Firstly, walk back until we reach the same height as `canonical`.
2873        while persisted.number > canonical.number {
2874            persisted = parent_num_hash(persisted)?;
2875        }
2876
2877        debug_assert_eq!(persisted.number, canonical.number);
2878
2879        // Now walk both chains back until we find a common ancestor.
2880        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    /// Invoked when we the canonical chain has been updated.
2891    ///
2892    /// This is invoked on a valid forkchoice update, or if we can make the target block canonical.
2893    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        // update the tracked canonical head
2898        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        // reinsert any missing reorged blocks
2904        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        // update the tracked in-memory state with the new chain
2916        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        // Update metrics based on new tip
2921        self.metrics.tree.canonical_chain_height.set(tip.number() as f64);
2922
2923        // sends an event to all active listeners about the new canonical chain
2924        self.canonical_in_memory_state.notify_canon_state(notification);
2925
2926        // emit event
2927        self.emit_event(ConsensusEngineEvent::CanonicalChainCommitted(
2928            Box::new(tip),
2929            start.elapsed(),
2930        ));
2931    }
2932
2933    /// This updates metrics based on the given reorg length and first reorged block number.
2934    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    /// This reinserts any blocks in the new chain that do not already exist in the tree
2954    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    /// This handles downloaded blocks that are shown to be disconnected from the canonical chain.
2969    ///
2970    /// This mainly compares the missing parent of the downloaded block with the current canonical
2971    /// tip, and decides whether or not backfill sync should be triggered.
2972    fn on_disconnected_downloaded_block(
2973        &self,
2974        downloaded_block: BlockNumHash,
2975        missing_parent: BlockNumHash,
2976        head: BlockNumHash,
2977    ) -> Option<TreeEvent> {
2978        // compare the missing parent with the canonical tip
2979        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        // continue downloading the missing parent
2987        //
2988        // this happens if either:
2989        //  * the missing parent block num < canonical tip num
2990        //    * this case represents a missing block on a fork that is shorter than the canonical
2991        //      chain
2992        //  * the missing parent block num >= canonical tip num, but the number of missing blocks is
2993        //    less than the backfill threshold
2994        //    * this case represents a potentially long range of blocks to download and execute
2995        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            // This happens when the missing parent is on an outdated
3003            // sidechain and we can only download the missing block itself
3004            DownloadRequest::single_block(missing_parent.hash)
3005        };
3006
3007        Some(TreeEvent::Download(request))
3008    }
3009
3010    /// Handles a downloaded block that was successfully inserted as valid.
3011    ///
3012    /// If the block matches the sync target head, returns [`TreeAction::MakeCanonical`].
3013    /// If it matches a non-head sync target (safe or finalized), makes it canonical inline
3014    /// and triggers a download for the remaining blocks towards the actual head.
3015    /// Otherwise, tries to connect buffered blocks.
3016    fn on_valid_downloaded_block(
3017        &mut self,
3018        block_num_hash: BlockNumHash,
3019    ) -> Result<Option<TreeEvent>, InsertBlockFatalError> {
3020        // check if we just inserted a block that's part of sync targets,
3021        // i.e. head, safe, or finalized
3022        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                // we just inserted the sync target head block, make it canonical
3029                return Ok(Some(TreeEvent::TreeAction(TreeAction::MakeCanonical {
3030                    sync_target_head: block_num_hash.hash,
3031                })))
3032            }
3033
3034            // This block is part of the sync target (safe or finalized) but not the
3035            // head. Make it canonical and try to connect any buffered children, then
3036            // continue downloading towards the actual head if needed.
3037            self.make_canonical(block_num_hash.hash)?;
3038            self.try_connect_buffered_blocks(block_num_hash)?;
3039
3040            // Check if we've reached the sync target head after connecting buffered
3041            // blocks (e.g. the head block may have already been buffered).
3042            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    /// Invoked with a block downloaded from the network
3056    ///
3057    /// Returns an event with the appropriate action to take, such as:
3058    ///  - download more missing blocks
3059    ///  - try to canonicalize the target if the `block` is the tracked target (head) block.
3060    #[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        // try to append the block
3076        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                // block is not connected to the canonical head, we need to download
3082                // its missing branch first
3083                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    /// Inserts a payload into the tree and executes it.
3106    ///
3107    /// This function validates the payload's basic structure, then executes it using the
3108    /// payload validator. The execution includes running all transactions in the payload
3109    /// and validating the resulting state transitions.
3110    ///
3111    /// Returns `InsertPayloadOk` if the payload was successfully inserted and executed,
3112    /// or `InsertPayloadError` if validation or execution failed.
3113    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    /// Inserts a block or payload into the blockchain tree with full execution.
3138    ///
3139    /// This is a generic function that handles both blocks and payloads by accepting
3140    /// a block identifier, input data, and execution/validation functions. It performs
3141    /// comprehensive checks and execution:
3142    ///
3143    /// - Validates that the block doesn't already exist in the tree
3144    /// - Ensures parent state is available, buffering if necessary
3145    /// - Executes the block/payload using the provided execute function
3146    /// - Handles both canonical and fork chain insertions
3147    /// - Updates pending block state when appropriate
3148    /// - Emits consensus engine events and records metrics
3149    ///
3150    /// Returns `InsertPayloadOk::Inserted(BlockStatus::Valid)` on successful execution,
3151    /// `InsertPayloadOk::AlreadySeen` if the block already exists, or
3152    /// `InsertPayloadOk::Inserted(BlockStatus::Disconnected)` if parent state is missing.
3153    #[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        // Check if block already exists - first in memory, then DB only if it could be persisted
3169        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        // Only query DB if block could be persisted (number <= last persisted block).
3175        // New blocks from CL always have number > last persisted, so skip DB lookup for them.
3176        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        // Ensure that the parent state is available.
3191        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                // we don't have the state required to execute this block, buffering it and find the
3200                // missing parent block
3201                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        // determine whether we are on a fork chain by comparing the block number with the
3219        // canonical head. This is a simple check that is sufficient for the event emission below.
3220        // A block is considered a fork if its number is less than or equal to the canonical head,
3221        // as this indicates there's already a canonical block at that height.
3222        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        // Emit slow block event immediately after execution so it appears even when
3247        // persistence hasn't completed yet (e.g. blocks arriving faster than persistence).
3248        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 the parent is the canonical head, we can insert the block as the pending block
3263        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        // emit insert event
3273        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    /// Handles an error that occurred while inserting a block.
3290    ///
3291    /// If this is a validation error this will mark the block as invalid.
3292    ///
3293    /// Returns the proper payload status response if the block is invalid.
3294    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        // if invalid block, we check the validation error. Otherwise return the fatal
3301        // error.
3302        let validation_err = error.ensure_validation_error()?;
3303
3304        // If the error was due to an invalid payload, the payload is added to the
3305        // invalid headers cache and `Ok` with [PayloadStatusEnum::Invalid] is
3306        // returned.
3307        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        // keep track of the invalid header unless the consensus impl considers it transient
3317        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    /// Handles a [`NewPayloadError`] by converting it to a [`PayloadStatus`].
3344    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        // we need to convert the error to a payload status (response to the CL)
3352
3353        let latest_valid_hash =
3354            if error.is_block_hash_mismatch() || error.is_invalid_versioned_hashes() {
3355                // Engine-API rules:
3356                // > `latestValidHash: null` if the blockHash validation has failed (<https://github.com/ethereum/execution-apis/blob/fe8e13c288c592ec154ce25c534e26cb7ce0530d/src/engine/shanghai.md?plain=1#L113>)
3357                // > `latestValidHash: null` if the expected and the actual arrays don't match (<https://github.com/ethereum/execution-apis/blob/fe8e13c288c592ec154ce25c534e26cb7ce0530d/src/engine/cancun.md?plain=1#L103>)
3358                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    /// Attempts to find the header for the given block hash if it is canonical.
3368    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    /// Updates the tracked finalized block if we have it.
3382    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                // if the finalized block is not known, we can't update the finalized block
3394                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                    // we're also persisting the finalized block on disk so we can reload it on
3401                    // restart this is required by optimism which queries the finalized block: <https://github.com/ethereum-optimism/optimism/blob/c383eb880f307caa3ca41010ec10f30f08396b2e/op-node/rollup/sync/start.go#L65-L65>
3402                    let _ = self.persistence.save_finalized_block_number(finalized.number());
3403                    self.canonical_in_memory_state.set_finalized(finalized.clone());
3404                    // Update finalized block height metric
3405                    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    /// Updates the tracked safe block if we have it
3417    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                // if the safe block is not known, we can't update the safe block
3426                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                    // we're also persisting the safe block on disk so we can reload it on
3431                    // restart this is required by optimism which queries the safe block: <https://github.com/ethereum-optimism/optimism/blob/c383eb880f307caa3ca41010ec10f30f08396b2e/op-node/rollup/sync/start.go#L65-L65>
3432                    let _ = self.persistence.save_safe_block_number(safe.number());
3433                    self.canonical_in_memory_state.set_safe(safe.clone());
3434                    // Update safe block height metric
3435                    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    /// Ensures that the given forkchoice state is consistent, assuming the head block has been
3447    /// made canonical.
3448    ///
3449    /// If the forkchoice state is consistent, this will return Ok(()). Otherwise, this will
3450    /// return an instance of [`OnForkChoiceUpdated`] that is INVALID.
3451    ///
3452    /// This also updates the safe and finalized blocks in the [`CanonicalInMemoryState`], if they
3453    /// are consistent with the head block.
3454    fn ensure_consistent_forkchoice_state(
3455        &self,
3456        state: ForkchoiceState,
3457    ) -> Result<(), OnForkChoiceUpdated> {
3458        // Ensure that the finalized block, if not zero, is known and in the canonical chain
3459        // after the head block is canonicalized.
3460        //
3461        // This ensures that the finalized block is consistent with the head block, i.e. the
3462        // finalized block is an ancestor of the head block.
3463        self.update_finalized_block(state.finalized_block_hash)?;
3464
3465        // Also ensure that the safe block, if not zero, is known and in the canonical chain
3466        // after the head block is canonicalized.
3467        //
3468        // This ensures that the safe block is consistent with the head block, i.e. the safe
3469        // block is an ancestor of the head block.
3470        self.update_safe_block(state.safe_block_hash)
3471    }
3472
3473    /// Validates the payload attributes with respect to the header and fork choice state.
3474    ///
3475    /// This is called during `engine_forkchoiceUpdated` when the CL provides payload attributes,
3476    /// indicating it wants the EL to start building a new block.
3477    ///
3478    /// Runs [`PayloadValidator::validate_payload_attributes_against_header`](reth_engine_primitives::PayloadValidator::validate_payload_attributes_against_header) to ensure
3479    /// `payloadAttributes.timestamp > headBlock.timestamp` per the Engine API spec.
3480    ///
3481    /// If validation passes, sends the attributes to the payload builder to start a new
3482    /// payload job. If it fails, returns `INVALID_PAYLOAD_ATTRIBUTES` without rolling back
3483    /// the forkchoice update.
3484    ///
3485    /// Note: At this point, the fork choice update is considered to be VALID, however, we can still
3486    /// return an error if the payload attributes are invalid.
3487    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        // 8. Client software MUST begin a payload build process building on top of
3501        //    forkchoiceState.headBlockHash and identified via buildProcessId value if
3502        //    payloadAttributes is not null and the forkchoice state has been updated successfully.
3503        //    The build process is specified in the Payload building section.
3504
3505        // Acquire this before preparing resources because state-root setup can already start
3506        // workers that need the current in-memory overlay.
3507        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        // send the payload to the builder and return the receiver for the pending payload
3520        // id, initiating payload job is handled asynchronously
3521        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        // Client software MUST respond to this method call in the following way:
3528        // {
3529        //      payloadStatus: {
3530        //          status: VALID,
3531        //          latestValidHash: forkchoiceState.headBlockHash,
3532        //          validationError: null
3533        //      },
3534        //      payloadId: buildProcessId
3535        // }
3536        //
3537        // if the payload is deemed VALID and the build process has begun.
3538        OnForkChoiceUpdated::updated_with_pending_payload_id(
3539            PayloadStatus::new(PayloadStatusEnum::Valid, Some(state.head_block_hash)),
3540            pending_payload_id,
3541        )
3542    }
3543
3544    /// Remove all blocks up to __and including__ the given block number.
3545    ///
3546    /// If a finalized hash is provided, the only non-canonical blocks which will be removed are
3547    /// those which have a fork point at or below the finalized hash.
3548    ///
3549    /// Canonical blocks below the upper bound will still be removed.
3550    pub(crate) fn remove_before(
3551        &mut self,
3552        upper_bound: BlockNumHash,
3553        finalized_hash: Option<B256>,
3554    ) -> ProviderResult<()> {
3555        // first fetch the finalized block number and then call the remove_before method on
3556        // tree_state
3557        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    /// Returns a builder for creating state providers for the given hash.
3572    ///
3573    /// This is an optimization for parallel execution contexts where we want to avoid
3574    /// creating state providers in the critical path.
3575    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/// Events received in the main engine loop.
3596#[derive(Debug)]
3597enum LoopEvent<T, N>
3598where
3599    N: NodePrimitives,
3600    T: PayloadTypes,
3601{
3602    /// An engine API message was received.
3603    EngineMessage(FromEngine<EngineApiRequest<T, N>, N::Block>),
3604    /// A persistence task completed.
3605    PersistenceComplete {
3606        /// The unified result of the persistence operation.
3607        result: PersistenceResult,
3608        /// When the persistence operation started.
3609        start_time: Instant,
3610    },
3611    /// The last active payload job has finished, so deferred in-memory work may proceed.
3612    PayloadBuildFinished,
3613    /// A channel was disconnected.
3614    Disconnected,
3615}
3616
3617/// Tracks payload jobs that may access the current in-memory overlay.
3618#[derive(Clone, Debug)]
3619struct PayloadBuildTracker {
3620    active: Arc<AtomicUsize>,
3621    finished_tx: Sender<()>,
3622}
3623
3624impl PayloadBuildTracker {
3625    /// Creates a tracker and a receiver notified when its active count reaches zero.
3626    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    /// Acquires a lease for one payload job.
3632    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    /// Returns whether at least one payload job is active.
3641    fn is_active(&self) -> bool {
3642        self.active.load(Ordering::Acquire) != 0
3643    }
3644}
3645
3646/// A lease held for the lifetime of a payload job.
3647#[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            // The bounded channel coalesces completion notifications. The engine always checks
3660            // the counter again before applying a pending handoff.
3661            let _ = self.finished_tx.try_send(());
3662        }
3663    }
3664}
3665
3666/// Block inclusion can be valid, accepted, or invalid. Invalid blocks are returned as an error
3667/// variant.
3668///
3669/// If we don't know the block's parent, we return `Disconnected`, as we can't claim that the block
3670/// is valid or not.
3671#[derive(Clone, Copy, Debug, Eq, PartialEq)]
3672pub enum BlockStatus {
3673    /// The block is valid: its parent state was available, so it was executed and inserted into
3674    /// the tree.
3675    ///
3676    /// Note: this does not imply the block extends the canonical chain. Blocks on a fork are
3677    /// executed and inserted the same way and report this status as well.
3678    Valid,
3679    /// The block may be valid and has an unknown missing ancestor.
3680    Disconnected {
3681        /// Current canonical head.
3682        head: BlockNumHash,
3683        /// The lowest ancestor block that is not connected to the canonical chain.
3684        missing_ancestor: BlockNumHash,
3685    },
3686}
3687
3688/// How a payload was inserted if it was valid.
3689///
3690/// If the payload was valid, but has already been seen, [`InsertPayloadOk::AlreadySeen`] is
3691/// returned, otherwise [`InsertPayloadOk::Inserted`] is returned.
3692#[derive(Clone, Copy, Debug, Eq, PartialEq)]
3693pub enum InsertPayloadOk {
3694    /// The payload was valid, but we have already seen it.
3695    AlreadySeen(BlockStatus),
3696    /// The payload was valid and inserted into the tree.
3697    Inserted(BlockStatus),
3698}
3699
3700/// Target for block persistence.
3701#[derive(Debug, Clone, Copy)]
3702enum PersistTarget {
3703    /// Persist up to `canonical_head - memory_block_buffer_target`.
3704    Threshold,
3705    /// Persist all blocks up to and including the canonical head.
3706    Head,
3707}
3708
3709/// Result of waiting for caches to become available.
3710#[derive(Debug, Clone, Copy, Default)]
3711pub struct CacheWaitDurations {
3712    /// Time spent waiting for the execution cache lock.
3713    pub execution_cache: Duration,
3714    /// Time spent waiting for the sparse trie lock.
3715    pub sparse_trie: Duration,
3716}
3717
3718/// Trait for types that can wait for caches to become available.
3719///
3720/// This is used by `reth_newPayload` endpoint to ensure that payload processing
3721/// waits for any ongoing operations to complete before starting.
3722pub trait WaitForCaches {
3723    /// Waits for cache updates to complete.
3724    ///
3725    /// Returns the time spent waiting for each cache separately.
3726    fn wait_for_caches(&self) -> CacheWaitDurations;
3727}