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::{
15    InsertBlockError, InsertBlockFatalError, InsertBlockProcessingError, InsertBlockValidationError,
16};
17use reth_chain_state::{
18    CanonicalInMemoryState, ExecutedBlock, ExecutionTimingStats, NewCanonicalChain,
19};
20use reth_consensus::{Consensus, FullConsensus};
21use reth_engine_primitives::{
22    BeaconEngineMessage, ConsensusEngineEvent, ExecutionPayload, ForkchoiceStateTracker,
23    NewPayloadTimings, OnForkChoiceUpdated, SlowBlockInfo,
24};
25use reth_errors::{ConsensusError, ProviderResult};
26use reth_evm::ConfigureEvm;
27use reth_network_p2p::full_block::SealedBlockWithAccessList;
28use reth_payload_builder::{BuildNewPayload, PayloadBuilderHandle, PayloadBuilderLease};
29use reth_payload_primitives::{BuiltPayload, NewPayloadError, PayloadAttributes, PayloadTypes};
30use reth_primitives_traits::{
31    FastInstant as Instant, NodePrimitives, RecoveredBlock, SealedBlock, SealedHeader,
32};
33use reth_provider::{
34    BalProvider, BlockExecutionOutput, BlockExecutionResult, BlockReader, ChangeSetReader,
35    DatabaseProviderFactory, ProviderError, PruneCheckpointReader, SaveBlocksInput,
36    StageCheckpointReader, StateProviderFactory, StateReader, StorageChangeSetReader,
37    StorageSettingsCache, TransactionVariant,
38};
39use reth_revm::database::StateProviderDatabase;
40use reth_stages_api::ControlFlow;
41use reth_storage_overlay::OverlayManager;
42use reth_tasks::{spawn_os_thread, utils::increase_thread_priority};
43use reth_trie::ComputedTrieData;
44use revm::interpreter::debug_unreachable;
45use state::TreeState;
46use std::{
47    fmt::Debug,
48    ops,
49    sync::{
50        atomic::{AtomicUsize, Ordering},
51        Arc,
52    },
53    time::Duration,
54};
55
56use crossbeam_channel::{Receiver, Sender};
57use tokio::sync::{
58    mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
59    oneshot,
60};
61use tracing::*;
62
63mod block_buffer;
64pub mod error;
65pub mod instrumented_state;
66mod invalid_headers;
67mod metrics;
68pub mod payload_processor;
69pub mod payload_validator;
70mod persistence_state;
71pub mod precompile_cache;
72pub mod state_root_strategy;
73#[cfg(test)]
74mod tests;
75mod trie_updates;
76mod txpool_prewarm;
77pub mod types;
78
79use crate::{persistence::PersistenceResult, tree::error::AdvancePersistenceError};
80pub use block_buffer::BlockBuffer;
81pub use invalid_headers::InvalidHeaderCache;
82pub use metrics::EngineApiMetrics;
83pub use payload_processor::*;
84pub use payload_validator::{BasicEngineValidator, EngineValidator};
85pub use persistence_state::PersistenceState;
86pub use reth_engine_primitives::TreeConfig;
87pub use reth_execution_cache::{
88    CachedStateCacheMetrics, CachedStateMetrics, CachedStateMetricsSource, CachedStateProvider,
89    ExecutionCache, PayloadExecutionCache, SavedCache, TxPoolPrewarmCacheSnapshot,
90};
91pub use txpool_prewarm::{
92    Source as TxPoolPrewarmSource, Transaction as TxPoolPrewarmTransaction,
93    Transactions as TxPoolPrewarmTransactions,
94};
95pub use types::{ExecutionEnv, ValidationOutcome, ValidationOutput};
96
97pub mod state;
98
99/// The largest gap for which the tree will be used to sync individual blocks by downloading them.
100///
101/// This is the default threshold, and represents the distance (gap) from the local head to a
102/// new (canonical) block, e.g. the forkchoice head block. If the block distance from the local head
103/// exceeds this threshold, the pipeline will be used to backfill the gap more efficiently.
104///
105/// E.g.: Local head `block.number` is 100 and the forkchoice head `block.number` is 133 (more than
106/// an epoch has slots), then this exceeds the threshold at which the pipeline should be used to
107/// backfill this gap.
108pub(crate) const MIN_BLOCKS_FOR_PIPELINE_RUN: u64 = EPOCH_SLOTS;
109
110/// The minimum number of blocks to retain in the changeset cache after eviction.
111///
112/// This ensures that recent changesets are kept in memory for potential reorgs,
113/// even when the finalized block is not set (e.g., on L2s like Optimism).
114const CHANGESET_CACHE_RETENTION_BLOCKS: u64 = 64;
115
116/// Tracks the state of the engine api internals.
117///
118/// This type is not shareable.
119#[derive(Debug)]
120pub struct EngineApiTreeState<N: NodePrimitives> {
121    /// Tracks the state of the blockchain tree.
122    tree_state: TreeState<N>,
123    /// Whether the next sparse trie task should attempt cache pruning during trie preservation.
124    pending_sparse_trie_prune: bool,
125    /// Tracks the forkchoice state updates received by the CL.
126    forkchoice_state_tracker: ForkchoiceStateTracker,
127    /// Buffer of detached blocks.
128    buffer: BlockBuffer<N::Block>,
129    /// Tracks the header of invalid payloads that were rejected by the engine because they're
130    /// invalid.
131    invalid_headers: InvalidHeaderCache,
132}
133
134impl<N: NodePrimitives> EngineApiTreeState<N> {
135    fn new(
136        block_buffer_limit: u32,
137        max_invalid_header_cache_length: u32,
138        invalid_header_hit_eviction_threshold: u8,
139        canonical_block: BlockNumHash,
140        engine_kind: EngineApiKind,
141        overlay_manager: OverlayManager<N>,
142    ) -> Self {
143        Self {
144            invalid_headers: InvalidHeaderCache::new(
145                max_invalid_header_cache_length,
146                invalid_header_hit_eviction_threshold,
147            ),
148            buffer: BlockBuffer::new(block_buffer_limit),
149            tree_state: TreeState::new(canonical_block, engine_kind, overlay_manager),
150            pending_sparse_trie_prune: false,
151            forkchoice_state_tracker: ForkchoiceStateTracker::default(),
152        }
153    }
154
155    /// Returns a reference to the tree state.
156    pub const fn tree_state(&self) -> &TreeState<N> {
157        &self.tree_state
158    }
159
160    /// Returns whether sparse trie pruning is pending.
161    pub const fn pending_sparse_trie_prune(&self) -> bool {
162        self.pending_sparse_trie_prune
163    }
164
165    /// Sets whether sparse trie pruning is pending for the next sparse trie task.
166    pub const fn set_pending_sparse_trie_prune(&mut self, pending: bool) {
167        self.pending_sparse_trie_prune = pending;
168    }
169
170    /// Takes a pending sparse trie prune request, if any, and snapshots the in-memory parent chain
171    /// ending at `parent_hash`.
172    ///
173    /// `None` means no prune request is pending. `Some(Vec::new())` means a prune was requested,
174    /// but no in-memory parent-chain blocks were found for the parent hash; the sparse trie task
175    /// should still prune nodes cached before the current block's epoch.
176    pub fn take_sparse_trie_prune_blocks(
177        &mut self,
178        parent_hash: B256,
179    ) -> Option<Vec<ExecutedBlock<N>>> {
180        if !self.pending_sparse_trie_prune {
181            return None
182        }
183
184        self.pending_sparse_trie_prune = false;
185        Some(
186            self.tree_state
187                .blocks_by_hash(parent_hash)
188                .map(|(_, blocks)| blocks)
189                .unwrap_or_default(),
190        )
191    }
192
193    /// Returns true if the block has been marked as invalid.
194    pub fn has_invalid_header(&mut self, hash: &B256) -> bool {
195        self.invalid_headers.get(hash).is_some()
196    }
197}
198
199/// The outcome of a tree operation.
200#[derive(Debug)]
201pub struct TreeOutcome<T> {
202    /// The outcome of the operation.
203    pub outcome: T,
204    /// An optional event to tell the caller to do something.
205    pub event: Option<TreeEvent>,
206    /// Whether the block was already seen, meaning no real execution happened during this
207    /// `newPayload` call.
208    pub already_seen: bool,
209}
210
211impl<T> TreeOutcome<T> {
212    /// Create new tree outcome.
213    pub const fn new(outcome: T) -> Self {
214        Self { outcome, event: None, already_seen: false }
215    }
216
217    /// Set event on the outcome.
218    pub fn with_event(mut self, event: TreeEvent) -> Self {
219        self.event = Some(event);
220        self
221    }
222
223    /// Set the `already_seen` flag on the outcome.
224    pub const fn with_already_seen(mut self, value: bool) -> Self {
225        self.already_seen = value;
226        self
227    }
228}
229
230/// Result of trying to insert a new payload in [`EngineApiTreeHandler`].
231#[derive(Debug)]
232pub struct TryInsertPayloadResult {
233    /// - `Valid`: Payload successfully validated and inserted
234    /// - `Syncing`: Parent missing, payload buffered for later
235    /// - Error status: Payload is invalid
236    pub status: PayloadStatus,
237    /// Whether the block was already seen
238    pub already_seen: bool,
239}
240
241impl TryInsertPayloadResult {
242    /// Convert the result into a [`TreeOutcome`].
243    #[inline]
244    pub fn into_outcome(self) -> TreeOutcome<PayloadStatus> {
245        TreeOutcome::new(self.status).with_already_seen(self.already_seen)
246    }
247}
248
249/// Events that are triggered by Tree Chain
250#[derive(Debug)]
251pub enum TreeEvent {
252    /// Tree action is needed.
253    TreeAction(TreeAction),
254    /// Backfill action is needed.
255    BackfillAction(BackfillAction),
256    /// Block download is needed.
257    Download(DownloadRequest),
258}
259
260impl TreeEvent {
261    /// Returns true if the event is a backfill action.
262    const fn is_backfill_action(&self) -> bool {
263        matches!(self, Self::BackfillAction(_))
264    }
265}
266
267/// The actions that can be performed on the tree.
268#[derive(Debug)]
269pub enum TreeAction {
270    /// Make target canonical.
271    MakeCanonical {
272        /// The sync target head hash
273        sync_target_head: B256,
274    },
275}
276
277/// The engine API tree handler implementation.
278///
279/// This type is responsible for processing engine API requests, maintaining the canonical state and
280/// emitting events.
281pub struct EngineApiTreeHandler<N, P, T, V, C>
282where
283    N: NodePrimitives,
284    T: PayloadTypes,
285    C: ConfigureEvm<Primitives = N> + 'static,
286{
287    provider: P,
288    consensus: Arc<dyn FullConsensus<N>>,
289    payload_validator: V,
290    /// Keeps track of internals such as executed and buffered blocks.
291    state: EngineApiTreeState<N>,
292    /// The half for sending messages to the engine.
293    ///
294    /// This is kept so that we can queue in messages to ourself that we can process later, for
295    /// example distributing workload across multiple messages that would otherwise take too long
296    /// to process. E.g. we might receive a range of downloaded blocks and we want to process
297    /// them one by one so that we can handle incoming engine API in between and don't become
298    /// unresponsive. This can happen during live sync transition where we're trying to close the
299    /// gap (up to 3 epochs of blocks in the worst case).
300    incoming_tx: Sender<FromEngine<EngineApiRequest<T, N>, N::Block>>,
301    /// Incoming engine API requests.
302    incoming: Receiver<FromEngine<EngineApiRequest<T, N>, N::Block>>,
303    /// Outgoing events that are emitted to the handler.
304    outgoing: UnboundedSender<EngineApiEvent<N>>,
305    /// Channels to the persistence layer.
306    persistence: PersistenceHandle<N>,
307    /// Tracks the state changes of the persistence task.
308    persistence_state: PersistenceState,
309    /// Flag indicating the state of the node's backfill synchronization process.
310    backfill_sync_state: BackfillSyncState,
311    /// Keeps track of the state of the canonical chain that isn't persisted yet.
312    /// This is intended to be accessed from external sources, such as rpc.
313    canonical_in_memory_state: CanonicalInMemoryState<N>,
314    /// Handle to the payload builder that will receive payload attributes for valid forkchoice
315    /// updates
316    payload_builder: PayloadBuilderHandle<T>,
317    /// Configuration settings.
318    config: TreeConfig,
319    /// Metrics for the engine api.
320    metrics: EngineApiMetrics,
321    /// The engine API variant of this handler
322    engine_kind: EngineApiKind,
323    /// The EVM configuration.
324    evm_config: C,
325    /// Timing statistics for executed blocks, keyed by block hash.
326    /// Stored here (not in `ExecutedBlock`) to avoid leaking observability concerns into the block
327    /// type. Entries are removed when blocks are persisted or invalidated.
328    execution_timing_stats: B256Map<Box<ExecutionTimingStats>>,
329    /// Tracks payload jobs that may still access in-memory overlay state.
330    payload_builds: PayloadBuildTracker,
331    /// Notifies the engine when the final active payload job finishes.
332    payload_build_finished: Receiver<()>,
333    /// Task runtime for spawning blocking work on named, reusable threads.
334    runtime: reth_tasks::Runtime,
335}
336
337impl<N, P: Debug, T: PayloadTypes + Debug, V: Debug, C> std::fmt::Debug
338    for EngineApiTreeHandler<N, P, T, V, C>
339where
340    N: NodePrimitives,
341    C: Debug + ConfigureEvm<Primitives = N>,
342{
343    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
344        f.debug_struct("EngineApiTreeHandler")
345            .field("provider", &self.provider)
346            .field("consensus", &self.consensus)
347            .field("payload_validator", &self.payload_validator)
348            .field("state", &self.state)
349            .field("incoming_tx", &self.incoming_tx)
350            .field("persistence", &self.persistence)
351            .field("persistence_state", &self.persistence_state)
352            .field("backfill_sync_state", &self.backfill_sync_state)
353            .field("canonical_in_memory_state", &self.canonical_in_memory_state)
354            .field("payload_builder", &self.payload_builder)
355            .field("config", &self.config)
356            .field("metrics", &self.metrics)
357            .field("engine_kind", &self.engine_kind)
358            .field("evm_config", &self.evm_config)
359            .field("execution_timing_stats", &self.execution_timing_stats.len())
360            .field("payload_builds_active", &self.payload_builds.is_active())
361            .field("runtime", &self.runtime)
362            .finish()
363    }
364}
365
366impl<N, P, T, V, C> EngineApiTreeHandler<N, P, T, V, C>
367where
368    N: NodePrimitives,
369    P: DatabaseProviderFactory
370        + BlockReader<Block = N::Block, Header = N::BlockHeader>
371        + StateProviderFactory
372        + StateReader<Receipt = N::Receipt>
373        + BalProvider
374        + Clone
375        + 'static,
376    P::Provider: BlockReader<Block = N::Block, Header = N::BlockHeader>
377        + PruneCheckpointReader
378        + StageCheckpointReader
379        + ChangeSetReader
380        + StorageChangeSetReader
381        + StorageSettingsCache
382        + 'static,
383    C: ConfigureEvm<Primitives = N> + 'static,
384    T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>,
385    V: EngineValidator<T> + WaitForCaches,
386{
387    /// Creates a new [`EngineApiTreeHandler`].
388    #[expect(clippy::too_many_arguments)]
389    pub fn new(
390        provider: P,
391        consensus: Arc<dyn FullConsensus<N>>,
392        payload_validator: V,
393        outgoing: UnboundedSender<EngineApiEvent<N>>,
394        state: EngineApiTreeState<N>,
395        canonical_in_memory_state: CanonicalInMemoryState<N>,
396        persistence: PersistenceHandle<N>,
397        persistence_state: PersistenceState,
398        payload_builder: PayloadBuilderHandle<T>,
399        config: TreeConfig,
400        engine_kind: EngineApiKind,
401        evm_config: C,
402        runtime: reth_tasks::Runtime,
403    ) -> Self {
404        let (incoming_tx, incoming) = crossbeam_channel::unbounded();
405
406        let (payload_builds, payload_build_finished) = PayloadBuildTracker::new();
407
408        Self {
409            provider,
410            consensus,
411            payload_validator,
412            incoming,
413            outgoing,
414            persistence,
415            persistence_state,
416            backfill_sync_state: BackfillSyncState::Idle,
417            state,
418            canonical_in_memory_state,
419            payload_builder,
420            config,
421            metrics: Default::default(),
422            incoming_tx,
423            engine_kind,
424            evm_config,
425            execution_timing_stats: B256Map::default(),
426            payload_builds,
427            payload_build_finished,
428            runtime,
429        }
430    }
431
432    /// Creates a new [`EngineApiTreeHandler`] instance and spawns it in its
433    /// own thread.
434    ///
435    /// Returns the sender through which incoming requests can be sent to the task and the receiver
436    /// end of a [`EngineApiEvent`] unbounded channel to receive events from the engine.
437    #[expect(clippy::complexity)]
438    pub fn spawn_new(
439        provider: P,
440        consensus: Arc<dyn FullConsensus<N>>,
441        payload_validator: V,
442        persistence: PersistenceHandle<N>,
443        payload_builder: PayloadBuilderHandle<T>,
444        canonical_in_memory_state: CanonicalInMemoryState<N>,
445        overlay_manager: OverlayManager<N>,
446        config: TreeConfig,
447        kind: EngineApiKind,
448        evm_config: C,
449        runtime: reth_tasks::Runtime,
450    ) -> (Sender<FromEngine<EngineApiRequest<T, N>, N::Block>>, UnboundedReceiver<EngineApiEvent<N>>)
451    {
452        let best_block_number = provider.best_block_number().unwrap_or(0);
453        let header = provider.sealed_header(best_block_number).ok().flatten().unwrap_or_default();
454
455        let persistence_state = PersistenceState {
456            last_persisted_block: BlockNumHash::new(best_block_number, header.hash()),
457            last_state_trie_persisted_block: BlockNumHash::new(best_block_number, header.hash()),
458            rx: None,
459        };
460
461        let (tx, outgoing) = unbounded_channel();
462        let state = EngineApiTreeState::new(
463            config.block_buffer_limit(),
464            config.max_invalid_header_cache_length(),
465            config.invalid_header_hit_eviction_threshold(),
466            header.num_hash(),
467            kind,
468            overlay_manager,
469        );
470
471        let task = Self::new(
472            provider,
473            consensus,
474            payload_validator,
475            tx,
476            state,
477            canonical_in_memory_state,
478            persistence,
479            persistence_state,
480            payload_builder,
481            config,
482            kind,
483            evm_config,
484            runtime,
485        );
486        let incoming = task.incoming_tx.clone();
487        spawn_os_thread("engine", || {
488            increase_thread_priority();
489            task.run()
490        });
491        (incoming, outgoing)
492    }
493
494    /// Returns a [`TreeOutcome`] indicating the forkchoice head is valid and canonical.
495    fn valid_outcome(state: ForkchoiceState) -> TreeOutcome<OnForkChoiceUpdated> {
496        TreeOutcome::new(OnForkChoiceUpdated::valid(PayloadStatus::new(
497            PayloadStatusEnum::Valid,
498            Some(state.head_block_hash),
499        )))
500    }
501
502    /// Returns a new [`Sender`] to send messages to this type.
503    pub fn sender(&self) -> Sender<FromEngine<EngineApiRequest<T, N>, N::Block>> {
504        self.incoming_tx.clone()
505    }
506
507    /// How many blocks the canonical tip is ahead of the last persisted block. A large gap means
508    /// persistence is falling behind execution.
509    const fn persistence_gap(&self) -> u64 {
510        self.state
511            .tree_state
512            .canonical_block_number()
513            .saturating_sub(self.persistence_state.last_persisted_block.number)
514    }
515
516    /// How many blocks beyond the configured in-memory buffer are awaiting persistence.
517    const fn persistence_backpressure_gap(&self) -> u64 {
518        self.persistence_gap().saturating_sub(self.config.memory_block_buffer_target())
519    }
520
521    /// Returns `true` when the main loop should stop draining the tree input channel.
522    ///
523    /// This is the case when persistence is already running and the number of blocks beyond the
524    /// configured in-memory buffer has reached the configured threshold.
525    const fn should_backpressure(&self) -> bool {
526        self.persistence_state.in_progress() &&
527            self.persistence_backpressure_gap() >=
528                self.config.persistence_backpressure_threshold()
529    }
530
531    /// Run the engine API handler.
532    ///
533    /// This will block the current thread and process incoming messages.
534    pub fn run(mut self) {
535        loop {
536            // Each iteration has three phases:
537            //
538            // 1. Non-blocking poll for persistence completion. If the background flush already
539            //    landed, absorb the result now so the gap calculation below is fresh.
540            // 2. Decide how to wait for the next event. When the canonical-to-persisted gap beyond
541            //    the in-memory buffer reaches the backpressure threshold we only block on the
542            //    persistence receiver, leaving new engine requests sitting in the unbounded
543            //    upstream channel.
544            // 3. Handle the event (engine message or persistence completion) and kick off a new
545            //    persistence cycle if the threshold is met again.
546            //
547            // The net effect: when the unbuffered persistence gap reaches the threshold, we stop
548            // processing incoming messages and let them queue in the channel. This is only a soft
549            // form of backpressure: it delays replies and, more importantly, prevents executing
550            // further blocks that would pile up in the persistence queue - where each block
551            // carries heavier state (eg. trie updates) than the raw payload sitting in the engine
552            // channel.
553            //
554            // Standard Ethereum CLs won't truly back off - the engine API has no
555            // backpressure semantics, and CLs typically timeout after ≈8s and resend - so
556            // this cannot prevent the incoming channel from growing under sustained load.
557            // But it shifts the bottleneck to the lighter-weight incoming queue rather than
558            // the costlier persistence pipeline. Other clients that respect reply latency
559            // can treat the delayed responses as a signal to chill out.
560            match self.try_poll_persistence() {
561                Ok(true) => {
562                    if let Err(err) = self.advance_persistence() {
563                        error!(target: "engine::tree", %err, "Advancing persistence failed");
564                        return
565                    }
566                    continue;
567                }
568                Ok(false) => {}
569                Err(err) => {
570                    error!(target: "engine::tree", %err, "Polling persistence failed");
571                    return
572                }
573            }
574
575            let event = if self.should_backpressure() {
576                self.metrics.engine.backpressure_active.set(1.0);
577                let stall_start = Instant::now();
578                let event = self.wait_for_persistence_event();
579                self.metrics.engine.backpressure_stall_duration.record(stall_start.elapsed());
580                event
581            } else {
582                self.metrics.engine.backpressure_active.set(0.0);
583                self.wait_for_event()
584            };
585
586            match event {
587                LoopEvent::EngineMessage(msg) => {
588                    debug!(target: "engine::tree", %msg, "received new engine message");
589                    match self.on_engine_message(msg) {
590                        Ok(ops::ControlFlow::Break(())) => return,
591                        Ok(ops::ControlFlow::Continue(())) => {}
592                        Err(fatal) => {
593                            error!(target: "engine::tree", %fatal, "insert block fatal error");
594                            return
595                        }
596                    }
597                }
598                LoopEvent::PersistenceComplete { result, start_time } => {
599                    if let Err(err) = self.on_persistence_complete(result, start_time) {
600                        error!(target: "engine::tree", %err, "Persistence complete handling failed");
601                        return
602                    }
603                }
604                LoopEvent::PayloadBuildFinished => {}
605                LoopEvent::Disconnected => {
606                    error!(target: "engine::tree", "Channel disconnected");
607                    return
608                }
609            }
610
611            // Always check if we need to trigger new persistence after any event:
612            // - After engine messages: new blocks may have been inserted that exceed the
613            //   persistence threshold
614            // - After persistence completion: we can now persist more blocks if needed
615            if let Err(err) = self.advance_persistence() {
616                error!(target: "engine::tree", %err, "Advancing persistence failed");
617                return
618            }
619        }
620    }
621
622    /// Blocks until the in-flight persistence task completes, used when we are under
623    /// backpressure.
624    ///
625    /// Unlike `wait_for_event`, this deliberately does not read from the tree input channel. Any
626    /// requests sent to the tree remain queued upstream until persistence catches up.
627    fn wait_for_persistence_event(&mut self) -> LoopEvent<T, N> {
628        let maybe_persistence = self.persistence_state.rx.take();
629
630        if let Some((persistence_rx, start_time, _action)) = maybe_persistence {
631            match persistence_rx.recv() {
632                Ok(result) => LoopEvent::PersistenceComplete { result, start_time },
633                Err(_) => LoopEvent::Disconnected,
634            }
635        } else {
636            self.wait_for_event()
637        }
638    }
639
640    /// Blocks until the next event is ready.
641    ///
642    /// Uses biased selection to prioritize persistence completion so in-memory state is updated and
643    /// further writes are unblocked.
644    fn wait_for_event(&mut self) -> LoopEvent<T, N> {
645        // Take ownership of persistence rx if present
646        let maybe_persistence = self.persistence_state.rx.take();
647
648        if let Some((persistence_rx, start_time, action)) = maybe_persistence {
649            // Biased select prioritizes persistence completion to update in memory state and
650            // unblock further writes
651            crossbeam_channel::select_biased! {
652                recv(persistence_rx) -> result => {
653                    // Don't put it back - consumed (oneshot-like behavior)
654                    match result {
655                        Ok(result) => LoopEvent::PersistenceComplete {
656                            result,
657                            start_time,
658                        },
659                        Err(_) => LoopEvent::Disconnected,
660                    }
661                },
662                recv(self.payload_build_finished) -> result => {
663                    // Put the persistence rx back - we didn't consume it.
664                    self.persistence_state.rx = Some((persistence_rx, start_time, action));
665                    match result {
666                        Ok(()) => LoopEvent::PayloadBuildFinished,
667                        Err(_) => LoopEvent::Disconnected,
668                    }
669                },
670                recv(self.incoming) -> msg => {
671                    // Put the persistence rx back - we didn't consume it
672                    self.persistence_state.rx = Some((persistence_rx, start_time, action));
673                    match msg {
674                        Ok(m) => LoopEvent::EngineMessage(m),
675                        Err(_) => LoopEvent::Disconnected,
676                    }
677                },
678            }
679        } else {
680            // No persistence in progress - wait on an incoming message or payload job completion.
681            crossbeam_channel::select_biased! {
682                recv(self.payload_build_finished) -> result => match result {
683                    Ok(()) => LoopEvent::PayloadBuildFinished,
684                    Err(_) => LoopEvent::Disconnected,
685                },
686                recv(self.incoming) -> msg => match msg {
687                    Ok(m) => LoopEvent::EngineMessage(m),
688                    Err(_) => LoopEvent::Disconnected,
689                },
690            }
691        }
692    }
693
694    /// Invoked when previously requested blocks were downloaded.
695    ///
696    /// If the block count exceeds the configured batch size we're allowed to execute at once, this
697    /// will execute the first batch and send the remaining blocks back through the channel so that
698    /// block request processing isn't blocked for a long time.
699    fn on_downloaded(
700        &mut self,
701        mut blocks: Vec<SealedBlockWithAccessList<N::Block>>,
702    ) -> Result<Option<TreeEvent>, InsertBlockFatalError> {
703        if blocks.is_empty() {
704            // nothing to execute
705            return Ok(None)
706        }
707
708        trace!(target: "engine::tree", block_count = %blocks.len(), "received downloaded blocks");
709        let batch = self.config.max_execute_block_batch_size().min(blocks.len());
710        for block in blocks.drain(..batch) {
711            if let Some(event) = self.on_downloaded_block(block)? {
712                let needs_backfill = event.is_backfill_action();
713                self.on_tree_event(event)?;
714                if needs_backfill {
715                    // can exit early if backfill is needed
716                    return Ok(None)
717                }
718            }
719        }
720
721        // if we still have blocks to execute, send them as a followup request
722        if !blocks.is_empty() {
723            let _ = self.incoming_tx.send(FromEngine::DownloadedBlocks(blocks));
724        }
725
726        Ok(None)
727    }
728
729    /// When the Consensus layer receives a new block via the consensus gossip protocol,
730    /// the transactions in the block are sent to the execution layer in the form of a
731    /// [`PayloadTypes::ExecutionData`], for example
732    /// [`ExecutionData`](reth_payload_primitives::PayloadTypes::ExecutionData). The
733    /// Execution layer executes the transactions and validates the state in the block header,
734    /// then passes validation data back to Consensus layer, that adds the block to the head of
735    /// its own blockchain and attests to it. The block is then broadcast over the consensus p2p
736    /// network in the form of a "Beacon block".
737    ///
738    /// These responses should adhere to the [Engine API Spec for
739    /// `engine_newPayload`](https://github.com/ethereum/execution-apis/blob/main/src/engine/paris.md#specification).
740    ///
741    /// This returns a [`PayloadStatus`] that represents the outcome of a processed new payload and
742    /// returns an error if an internal error occurred.
743    #[instrument(
744        level = "debug",
745        target = "engine::tree",
746        skip_all,
747        fields(block_hash = %payload.block_hash(), block_num = %payload.block_number()),
748    )]
749    fn on_new_payload(
750        &mut self,
751        payload: T::ExecutionData,
752    ) -> Result<TreeOutcome<PayloadStatus>, InsertBlockProcessingError> {
753        let _thread_resource_usage =
754            self.metrics.engine.new_payload.measure_thread_resource_usage();
755        trace!(target: "engine::tree", "invoked new payload");
756
757        // start timing for the new payload process
758        let start = Instant::now();
759
760        // Ensures that the given payload does not violate any consensus rules that concern the
761        // block's layout, like:
762        //    - missing or invalid base fee
763        //    - invalid extra data
764        //    - invalid transactions
765        //    - incorrect hash
766        //    - the versioned hashes passed with the payload do not exactly match transaction
767        //      versioned hashes
768        //    - the block does not contain blob transactions if it is pre-cancun
769        //
770        // This validates the following engine API rule:
771        //
772        // 3. Given the expected array of blob versioned hashes client software **MUST** run its
773        //    validation by taking the following steps:
774        //
775        //   1. Obtain the actual array by concatenating blob versioned hashes lists
776        //      (`tx.blob_versioned_hashes`) of each [blob
777        //      transaction](https://eips.ethereum.org/EIPS/eip-4844#new-transaction-type) included
778        //      in the payload, respecting the order of inclusion. If the payload has no blob
779        //      transactions the expected array **MUST** be `[]`.
780        //
781        //   2. Return `{status: INVALID, latestValidHash: null, validationError: errorMessage |
782        //      null}` if the expected and the actual arrays don't match.
783        //
784        // This validation **MUST** be instantly run in all cases even during active sync process.
785
786        let num_hash = payload.num_hash();
787        let engine_event = ConsensusEngineEvent::BlockReceived(num_hash);
788        self.emit_event(EngineApiEvent::BeaconConsensus(engine_event));
789
790        let block_hash = num_hash.hash;
791
792        // Check for invalid ancestors
793        if let Some(invalid) = self.find_invalid_ancestor(&payload) {
794            let status = self.handle_invalid_ancestor_payload(payload, invalid)?;
795            return Ok(TreeOutcome::new(status));
796        }
797
798        // record pre-execution phase duration
799        self.metrics.block_validation.record_payload_validation(start.elapsed().as_secs_f64());
800
801        let mut outcome = if self.backfill_sync_state.is_idle() {
802            self.try_insert_payload(payload)?.into_outcome()
803        } else {
804            TreeOutcome::new(self.try_buffer_payload(payload)?)
805        };
806
807        // if the block is valid and it is the current sync target head, make it canonical
808        if outcome.outcome.is_valid() && self.is_sync_target_head(block_hash) {
809            // Only create the canonical event if this block isn't already the canonical head
810            if self.state.tree_state.canonical_block_hash() != block_hash {
811                outcome = outcome.with_event(TreeEvent::TreeAction(TreeAction::MakeCanonical {
812                    sync_target_head: block_hash,
813                }));
814            }
815        }
816
817        // record total newPayload duration
818        self.metrics.block_validation.total_duration.record(start.elapsed().as_secs_f64());
819
820        Ok(outcome)
821    }
822
823    /// Processes a payload during normal sync operation.
824    #[instrument(level = "debug", target = "engine::tree", skip_all)]
825    fn try_insert_payload(
826        &mut self,
827        payload: T::ExecutionData,
828    ) -> Result<TryInsertPayloadResult, InsertBlockProcessingError> {
829        let block_hash = payload.block_hash();
830        let num_hash = payload.num_hash();
831        let parent_hash = payload.parent_hash();
832        let mut latest_valid_hash = None;
833
834        match self.insert_payload(payload) {
835            Ok(status) => {
836                let (status, already_seen) = match status {
837                    InsertPayloadOk::Inserted(BlockStatus::Valid) => {
838                        latest_valid_hash = Some(block_hash);
839                        self.try_connect_buffered_blocks(num_hash)?;
840                        (PayloadStatusEnum::Valid, false)
841                    }
842                    InsertPayloadOk::AlreadySeen(BlockStatus::Valid) => {
843                        latest_valid_hash = Some(block_hash);
844                        (PayloadStatusEnum::Valid, true)
845                    }
846                    InsertPayloadOk::Inserted(BlockStatus::Disconnected { .. }) => {
847                        (PayloadStatusEnum::Syncing, false)
848                    }
849                    InsertPayloadOk::AlreadySeen(BlockStatus::Disconnected { .. }) => {
850                        // not known to be invalid, but we don't know anything else
851                        (PayloadStatusEnum::Syncing, true)
852                    }
853                };
854
855                Ok(TryInsertPayloadResult {
856                    status: PayloadStatus::new(status, latest_valid_hash),
857                    already_seen,
858                })
859            }
860            Err(error) => {
861                let status = match error {
862                    InsertPayloadError::Block(error) => self.on_insert_block_error(error)?,
863                    InsertPayloadError::Payload(error) => self
864                        .on_new_payload_error(error, num_hash, parent_hash)
865                        .map_err(InsertBlockFatalError::from)?,
866                };
867
868                Ok(TryInsertPayloadResult { status, already_seen: false })
869            }
870        }
871    }
872
873    /// Stores a payload for later processing during backfill sync.
874    ///
875    /// During backfill, the node lacks the state needed to validate payloads,
876    /// so they are buffered (stored in memory) until their parent blocks are synced.
877    ///
878    /// Returns:
879    /// - `Syncing`: Payload successfully buffered
880    /// - Error status: Payload is malformed or invalid
881    fn try_buffer_payload(
882        &mut self,
883        payload: T::ExecutionData,
884    ) -> Result<PayloadStatus, InsertBlockProcessingError> {
885        let parent_hash = payload.parent_hash();
886        let num_hash = payload.num_hash();
887
888        match self.payload_validator.convert_payload_to_block(payload) {
889            // if the block is well-formed, buffer it for later
890            Ok(block) => {
891                if let Err(error) = self.buffer_block(block) {
892                    self.on_insert_block_error(error)
893                } else {
894                    Ok(PayloadStatus::from_status(PayloadStatusEnum::Syncing))
895                }
896            }
897            Err(error) => Ok(self
898                .on_new_payload_error(error, num_hash, parent_hash)
899                .map_err(InsertBlockFatalError::from)?),
900        }
901    }
902
903    /// Returns the new chain for the given head.
904    ///
905    /// This also handles reorgs.
906    ///
907    /// Note: This does not update the tracked state and instead returns the new chain based on the
908    /// given head.
909    fn on_new_head(&self, new_head: B256) -> ProviderResult<Option<NewCanonicalChain<N>>> {
910        // get the executed new head block
911        let Some(new_head_block) = self.state.tree_state.blocks_by_hash.get(&new_head) else {
912            debug!(target: "engine::tree", new_head=?new_head, "New head block not found in inmemory tree state");
913            self.metrics.engine.executed_new_block_cache_miss.increment(1);
914            return Ok(None)
915        };
916
917        let new_head_number = new_head_block.recovered_block().number();
918        let mut current_canonical_number = self.state.tree_state.current_canonical_head.number;
919
920        let mut new_chain = vec![new_head_block.clone()];
921        let mut current_hash = new_head_block.recovered_block().parent_hash();
922        let mut current_number = new_head_number - 1;
923
924        // Walk back the new chain until we reach a block we know about
925        //
926        // This is only done for in-memory blocks, because we should not have persisted any blocks
927        // that are _above_ the current canonical head.
928        while current_number > current_canonical_number {
929            if let Some(block) = self.state.tree_state.executed_block_by_hash(current_hash).cloned()
930            {
931                current_hash = block.recovered_block().parent_hash();
932                current_number -= 1;
933                new_chain.push(block);
934            } else {
935                warn!(target: "engine::tree", current_hash=?current_hash, "Sidechain block not found in TreeState");
936                // This should never happen as we're walking back a chain that should connect to
937                // the canonical chain
938                return Ok(None)
939            }
940        }
941
942        // If we have reached the current canonical head by walking back from the target, then we
943        // know this represents an extension of the canonical chain.
944        if current_hash == self.state.tree_state.current_canonical_head.hash {
945            new_chain.reverse();
946
947            // Simple extension of the current chain
948            return Ok(Some(NewCanonicalChain::Commit { new: new_chain }))
949        }
950
951        // We have a reorg. Walk back both chains to find the fork point.
952        let mut old_chain = Vec::new();
953        let mut old_hash = self.state.tree_state.current_canonical_head.hash;
954
955        // If the canonical chain is ahead of the new chain,
956        // gather all blocks until new head number.
957        while current_canonical_number > current_number {
958            let block = self.canonical_block_by_hash(old_hash)?;
959            old_hash = block.recovered_block().parent_hash();
960            old_chain.push(block);
961            current_canonical_number -= 1;
962        }
963
964        // Both new and old chain pointers are now at the same height.
965        debug_assert_eq!(current_number, current_canonical_number);
966
967        // Walk both chains from specified hashes at same height until
968        // a common ancestor (fork block) is reached.
969        while old_hash != current_hash {
970            let block = self.canonical_block_by_hash(old_hash)?;
971            old_hash = block.recovered_block().parent_hash();
972            old_chain.push(block);
973
974            if let Some(block) = self.state.tree_state.executed_block_by_hash(current_hash).cloned()
975            {
976                current_hash = block.recovered_block().parent_hash();
977                new_chain.push(block);
978            } else {
979                // This shouldn't happen as we've already walked this path
980                warn!(target: "engine::tree", invalid_hash=?current_hash, "New chain block not found in TreeState");
981                return Ok(None)
982            }
983        }
984        new_chain.reverse();
985        old_chain.reverse();
986
987        Ok(Some(NewCanonicalChain::Reorg { new: new_chain, old: old_chain }))
988    }
989
990    /// Updates the latest block state to the specified canonical ancestor.
991    ///
992    /// This method ensures that the latest block tracks the given canonical header by resetting
993    ///
994    /// # Arguments
995    /// * `canonical_header` - The canonical header to set as the new head
996    ///
997    /// # Returns
998    /// * `ProviderResult<()>` - Ok(()) on success, error if state update fails
999    ///
1000    /// Caution: This unwinds the canonical chain
1001    fn update_latest_block_to_canonical_ancestor(
1002        &mut self,
1003        canonical_header: &SealedHeader<N::BlockHeader>,
1004    ) -> ProviderResult<()> {
1005        debug!(target: "engine::tree", head = ?canonical_header.num_hash(), "Update latest block to canonical ancestor");
1006        let current_head_number = self.state.tree_state.canonical_block_number();
1007        let new_head_number = canonical_header.number();
1008        let new_head_hash = canonical_header.hash();
1009
1010        // Update tree state with the new canonical head
1011        self.state.tree_state.set_canonical_head(canonical_header.num_hash());
1012
1013        // Handle the state update based on whether this is an unwind scenario
1014        if new_head_number < current_head_number {
1015            debug!(
1016                target: "engine::tree",
1017                current_head = current_head_number,
1018                new_head = new_head_number,
1019                new_head_hash = ?new_head_hash,
1020                "FCU unwind detected: reverting to canonical ancestor"
1021            );
1022
1023            self.handle_canonical_chain_unwind(current_head_number, canonical_header)
1024        } else {
1025            debug!(
1026                target: "engine::tree",
1027                previous_head = current_head_number,
1028                new_head = new_head_number,
1029                new_head_hash = ?new_head_hash,
1030                "Advancing latest block to canonical ancestor"
1031            );
1032            self.handle_chain_advance_or_same_height(canonical_header)
1033        }
1034    }
1035
1036    /// Handles chain unwind scenarios by collecting blocks to remove and performing an unwind back
1037    /// to the canonical header
1038    fn handle_canonical_chain_unwind(
1039        &self,
1040        current_head_number: u64,
1041        canonical_header: &SealedHeader<N::BlockHeader>,
1042    ) -> ProviderResult<()> {
1043        let new_head_number = canonical_header.number();
1044        debug!(
1045            target: "engine::tree",
1046            from = current_head_number,
1047            to = new_head_number,
1048            "Handling unwind: collecting blocks to remove from in-memory state"
1049        );
1050
1051        // Collect blocks that need to be removed from memory
1052        let old_blocks =
1053            self.collect_blocks_for_canonical_unwind(new_head_number, current_head_number);
1054
1055        // Load and apply the canonical ancestor block
1056        self.apply_canonical_ancestor_via_reorg(canonical_header, old_blocks)
1057    }
1058
1059    /// Collects blocks from memory that need to be removed during an unwind to a canonical block.
1060    fn collect_blocks_for_canonical_unwind(
1061        &self,
1062        new_head_number: u64,
1063        current_head_number: u64,
1064    ) -> Vec<ExecutedBlock<N>> {
1065        let mut old_blocks =
1066            Vec::with_capacity((current_head_number.saturating_sub(new_head_number)) as usize);
1067
1068        for block_num in (new_head_number + 1)..=current_head_number {
1069            if let Some(block_state) = self.canonical_in_memory_state.state_by_number(block_num) {
1070                let executed_block = block_state.block_ref().clone();
1071                old_blocks.push(executed_block);
1072                debug!(
1073                    target: "engine::tree",
1074                    block_number = block_num,
1075                    "Collected block for removal from in-memory state"
1076                );
1077            }
1078        }
1079
1080        if old_blocks.is_empty() {
1081            debug!(
1082                target: "engine::tree",
1083                "No blocks found in memory to remove, will clear and reset state"
1084            );
1085        }
1086
1087        old_blocks
1088    }
1089
1090    /// Applies the canonical ancestor block via a reorg operation.
1091    fn apply_canonical_ancestor_via_reorg(
1092        &self,
1093        canonical_header: &SealedHeader<N::BlockHeader>,
1094        old_blocks: Vec<ExecutedBlock<N>>,
1095    ) -> ProviderResult<()> {
1096        let new_head_hash = canonical_header.hash();
1097        let new_head_number = canonical_header.number();
1098
1099        // Load the canonical ancestor's block
1100        let executed_block = self.canonical_block_by_hash(new_head_hash)?;
1101        // Perform the reorg to properly handle the unwind
1102        self.canonical_in_memory_state
1103            .update_chain(NewCanonicalChain::Reorg { new: vec![executed_block], old: old_blocks });
1104
1105        // CRITICAL: Update the canonical head after the reorg
1106        // This ensures get_canonical_head() returns the correct block
1107        self.canonical_in_memory_state.set_canonical_head(canonical_header.clone());
1108
1109        debug!(
1110            target: "engine::tree",
1111            block_number = new_head_number,
1112            block_hash = ?new_head_hash,
1113            "Successfully loaded canonical ancestor into memory via reorg"
1114        );
1115
1116        Ok(())
1117    }
1118
1119    /// Handles chain advance or same height scenarios.
1120    fn handle_chain_advance_or_same_height(
1121        &self,
1122        canonical_header: &SealedHeader<N::BlockHeader>,
1123    ) -> ProviderResult<()> {
1124        // Load the block into memory if it's not already present
1125        self.ensure_block_in_memory(canonical_header.number(), canonical_header.hash())?;
1126
1127        // Update the canonical head header
1128        self.canonical_in_memory_state.set_canonical_head(canonical_header.clone());
1129
1130        Ok(())
1131    }
1132
1133    /// Ensures a block is loaded into memory if not already present.
1134    fn ensure_block_in_memory(&self, block_number: u64, block_hash: B256) -> ProviderResult<()> {
1135        // Check if block is already in memory
1136        if self.canonical_in_memory_state.state_by_number(block_number).is_some() {
1137            return Ok(());
1138        }
1139
1140        // Load the block from storage
1141        let executed_block = self.canonical_block_by_hash(block_hash)?;
1142        self.canonical_in_memory_state
1143            .update_chain(NewCanonicalChain::Commit { new: vec![executed_block] });
1144
1145        debug!(
1146            target: "engine::tree",
1147            block_number,
1148            block_hash = ?block_hash,
1149            "Added canonical block to in-memory state"
1150        );
1151
1152        Ok(())
1153    }
1154
1155    /// Invoked when we receive a new forkchoice update message. Calls into the blockchain tree
1156    /// to resolve chain forks and ensure that the Execution Layer is working with the latest valid
1157    /// chain.
1158    ///
1159    /// These responses should adhere to the [Engine API Spec for
1160    /// `engine_forkchoiceUpdated`](https://github.com/ethereum/execution-apis/blob/main/src/engine/paris.md#specification-1).
1161    ///
1162    /// Returns an error if an internal error occurred like a database error.
1163    #[instrument(level = "debug", target = "engine::tree", skip_all, fields(head = % state.head_block_hash, safe = % state.safe_block_hash,finalized = % state.finalized_block_hash))]
1164    fn on_forkchoice_updated(
1165        &mut self,
1166        state: ForkchoiceState,
1167        attrs: Option<T::PayloadAttributes>,
1168    ) -> ProviderResult<TreeOutcome<OnForkChoiceUpdated>> {
1169        trace!(target: "engine::tree", ?attrs, "invoked forkchoice update");
1170
1171        // Record metrics
1172        self.record_forkchoice_metrics();
1173
1174        // Pre-validation of forkchoice state
1175        if let Some(early_result) = self.validate_forkchoice_state(state)? {
1176            return Ok(TreeOutcome::new(early_result));
1177        }
1178
1179        // Return early if we are on the correct fork
1180        if let Some(result) = self.handle_canonical_head(state, &attrs)? {
1181            return Ok(result);
1182        }
1183
1184        // Attempt to apply a chain update when the head differs from our canonical chain.
1185        // This handles reorgs and chain extensions by making the specified head canonical.
1186        if let Some(result) = self.apply_chain_update(state, &attrs)? {
1187            return Ok(result);
1188        }
1189
1190        // Fallback that ensures to catch up to the network's state.
1191        self.handle_missing_block(state)
1192    }
1193
1194    /// Records metrics for forkchoice updated calls
1195    fn record_forkchoice_metrics(&self) {
1196        self.canonical_in_memory_state.on_forkchoice_update_received();
1197    }
1198
1199    /// Pre-validates the forkchoice state and returns early if validation fails.
1200    ///
1201    /// Returns `Some(OnForkChoiceUpdated)` if validation fails and an early response should be
1202    /// returned. Returns `None` if validation passes and processing should continue.
1203    fn validate_forkchoice_state(
1204        &mut self,
1205        state: ForkchoiceState,
1206    ) -> ProviderResult<Option<OnForkChoiceUpdated>> {
1207        if state.head_block_hash.is_zero() {
1208            return Ok(Some(OnForkChoiceUpdated::invalid_state()));
1209        }
1210
1211        // Check if the new head hash is connected to any ancestor that we previously marked as
1212        // invalid
1213        let lowest_buffered_ancestor_fcu = self.lowest_buffered_ancestor_or(state.head_block_hash);
1214        if let Some(status) = self.check_invalid_ancestor(lowest_buffered_ancestor_fcu)? {
1215            return Ok(Some(OnForkChoiceUpdated::with_invalid(status)));
1216        }
1217
1218        if !self.backfill_sync_state.is_idle() {
1219            // We can only process new forkchoice updates if the pipeline is idle, since it requires
1220            // exclusive access to the database
1221            trace!(target: "engine::tree", "Pipeline is syncing, skipping forkchoice update");
1222            return Ok(Some(OnForkChoiceUpdated::syncing()));
1223        }
1224
1225        Ok(None)
1226    }
1227
1228    /// Handles the case where the forkchoice head is already canonical.
1229    ///
1230    /// Returns `Some(TreeOutcome<OnForkChoiceUpdated>)` if the head is already canonical and
1231    /// processing is complete. Returns `None` if the head is not canonical and processing
1232    /// should continue.
1233    fn handle_canonical_head(
1234        &mut self,
1235        state: ForkchoiceState,
1236        attrs: &Option<T::PayloadAttributes>, // Changed to reference
1237    ) -> ProviderResult<Option<TreeOutcome<OnForkChoiceUpdated>>> {
1238        // Process the forkchoice update by trying to make the head block canonical
1239        //
1240        // We can only process this forkchoice update if:
1241        // - we have the `head` block
1242        // - the head block is part of a chain that is connected to the canonical chain. This
1243        //   includes reorgs.
1244        //
1245        // Performing a FCU involves:
1246        // - marking the FCU's head block as canonical
1247        // - updating in memory state to reflect the new canonical chain
1248        // - updating canonical state trackers
1249        // - emitting a canonicalization event for the new chain (including reorg)
1250        // - if we have payload attributes, delegate them to the payload service
1251
1252        if self.state.tree_state.canonical_block_hash() != state.head_block_hash {
1253            return Ok(None);
1254        }
1255
1256        trace!(target: "engine::tree", "fcu head hash is already canonical");
1257
1258        // Update the safe and finalized blocks and ensure their values are valid
1259        if let Err(outcome) = self.ensure_consistent_forkchoice_state(state) {
1260            // safe or finalized hashes are invalid
1261            return Ok(Some(TreeOutcome::new(outcome)));
1262        }
1263
1264        self.payload_validator.on_canonical_head_changed(state.head_block_hash, &self.state);
1265
1266        // Process payload attributes if the head is already canonical
1267        if let Some(attr) = attrs {
1268            let tip = self
1269                .sealed_header_by_hash(self.state.tree_state.canonical_block_hash())?
1270                .ok_or_else(|| {
1271                    // If we can't find the canonical block, then something is wrong and we need
1272                    // to return an error
1273                    ProviderError::HeaderNotFound(state.head_block_hash.into())
1274                })?;
1275            // Clone only when we actually need to process the attributes
1276            let updated = self.process_payload_attributes(attr.clone(), &tip, state);
1277            return Ok(Some(TreeOutcome::new(updated)));
1278        }
1279
1280        // The head block is already canonical
1281        Ok(Some(Self::valid_outcome(state)))
1282    }
1283
1284    /// Applies chain update for the new head block and processes payload attributes.
1285    ///
1286    /// This method handles the case where the forkchoice head differs from our current canonical
1287    /// head. It attempts to make the specified head block canonical by:
1288    /// - Checking if the head is already part of the canonical chain
1289    /// - Applying chain reorganizations (reorgs) if necessary
1290    /// - Processing payload attributes if provided
1291    /// - Returning the appropriate forkchoice update response
1292    ///
1293    /// Returns `Some(TreeOutcome<OnForkChoiceUpdated>)` if a chain update was successfully applied.
1294    /// Returns `None` if no chain update was needed or possible.
1295    fn apply_chain_update(
1296        &mut self,
1297        state: ForkchoiceState,
1298        attrs: &Option<T::PayloadAttributes>,
1299    ) -> ProviderResult<Option<TreeOutcome<OnForkChoiceUpdated>>> {
1300        // Check if the head is already part of the canonical chain
1301        if let Ok(Some(canonical_header)) = self.find_canonical_header(state.head_block_hash) {
1302            debug!(target: "engine::tree", head = canonical_header.number(), "fcu head block is already canonical");
1303
1304            // For OpStack, or if explicitly configured, the proposers are allowed to reorg their
1305            // own chain at will, so we need to always trigger a new payload job if requested.
1306            let always_trigger_payload_job = self.engine_kind.is_opstack() ||
1307                self.config.always_process_payload_attributes_on_canonical_head();
1308
1309            // A canonical ancestor below the latest known finalized block can never become the
1310            // head again, because this would reorg out the finalized block. Such a forkchoice
1311            // update exceeds the supported reorg depth and is rejected regardless of the payload
1312            // attributes:
1313            // <https://github.com/ethereum/execution-apis/blob/bf20b4083284e677db19e7f3871bd669b88354a6/src/engine/paris.md?plain=1#L221>
1314            //
1315            // The stored finalized block is used because a forkchoice update MAY carry a zero
1316            // finalized hash without clearing previously established finality.
1317            if !always_trigger_payload_job &&
1318                self.canonical_in_memory_state
1319                    .get_finalized_num_hash()
1320                    .is_some_and(|finalized| canonical_header.number() < finalized.number)
1321            {
1322                debug!(target: "engine::tree", head = canonical_header.number(), "rejecting canonical ancestor fcu below the finalized block");
1323                return Ok(Some(TreeOutcome::new(OnForkChoiceUpdated::too_deep_reorg())));
1324            }
1325
1326            // We need to effectively unwind the _canonical_ chain to the FCU's head, which is
1327            // part of the canonical chain. We need to update the latest block state to reflect
1328            // the canonical ancestor. This ensures that state providers and the transaction
1329            // pool operate with the correct chain state after forkchoice update processing, and
1330            // new payloads built on the reorg'd head will be added to the tree immediately.
1331            if always_trigger_payload_job && self.config.unwind_canonical_header() {
1332                self.update_latest_block_to_canonical_ancestor(&canonical_header)?;
1333            }
1334
1335            // A canonical ancestor at or above the latest known finalized block can become the
1336            // parent of the next block, e.g. when the CL wants to reorg out the current head.
1337            // The canonical chain remains untouched here; the block built on the ancestor
1338            // triggers the actual reorg once it is inserted via newPayload and FCU'd.
1339            if let Some(attr) = attrs {
1340                debug!(target: "engine::tree", head = canonical_header.number(), "handling payload attributes for canonical head");
1341                // Clone only when we actually need to process the attributes
1342                let updated =
1343                    self.process_payload_attributes(attr.clone(), &canonical_header, state);
1344                return Ok(Some(TreeOutcome::new(updated)));
1345            }
1346
1347            // The head block is already canonical and we're not processing payload attributes,
1348            // so we're not triggering a payload job and can return right away
1349            return Ok(Some(Self::valid_outcome(state)));
1350        }
1351
1352        // Ensure we can apply a new chain update for the head block
1353        if let Some(chain_update) = self.on_new_head(state.head_block_hash)? {
1354            let tip = chain_update.tip().clone_sealed_header();
1355            self.on_canonical_chain_update(chain_update);
1356
1357            // Update the safe and finalized blocks and ensure their values are valid
1358            if let Err(outcome) = self.ensure_consistent_forkchoice_state(state) {
1359                // safe or finalized hashes are invalid
1360                return Ok(Some(TreeOutcome::new(outcome)));
1361            }
1362
1363            if let Some(attr) = attrs {
1364                // Clone only when we actually need to process the attributes
1365                let updated = self.process_payload_attributes(attr.clone(), &tip, state);
1366                return Ok(Some(TreeOutcome::new(updated)));
1367            }
1368
1369            return Ok(Some(Self::valid_outcome(state)));
1370        }
1371
1372        Ok(None)
1373    }
1374
1375    /// Handles the case where the head block is missing and needs to be downloaded.
1376    ///
1377    /// This is the fallback case when all other forkchoice update scenarios have been exhausted.
1378    /// Returns a `TreeOutcome` with syncing status and download event.
1379    fn handle_missing_block(
1380        &self,
1381        state: ForkchoiceState,
1382    ) -> ProviderResult<TreeOutcome<OnForkChoiceUpdated>> {
1383        // We don't have the block to perform the forkchoice update
1384        // We assume the FCU is valid and at least the head is missing,
1385        // so we need to start syncing to it
1386        //
1387        // find the appropriate target to sync to, if we don't have the safe block hash then we
1388        // start syncing to the safe block via backfill first
1389        let target = if self.state.forkchoice_state_tracker.is_empty() &&
1390        // check that safe block is valid and missing
1391        !state.safe_block_hash.is_zero() &&
1392        self.find_canonical_header(state.safe_block_hash).ok().flatten().is_none()
1393        {
1394            debug!(target: "engine::tree", "missing safe block on initial FCU, downloading safe block");
1395            state.safe_block_hash
1396        } else {
1397            state.head_block_hash
1398        };
1399
1400        let target = self.lowest_buffered_ancestor_or(target);
1401        trace!(target: "engine::tree", %target, "downloading missing block");
1402
1403        Ok(TreeOutcome::new(OnForkChoiceUpdated::valid(PayloadStatus::from_status(
1404            PayloadStatusEnum::Syncing,
1405        )))
1406        .with_event(TreeEvent::Download(
1407            DownloadRequest::single_block(target)
1408                .with_access_lists(self.should_download_access_lists()),
1409        )))
1410    }
1411
1412    /// Helper method to remove blocks and set the persistence state. This ensures we keep track of
1413    /// the current persistence action while we're removing blocks.
1414    fn remove_blocks(&mut self, new_tip_num: u64) {
1415        debug!(target: "engine::tree", ?new_tip_num, last_persisted_block_number=?self.persistence_state.last_persisted_block.number, "Removing blocks using persistence task");
1416        if new_tip_num < self.persistence_state.last_persisted_block.number {
1417            debug!(target: "engine::tree", ?new_tip_num, "Starting remove blocks job");
1418            self.state.set_pending_sparse_trie_prune(false);
1419            let (tx, rx) = crossbeam_channel::bounded(1);
1420            let _ = self.persistence.remove_blocks_above(new_tip_num, tx);
1421            self.persistence_state.start_remove(new_tip_num, rx);
1422        }
1423    }
1424
1425    /// Helper method to save blocks and set the persistence state. This ensures we keep track of
1426    /// the current persistence action while we're saving blocks.
1427    fn persist_blocks(&mut self, input: SaveBlocksInput<N>) {
1428        let highest_num_hash = input.last_block();
1429        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");
1430
1431        let (tx, rx) = crossbeam_channel::bounded(1);
1432        let _ = self.persistence.save_blocks(input, tx);
1433
1434        self.persistence_state.start_save(highest_num_hash, rx);
1435    }
1436
1437    /// Triggers new persistence actions if no persistence task is currently in progress.
1438    ///
1439    /// This checks if we need to remove blocks (disk reorg) or save new blocks to disk.
1440    /// Persistence completion is handled separately via the `wait_for_event` method.
1441    fn advance_persistence(&mut self) -> Result<(), AdvancePersistenceError> {
1442        if !self.persistence_state.in_progress() {
1443            let payload_build_active = self.payload_builds.is_active();
1444            if let Some(new_tip_num) = self.find_disk_reorg()? {
1445                self.remove_blocks(new_tip_num)
1446            } else if self.backfill_sync_state.is_pending_revalidation() &&
1447                !payload_build_active &&
1448                self.persistence_state.last_state_trie_persisted_block !=
1449                    self.persistence_state.last_persisted_block
1450            {
1451                let Some(input) = self.get_save_blocks_input(PersistTarget::Persisted) else {
1452                    return Err(AdvancePersistenceError::StateTrieCatchupUnavailable)
1453                };
1454                self.persist_blocks(input);
1455            } else if self.backfill_sync_state.is_pending_revalidation() && !payload_build_active {
1456                self.revalidate_pending_backfill()?;
1457            } else if let Some(input) = self.get_save_blocks_input(PersistTarget::Threshold) {
1458                self.persist_blocks(input);
1459            }
1460        }
1461
1462        Ok(())
1463    }
1464
1465    /// Finishes termination by persisting all remaining blocks and signaling completion.
1466    ///
1467    /// This blocks until all persistence is complete. Always signals completion,
1468    /// even if an error occurs.
1469    fn finish_termination(
1470        &mut self,
1471        pending_termination: oneshot::Sender<()>,
1472    ) -> Result<(), AdvancePersistenceError> {
1473        trace!(target: "engine::tree", "finishing termination, persisting remaining blocks");
1474        let result = self.persist_until_complete();
1475        let _ = pending_termination.send(());
1476        result
1477    }
1478
1479    /// Persists all remaining blocks until none are left.
1480    fn persist_until_complete(&mut self) -> Result<(), AdvancePersistenceError> {
1481        loop {
1482            // Wait for any in-progress persistence to complete (blocking)
1483            if let Some((rx, start_time, action)) = self.persistence_state.rx.take() {
1484                debug!(target: "engine::tree", ?action, "waiting for in-flight persistence");
1485                let result = rx.recv().map_err(|_| AdvancePersistenceError::ChannelClosed)?;
1486                self.on_persistence_complete(result, start_time)?;
1487                continue
1488            }
1489
1490            // Persistence can finish against a branch that the in-memory canonical chain has
1491            // reorged away from. Unwind that stale disk branch before building a head-targeted
1492            // save.
1493            if let Some(new_tip_num) = self.find_disk_reorg()? {
1494                self.remove_blocks(new_tip_num);
1495                continue
1496            }
1497
1498            let Some(input) = self.get_save_blocks_input(PersistTarget::Head) else {
1499                debug!(target: "engine::tree", "persistence complete, signaling termination");
1500                return Ok(())
1501            };
1502
1503            debug!(target: "engine::tree", count = input.persist_rest_blocks().len(), "persisting remaining blocks before shutdown");
1504            self.persist_blocks(input);
1505        }
1506    }
1507
1508    /// Tries to poll for a completed persistence task (non-blocking).
1509    ///
1510    /// Returns `true` if a persistence task was completed, `false` otherwise.
1511    fn try_poll_persistence(&mut self) -> Result<bool, AdvancePersistenceError> {
1512        let Some((rx, start_time, action)) = self.persistence_state.rx.take() else {
1513            return Ok(false);
1514        };
1515
1516        match rx.try_recv() {
1517            Ok(result) => {
1518                self.on_persistence_complete(result, start_time)?;
1519                Ok(true)
1520            }
1521            Err(crossbeam_channel::TryRecvError::Empty) => {
1522                // Not ready yet, put it back
1523                self.persistence_state.rx = Some((rx, start_time, action));
1524                Ok(false)
1525            }
1526            Err(crossbeam_channel::TryRecvError::Disconnected) => {
1527                Err(AdvancePersistenceError::ChannelClosed)
1528            }
1529        }
1530    }
1531
1532    /// Handles a completed persistence task.
1533    fn on_persistence_complete(
1534        &mut self,
1535        result: PersistenceResult,
1536        start_time: Instant,
1537    ) -> Result<(), AdvancePersistenceError> {
1538        self.metrics.engine.persistence_duration.record(start_time.elapsed());
1539
1540        let PersistenceResult { last_block, last_state_trie_block, commit_duration } = result;
1541        debug_assert!(
1542            last_state_trie_block.number <= last_block.number,
1543            "state/trie frontier cannot exceed the last persisted block"
1544        );
1545
1546        debug!(target: "engine::tree", ?last_block, ?last_state_trie_block, elapsed=?start_time.elapsed(), "Finished persisting, calling finish");
1547        self.persistence_state.finish(last_block, last_state_trie_block);
1548
1549        let last_block_number = last_block.number;
1550
1551        // Evict cached changesets for blocks below the eviction threshold.
1552        // Keep at least CHANGESET_CACHE_RETENTION_BLOCKS from the persisted tip, and also respect
1553        // the finalized block if set.
1554        let min_threshold = last_block_number.saturating_sub(CHANGESET_CACHE_RETENTION_BLOCKS);
1555        let eviction_threshold =
1556            if let Some(finalized) = self.canonical_in_memory_state.get_finalized_num_hash() {
1557                // Use the minimum of finalized block and retention threshold to be conservative
1558                finalized.number.min(min_threshold)
1559            } else {
1560                // When finalized is not set (e.g., on L2s), use the retention threshold
1561                min_threshold
1562            };
1563        debug!(
1564            target: "engine::tree",
1565            last_persisted = last_block_number,
1566            finalized_number = ?self.canonical_in_memory_state.get_finalized_num_hash().map(|f| f.number),
1567            eviction_threshold,
1568            "Evicting changesets below threshold"
1569        );
1570        self.state.tree_state.overlay_manager.evict_cached_changesets(eviction_threshold);
1571
1572        self.on_new_persisted_block()?;
1573
1574        self.purge_timing_stats(last_block_number, commit_duration);
1575
1576        Ok(())
1577    }
1578
1579    /// Handles a message from the engine.
1580    ///
1581    /// Returns `ControlFlow::Break(())` if the engine should terminate.
1582    fn on_engine_message(
1583        &mut self,
1584        msg: FromEngine<EngineApiRequest<T, N>, N::Block>,
1585    ) -> Result<ops::ControlFlow<()>, InsertBlockFatalError> {
1586        match msg {
1587            FromEngine::Event(event) => match event {
1588                FromOrchestrator::BackfillSyncStarted => {
1589                    debug!(target: "engine::tree", "received backfill sync started event");
1590                    self.backfill_sync_state = BackfillSyncState::Active;
1591                }
1592                FromOrchestrator::BackfillSyncFinished(ctrl) => {
1593                    self.on_backfill_sync_finished(ctrl)?;
1594                }
1595                FromOrchestrator::Terminate { tx } => {
1596                    debug!(target: "engine::tree", "received terminate request");
1597                    if let Err(err) = self.finish_termination(tx) {
1598                        error!(target: "engine::tree", %err, "Termination failed");
1599                    }
1600                    return Ok(ops::ControlFlow::Break(()))
1601                }
1602            },
1603            FromEngine::Request(request) => {
1604                match request {
1605                    EngineApiRequest::InsertExecutedBlock(payload) => {
1606                        let block_num_hash = payload.recovered_block.num_hash();
1607                        if block_num_hash.number <= self.state.tree_state.canonical_block_number() {
1608                            // outdated block that can be skipped
1609                            return Ok(ops::ControlFlow::Continue(()))
1610                        }
1611
1612                        if self.state.tree_state.contains_hash(&block_num_hash.hash) {
1613                            // block already known to the tree (e.g. delivered via newPayload first)
1614                            return Ok(ops::ControlFlow::Continue(()))
1615                        }
1616
1617                        debug!(target: "engine::tree", block=?block_num_hash, "inserting already executed block");
1618                        let now = Instant::now();
1619
1620                        let block = match self.payload_validator.on_inserted_executed_block(payload)
1621                        {
1622                            Ok(block) => block,
1623                            Err(err) => {
1624                                warn!(target: "engine::tree", %err, block=?block_num_hash, "Failed to insert already executed block");
1625                                return Ok(ops::ControlFlow::Continue(()))
1626                            }
1627                        };
1628
1629                        let is_pending = self.state.tree_state.canonical_block_hash() ==
1630                            block.recovered_block().parent_hash();
1631                        self.state.tree_state.insert_executed(block.clone());
1632
1633                        if is_pending {
1634                            debug!(target: "engine::tree", pending=?block_num_hash, "updating pending block");
1635                            self.canonical_in_memory_state.set_pending_block(block.clone());
1636                        }
1637
1638                        self.metrics.engine.inserted_already_executed_blocks.increment(1);
1639                        self.emit_event(EngineApiEvent::BeaconConsensus(
1640                            ConsensusEngineEvent::CanonicalBlockAdded(block, now.elapsed()),
1641                        ));
1642                    }
1643                    EngineApiRequest::Beacon(request) => {
1644                        match request {
1645                            BeaconEngineMessage::ForkchoiceUpdated { state, payload_attrs, tx } => {
1646                                let has_attrs = payload_attrs.is_some();
1647
1648                                let start = Instant::now();
1649                                let mut output = self.on_forkchoice_updated(state, payload_attrs);
1650
1651                                if let Ok(res) = &mut output {
1652                                    // track last received forkchoice state
1653                                    self.state
1654                                        .forkchoice_state_tracker
1655                                        .set_latest(state, res.outcome.forkchoice_status());
1656
1657                                    // emit an event about the handled FCU
1658                                    self.emit_event(ConsensusEngineEvent::ForkchoiceUpdated(
1659                                        state,
1660                                        res.outcome.forkchoice_status(),
1661                                    ));
1662
1663                                    // handle the event if any
1664                                    self.on_maybe_tree_event(res.event.take())?;
1665                                }
1666
1667                                if let Err(ref err) = output {
1668                                    error!(target: "engine::tree", %err, ?state, "Error processing forkchoice update");
1669                                }
1670
1671                                self.metrics.engine.forkchoice_updated.update_response_metrics(
1672                                    start,
1673                                    &mut self.metrics.engine.new_payload.latest_finish_at,
1674                                    has_attrs,
1675                                    &output,
1676                                );
1677
1678                                if let Err(err) =
1679                                    tx.send(output.map(|o| o.outcome).map_err(Into::into))
1680                                {
1681                                    self.metrics
1682                                        .engine
1683                                        .failed_forkchoice_updated_response_deliveries
1684                                        .increment(1);
1685                                    warn!(target: "engine::tree", ?state, elapsed=?start.elapsed(), "Failed to deliver forkchoiceUpdated response, receiver dropped (request cancelled): {err:?}");
1686                                }
1687                            }
1688                            BeaconEngineMessage::NewPayload { payload, tx } => {
1689                                let start = Instant::now();
1690                                let gas_used = payload.gas_used();
1691                                let num_hash = payload.num_hash();
1692                                let mut output = self.on_new_payload(payload);
1693                                self.metrics.engine.new_payload.update_response_metrics(
1694                                    start,
1695                                    &mut self.metrics.engine.forkchoice_updated.latest_finish_at,
1696                                    &output,
1697                                    gas_used,
1698                                );
1699
1700                                let maybe_event =
1701                                    output.as_mut().ok().and_then(|out| out.event.take());
1702
1703                                // emit response
1704                                if let Err(err) =
1705                                    tx.send(output.map(|o| o.outcome).map_err(Into::into))
1706                                {
1707                                    warn!(target: "engine::tree", payload=?num_hash, elapsed=?start.elapsed(), "Failed to deliver newPayload response, receiver dropped (request cancelled): {err:?}");
1708                                    self.metrics
1709                                        .engine
1710                                        .failed_new_payload_response_deliveries
1711                                        .increment(1);
1712                                }
1713
1714                                // handle the event if any
1715                                self.on_maybe_tree_event(maybe_event)?;
1716                            }
1717                            BeaconEngineMessage::RethNewPayload {
1718                                payload,
1719                                wait_for_persistence,
1720                                wait_for_caches,
1721                                tx,
1722                                enqueued_at,
1723                            } => {
1724                                debug!(
1725                                    target: "engine::tree",
1726                                    wait_for_persistence,
1727                                    wait_for_caches,
1728                                    "Processing reth_newPayload"
1729                                );
1730
1731                                let backpressure_wait = enqueued_at.elapsed();
1732
1733                                let explicit_persistence_wait = if wait_for_persistence {
1734                                    let pending_persistence = self.persistence_state.rx.take();
1735                                    if let Some((rx, start_time, _action)) = pending_persistence {
1736                                        let (persistence_tx, persistence_rx) =
1737                                            std::sync::mpsc::channel();
1738                                        self.runtime.spawn_blocking_named(
1739                                            "wait-persist",
1740                                            move || {
1741                                                let start = Instant::now();
1742                                                let result = rx
1743                                                    .recv()
1744                                                    .expect("persistence state channel closed");
1745                                                let _ = persistence_tx.send((
1746                                                    result,
1747                                                    start_time,
1748                                                    start.elapsed(),
1749                                                ));
1750                                            },
1751                                        );
1752                                        let (result, start_time, wait_duration) = persistence_rx
1753                                            .recv()
1754                                            .expect("persistence result channel closed");
1755                                        let _ = self.on_persistence_complete(result, start_time);
1756                                        wait_duration
1757                                    } else {
1758                                        Duration::ZERO
1759                                    }
1760                                } else {
1761                                    Duration::ZERO
1762                                };
1763
1764                                let cache_wait = wait_for_caches
1765                                    .then(|| self.payload_validator.wait_for_caches());
1766
1767                                let start = Instant::now();
1768                                let gas_used = payload.gas_used();
1769                                let num_hash = payload.num_hash();
1770                                let mut output = self.on_new_payload(payload);
1771                                let latency = start.elapsed();
1772                                self.metrics.engine.new_payload.update_response_metrics(
1773                                    start,
1774                                    &mut self.metrics.engine.forkchoice_updated.latest_finish_at,
1775                                    &output,
1776                                    gas_used,
1777                                );
1778
1779                                let maybe_event =
1780                                    output.as_mut().ok().and_then(|out| out.event.take());
1781
1782                                let timings = NewPayloadTimings {
1783                                    latency,
1784                                    persistence_wait: backpressure_wait + explicit_persistence_wait,
1785                                    execution_cache_wait: cache_wait
1786                                        .map(|wait| wait.execution_cache),
1787                                    sparse_trie_wait: cache_wait.map(|wait| wait.sparse_trie),
1788                                };
1789                                if let Err(err) = tx
1790                                    .send(output.map(|o| (o.outcome, timings)).map_err(Into::into))
1791                                {
1792                                    error!(
1793                                        target: "engine::tree",
1794                                        payload=?num_hash,
1795                                        elapsed=?latency,
1796                                        "Failed to send event: {err:?}"
1797                                    );
1798                                    self.metrics
1799                                        .engine
1800                                        .failed_new_payload_response_deliveries
1801                                        .increment(1);
1802                                }
1803
1804                                self.on_maybe_tree_event(maybe_event)?;
1805                            }
1806                        }
1807                    }
1808                }
1809            }
1810            FromEngine::DownloadedBlocks(blocks) => {
1811                if let Some(event) = self.on_downloaded(blocks)? {
1812                    self.on_tree_event(event)?;
1813                }
1814            }
1815        }
1816        Ok(ops::ControlFlow::Continue(()))
1817    }
1818
1819    /// Invoked if the backfill sync has finished to target.
1820    ///
1821    /// At this point we consider the block synced to the backfill target.
1822    ///
1823    /// Checks the tracked finalized block against the block on disk and requests another backfill
1824    /// run if the distance to the tip exceeds the threshold for another backfill run.
1825    ///
1826    /// This will also do the necessary housekeeping of the tree state, this includes:
1827    ///  - removing all blocks below the backfill height
1828    ///  - resetting the canonical in-memory state
1829    ///
1830    /// In case backfill resulted in an unwind, this will clear the tree state above the unwind
1831    /// target block.
1832    fn on_backfill_sync_finished(
1833        &mut self,
1834        ctrl: ControlFlow,
1835    ) -> Result<(), InsertBlockFatalError> {
1836        debug!(target: "engine::tree", "received backfill sync finished event");
1837        self.backfill_sync_state = BackfillSyncState::Idle;
1838
1839        // Pipeline unwound, memorize the invalid block and wait for CL for next sync target.
1840        let backfill_height = if let ControlFlow::Unwind { bad_block, target } = &ctrl {
1841            warn!(target: "engine::tree", invalid_block=?bad_block, "Bad block detected in unwind");
1842            // update the `invalid_headers` cache with the new invalid header
1843            self.state.invalid_headers.insert(**bad_block);
1844
1845            // if this was an unwind then the target is the new height
1846            Some(*target)
1847        } else {
1848            // backfill height is the block number that the backfill finished at
1849            ctrl.block_number()
1850        };
1851
1852        // backfill height is the block number that the backfill finished at
1853        let Some(backfill_height) = backfill_height else { return Ok(()) };
1854
1855        // state house keeping after backfill sync
1856        // remove all executed blocks below the backfill height
1857        //
1858        // We set the `finalized_num` to `Some(backfill_height)` to ensure we remove all state
1859        // before that
1860        let Some(backfill_num_hash) = self
1861            .provider
1862            .block_hash(backfill_height)?
1863            .map(|hash| BlockNumHash { hash, number: backfill_height })
1864        else {
1865            debug!(target: "engine::tree", ?ctrl, "Backfill block not found");
1866            return Ok(())
1867        };
1868
1869        if ctrl.is_unwind() {
1870            // the node reset so we need to clear everything above that height so that backfill
1871            // height is the new canonical block.
1872            self.state.set_pending_sparse_trie_prune(false);
1873            self.state.tree_state.reset(backfill_num_hash)
1874        } else {
1875            self.state.tree_state.remove_until(
1876                backfill_num_hash,
1877                self.persistence_state.last_persisted_block.hash,
1878                Some(backfill_num_hash),
1879            );
1880        }
1881
1882        self.metrics.engine.executed_blocks.set(self.state.tree_state.block_count() as f64);
1883        self.metrics.tree.canonical_chain_height.set(backfill_height as f64);
1884
1885        // remove all buffered blocks below the backfill height
1886        self.state.buffer.remove_old_blocks(backfill_height);
1887        self.purge_timing_stats(backfill_height, None);
1888        // we remove all entries because now we're synced to the backfill target and consider this
1889        // the canonical chain
1890        self.canonical_in_memory_state.clear_state();
1891
1892        if let Ok(Some(new_head)) = self.provider.sealed_header(backfill_height) {
1893            // update the tracked chain height, after backfill sync both the canonical height and
1894            // persisted height are the same
1895            self.state.tree_state.set_canonical_head(new_head.num_hash());
1896            self.persistence_state.finish(new_head.num_hash(), new_head.num_hash());
1897
1898            // update the tracked canonical head
1899            self.canonical_in_memory_state.set_canonical_head(new_head);
1900
1901            // If the pipeline reached the head of the syncing FCU, apply its safe and finalized
1902            // blocks now that the head is canonical. An unwind must wait for a new FCU because the
1903            // supplied sync target may have been invalidated.
1904            if !ctrl.is_unwind() {
1905                self.on_canonicalized_sync_target(backfill_num_hash.hash);
1906            }
1907        }
1908
1909        // check if we need to run backfill again by comparing the most recent backfill target
1910        // height to the backfill height
1911        let Some(sync_target_state) = self.state.forkchoice_state_tracker.sync_target_state()
1912        else {
1913            return Ok(())
1914        };
1915        if !self.engine_kind.is_opstack() && sync_target_state.finalized_block_hash.is_zero() {
1916            // no finalized block, can't check distance on non-OP Stack chains
1917            return Ok(())
1918        }
1919        let target_hash = self.backfill_target_hash(sync_target_state);
1920        if target_hash.is_zero() {
1921            return Ok(())
1922        }
1923        // get the block number of the backfill target block, if we have it buffered
1924        let newest_target = self.state.buffer.block(&target_hash).map(|block| block.number());
1925
1926        // The block number that the backfill finished at - if the progress or newest target is
1927        // None then we can't check the distance anyways.
1928        //
1929        // If both are Some, we perform another distance check and return the desired backfill
1930        // target
1931        if let Some(backfill_target) =
1932            ctrl.block_number().zip(newest_target).and_then(|(progress, target_number)| {
1933                // Determines whether or not we should run backfill again, in case
1934                // the new gap is still large enough and requires running backfill again
1935                self.backfill_sync_target(progress, target_number, None)
1936            })
1937        {
1938            // request another backfill run
1939            self.emit_event(EngineApiEvent::BackfillAction(BackfillAction::Start(
1940                backfill_target.into(),
1941            )));
1942            return Ok(())
1943        };
1944
1945        // Check if there are more blocks to sync between current head and FCU target
1946        if let Some(lowest_buffered) =
1947            self.state.buffer.lowest_ancestor(&sync_target_state.head_block_hash)
1948        {
1949            let current_head_num = self.state.tree_state.current_canonical_head.number;
1950            let target_head_num = lowest_buffered.number();
1951
1952            if let Some(distance) = self.distance_from_local_tip(current_head_num, target_head_num)
1953            {
1954                // There are blocks between current head and FCU target, download them
1955                debug!(
1956                    target: "engine::tree",
1957                    %current_head_num,
1958                    %target_head_num,
1959                    %distance,
1960                    "Backfill complete, downloading remaining blocks to reach FCU target"
1961                );
1962
1963                self.emit_event(EngineApiEvent::Download(
1964                    DownloadRequest::block_range(lowest_buffered.parent_hash(), distance)
1965                        .with_access_lists(self.should_download_access_lists()),
1966                ));
1967                return Ok(());
1968            }
1969        } else {
1970            // We don't have the head block or any of its ancestors buffered. Request
1971            // a download for the head block which will then trigger further sync.
1972            debug!(
1973                target: "engine::tree",
1974                head_hash = %sync_target_state.head_block_hash,
1975                "Backfill complete but head block not buffered, requesting download"
1976            );
1977            self.emit_event(EngineApiEvent::Download(
1978                DownloadRequest::single_block(sync_target_state.head_block_hash)
1979                    .with_access_lists(self.should_download_access_lists()),
1980            ));
1981            return Ok(());
1982        }
1983
1984        // try to close the gap by executing buffered blocks that are child blocks of the new head
1985        self.try_connect_buffered_blocks(self.state.tree_state.current_canonical_head)
1986    }
1987
1988    /// Attempts to make the given target canonical.
1989    ///
1990    /// This will update the tracked canonical in memory state and do the necessary housekeeping.
1991    fn make_canonical(&mut self, target: B256) -> ProviderResult<()> {
1992        if let Some(chain_update) = self.on_new_head(target)? {
1993            self.on_canonical_chain_update(chain_update);
1994        }
1995
1996        self.on_canonicalized_sync_target(target);
1997
1998        Ok(())
1999    }
2000
2001    /// Applies the tracked forkchoice state once its sync target head becomes canonical.
2002    fn on_canonicalized_sync_target(&mut self, target: B256) {
2003        let Some(sync_target_state) = self
2004            .state
2005            .forkchoice_state_tracker
2006            .sync_target_state()
2007            .filter(|state| state.head_block_hash == target)
2008        else {
2009            return;
2010        };
2011
2012        if let Err(outcome) = self.ensure_consistent_forkchoice_state(sync_target_state) {
2013            debug!(
2014                target: "engine::tree",
2015                head = %sync_target_state.head_block_hash,
2016                safe = %sync_target_state.safe_block_hash,
2017                finalized = %sync_target_state.finalized_block_hash,
2018                ?outcome,
2019                "Canonicalized sync target head before safe/finalized could be applied"
2020            );
2021            return;
2022        }
2023
2024        self.state.forkchoice_state_tracker.promote_sync_target_to_valid(sync_target_state);
2025    }
2026
2027    /// Convenience function to handle an optional tree event.
2028    fn on_maybe_tree_event(&mut self, event: Option<TreeEvent>) -> ProviderResult<()> {
2029        if let Some(event) = event {
2030            self.on_tree_event(event)?;
2031        }
2032
2033        Ok(())
2034    }
2035
2036    /// Handles a tree event.
2037    ///
2038    /// Returns an error if a [`TreeAction::MakeCanonical`] results in a fatal error.
2039    fn on_tree_event(&mut self, event: TreeEvent) -> ProviderResult<()> {
2040        match event {
2041            TreeEvent::TreeAction(action) => match action {
2042                TreeAction::MakeCanonical { sync_target_head } => {
2043                    self.make_canonical(sync_target_head)?;
2044                }
2045            },
2046            TreeEvent::BackfillAction(action) => {
2047                self.emit_event(EngineApiEvent::BackfillAction(action));
2048            }
2049            TreeEvent::Download(action) => {
2050                self.emit_event(EngineApiEvent::Download(action));
2051            }
2052        }
2053
2054        Ok(())
2055    }
2056
2057    /// Removes timing stats for blocks at or below `below_number`.
2058    ///
2059    /// No-op when detailed block logging is disabled (no stats are recorded in that case).
2060    /// When `commit_duration` is provided and a slow block threshold is configured, checks
2061    /// each removed block against the threshold and emits a [`ConsensusEngineEvent::SlowBlock`]
2062    /// event for blocks that exceed it.
2063    fn purge_timing_stats(&mut self, below_number: u64, commit_duration: Option<Duration>) {
2064        let threshold = self.config.slow_block_threshold();
2065        let check_slow = commit_duration.is_some() && threshold.is_some();
2066
2067        // Two-pass: collect keys first because emit_event borrows &mut self.
2068        let keys_to_remove: Vec<B256> = self
2069            .execution_timing_stats
2070            .iter()
2071            .filter(|(_, stats)| stats.block_number <= below_number)
2072            .map(|(k, _)| *k)
2073            .collect();
2074
2075        for key in keys_to_remove {
2076            let stats = self.execution_timing_stats.remove(&key).expect("key just found");
2077            if check_slow {
2078                let commit_dur = commit_duration.expect("checked above");
2079                // state_read_duration is already included in execution_duration
2080                let total_duration =
2081                    stats.execution_duration + stats.state_hash_duration + commit_dur;
2082
2083                if total_duration > threshold.expect("checked above") {
2084                    self.emit_event(ConsensusEngineEvent::SlowBlock(SlowBlockInfo {
2085                        stats,
2086                        commit_duration: Some(commit_dur),
2087                        total_duration,
2088                    }));
2089                }
2090            }
2091        }
2092    }
2093
2094    /// Re-evaluates whether a deferred backfill is still required after persistence catches up.
2095    fn revalidate_pending_backfill(&mut self) -> ProviderResult<()> {
2096        debug_assert!(self.backfill_sync_state.is_pending_revalidation());
2097
2098        let sync_target_state = self.state.forkchoice_state_tracker.sync_target_state();
2099        let backfill_target = if let Some(state) = sync_target_state {
2100            let configured_target = self.backfill_target_hash(state);
2101            let target_hash =
2102                if configured_target.is_zero() { state.head_block_hash } else { configured_target };
2103            let target_number = if let Some(block) = self.state.buffer.block(&target_hash) {
2104                Some(block.number())
2105            } else {
2106                self.sealed_header_by_hash(target_hash)?.map(|header| header.number())
2107            };
2108
2109            target_number.and_then(|target_number| {
2110                self.backfill_sync_target(
2111                    self.state.tree_state.canonical_block_number(),
2112                    target_number,
2113                    None,
2114                )
2115            })
2116        } else {
2117            None
2118        };
2119
2120        if let Some(target) = backfill_target {
2121            self.dispatch_backfill_action(BackfillAction::Start(target.into()));
2122            return Ok(())
2123        }
2124
2125        self.backfill_sync_state = BackfillSyncState::Idle;
2126        debug!(target: "engine::tree", "dropping deferred backfill after re-evaluation");
2127
2128        // The target may have changed while persistence was draining. Resume the live-sync
2129        // download flow so a newer target can produce a fresh backfill decision.
2130        if let Some(state) = sync_target_state &&
2131            state.head_block_hash != self.state.tree_state.canonical_block_hash()
2132        {
2133            let target = self.lowest_buffered_ancestor_or(state.head_block_hash);
2134            self.send_event(EngineApiEvent::Download(DownloadRequest::single_block(target)));
2135        }
2136
2137        Ok(())
2138    }
2139
2140    /// Emits an outgoing event to the engine.
2141    fn emit_event(&mut self, event: impl Into<EngineApiEvent<N>>) {
2142        let event = event.into();
2143
2144        if let EngineApiEvent::BackfillAction(action) = event {
2145            debug_assert_eq!(
2146                self.backfill_sync_state,
2147                BackfillSyncState::Idle,
2148                "backfill action should only be emitted when backfill is idle"
2149            );
2150
2151            let persistence_in_progress = self.persistence_state.in_progress();
2152            let state_trie_needs_catchup = self.persistence_state.last_state_trie_persisted_block !=
2153                self.persistence_state.last_persisted_block;
2154            if self.payload_builds.is_active() ||
2155                persistence_in_progress ||
2156                state_trie_needs_catchup
2157            {
2158                // Backfill can remove the same in-memory blocks as an active payload job or a
2159                // persistence task. Enter pending mode to prevent new payload jobs and
2160                // re-evaluate the current sync target after all readers and writes drain.
2161                debug!(
2162                    target: "engine::tree",
2163                    last_persisted_block = self.persistence_state.last_persisted_block.number,
2164                    last_state_trie_persisted_block = self
2165                        .persistence_state
2166                        .last_state_trie_persisted_block
2167                        .number,
2168                    "deferring backfill until persistence and payload jobs drain"
2169                );
2170                self.backfill_sync_state = BackfillSyncState::PendingRevalidation;
2171                return
2172            }
2173
2174            self.dispatch_backfill_action(action);
2175            return
2176        }
2177
2178        self.send_event(event);
2179    }
2180
2181    /// Dispatches a validated backfill action to the orchestrator.
2182    fn dispatch_backfill_action(&mut self, action: BackfillAction) {
2183        debug_assert!(
2184            self.backfill_sync_state.is_idle() ||
2185                self.backfill_sync_state.is_pending_revalidation(),
2186            "backfill action can only be dispatched while idle or pending revalidation"
2187        );
2188        self.backfill_sync_state = BackfillSyncState::Pending;
2189        self.metrics.engine.pipeline_runs.increment(1);
2190        debug!(target: "engine::tree", "emitting backfill action event");
2191        self.send_event(EngineApiEvent::BackfillAction(action));
2192    }
2193
2194    /// Sends an event to the orchestrator.
2195    fn send_event(&self, event: EngineApiEvent<N>) {
2196        let _ = self.outgoing.send(event).inspect_err(
2197            |err| error!(target: "engine::tree", "Failed to send internal event: {err:?}"),
2198        );
2199    }
2200
2201    /// Returns the blocks and frontiers for the next persistence cycle, if one should start.
2202    ///
2203    /// Threshold persistence honors the normal scheduling gates and retains the configured
2204    /// in-memory block buffer. Persisted-target persistence catches the state/trie frontier up to
2205    /// the existing database tip. Head persistence bypasses those gates during shutdown and
2206    /// returns `None` once both persistence frontiers have reached the canonical head.
2207    fn get_save_blocks_input(&self, target: PersistTarget) -> Option<SaveBlocksInput<N>> {
2208        // We will calculate the state root using the database, so we need to be sure there are no
2209        // changes
2210        debug_assert!(!self.persistence_state.in_progress());
2211
2212        let prev_partial_state_trie = self.persistence_state.last_state_trie_persisted_block.number;
2213        let prev_db_tip = self.persistence_state.last_persisted_block.number;
2214        let canonical_head_number = self.state.tree_state.canonical_block_number();
2215
2216        let (new_db_tip, new_partial_state_trie) = match target {
2217            PersistTarget::Head => (canonical_head_number, canonical_head_number),
2218            PersistTarget::Persisted => {
2219                // Catch-up persistence is the transition into pipeline sync, so it deliberately
2220                // runs while backfill is pending and bypasses the normal threshold gates.
2221                debug_assert!(self.backfill_sync_state.is_pending_revalidation());
2222                debug_assert!(!self.payload_builds.is_active());
2223                (prev_db_tip, prev_db_tip)
2224            }
2225            PersistTarget::Threshold => {
2226                if (self.config.suppress_persistence_during_build() &&
2227                    self.payload_builds.is_active()) ||
2228                    !self.backfill_sync_state.is_idle()
2229                {
2230                    return None
2231                }
2232
2233                let persistence_threshold =
2234                    usize::try_from(self.config.persistence_threshold()).unwrap_or(usize::MAX);
2235                if self.canonical_in_memory_state.canonical_chain().count() <= persistence_threshold
2236                {
2237                    return None
2238                }
2239
2240                let new_db_tip =
2241                    canonical_head_number.saturating_sub(self.config.memory_block_buffer_target());
2242                if new_db_tip <= prev_db_tip {
2243                    return None
2244                }
2245
2246                let new_partial_state_trie = new_db_tip
2247                    .saturating_sub(self.config.num_state_masking_blocks())
2248                    .max(prev_partial_state_trie);
2249                (new_db_tip, new_partial_state_trie)
2250            }
2251        };
2252
2253        debug_assert!(
2254            new_db_tip >= prev_db_tip,
2255            "disk reorg must be resolved before saving blocks"
2256        );
2257        debug_assert!(
2258            new_partial_state_trie >= prev_partial_state_trie,
2259            "disk reorg must be resolved before saving state/trie"
2260        );
2261
2262        if new_db_tip == prev_db_tip && new_partial_state_trie == prev_partial_state_trie {
2263            return None
2264        }
2265
2266        let mut blocks = Vec::new();
2267        let mut current_hash = self.state.tree_state.canonical_block_hash();
2268
2269        debug!(
2270            target: "engine::tree",
2271            ?current_hash,
2272            ?prev_partial_state_trie,
2273            ?prev_db_tip,
2274            ?canonical_head_number,
2275            ?new_partial_state_trie,
2276            ?new_db_tip,
2277            target = ?target,
2278            "Returning save input"
2279        );
2280        while let Some(block) = self.state.tree_state.blocks_by_hash.get(&current_hash) {
2281            if block.recovered_block().number() <= prev_partial_state_trie {
2282                break;
2283            }
2284
2285            if block.recovered_block().number() <= new_db_tip {
2286                blocks.push(block.clone());
2287            }
2288
2289            current_hash = block.recovered_block().parent_hash();
2290        }
2291
2292        // Reverse the order so that the oldest block comes first
2293        blocks.reverse();
2294
2295        Some(SaveBlocksInput::new(
2296            blocks,
2297            prev_db_tip,
2298            prev_partial_state_trie,
2299            new_db_tip,
2300            new_partial_state_trie,
2301        ))
2302    }
2303
2304    /// This clears the blocks from the in-memory tree state that have been persisted to the
2305    /// database.
2306    ///
2307    /// This also updates the canonical in-memory state to reflect the newest persisted block
2308    /// height.
2309    ///
2310    /// Assumes that `finish` has been called on the `persistence_state` at least once
2311    fn on_new_persisted_block(&mut self) -> ProviderResult<()> {
2312        let in_memory_persisted_block = self.persistence_state.last_state_trie_persisted_block;
2313
2314        // If we have an on-disk reorg, we need to handle it first before touching the in-memory
2315        // state.
2316        if let Some(remove_above) = self.find_disk_reorg()? {
2317            self.remove_blocks(remove_above);
2318            return Ok(())
2319        }
2320
2321        let finalized = self.state.forkchoice_state_tracker.last_valid_finalized();
2322        self.remove_before(in_memory_persisted_block, finalized)?;
2323        self.canonical_in_memory_state.remove_persisted_blocks_until(
2324            self.persistence_state.last_persisted_block,
2325            in_memory_persisted_block.number,
2326        );
2327        self.state.set_pending_sparse_trie_prune(self.should_prune_sparse_trie());
2328        Ok(())
2329    }
2330
2331    /// Returns whether sparse trie pruning should be attempted by the next sparse trie task.
2332    const fn should_prune_sparse_trie(&self) -> bool {
2333        self.config.use_state_root_task()
2334    }
2335
2336    /// Return an [`ExecutedBlock`] from database or in-memory state by hash.
2337    ///
2338    /// Note: This function attempts to fetch the `ExecutedBlock` from either in-memory state
2339    /// or the database. If the required historical data (such as trie change sets) has been
2340    /// pruned for a given block, this operation will return an error. On archive nodes, it
2341    /// can retrieve any block.
2342    #[instrument(level = "debug", target = "engine::tree", skip(self))]
2343    fn canonical_block_by_hash(&self, hash: B256) -> ProviderResult<ExecutedBlock<N>> {
2344        trace!(target: "engine::tree", ?hash, "Fetching executed block by hash");
2345        // check memory first
2346        if let Some(block) = self.state.tree_state.executed_block_by_hash(hash) {
2347            return Ok(block.clone())
2348        }
2349
2350        let (block, senders) = self
2351            .provider
2352            .sealed_block_with_senders(hash.into(), TransactionVariant::WithHash)?
2353            .ok_or_else(|| ProviderError::HeaderNotFound(hash.into()))?
2354            .split_sealed();
2355        let mut execution_output = self
2356            .provider
2357            .get_state(block.header().number())?
2358            .ok_or_else(|| ProviderError::StateForNumberNotFound(block.header().number()))?;
2359        let bundle_state = execution_output.state();
2360        // `get_state` can return an in-memory execution outcome that retains destruction statuses.
2361        // Hashing it requires the parent provider to expand a pre-existing destroyed account's
2362        // storage into zero-valued slots.
2363        let hashed_state = self
2364            .provider
2365            .state_by_block_hash(block.parent_hash())?
2366            .hashed_post_state(bundle_state)?;
2367
2368        debug!(
2369            target: "engine::tree",
2370            number = ?block.number(),
2371            "computing block trie updates",
2372        );
2373        let db_provider = self.provider.database_provider_ro()?;
2374        let trie_updates = self
2375            .state
2376            .tree_state
2377            .overlay_manager
2378            .compute_block_trie_updates(&db_provider, block.number())?;
2379
2380        let sorted_hashed_state = Arc::new(hashed_state.into_sorted());
2381        let sorted_trie_updates = Arc::new(trie_updates);
2382        let trie_data = ComputedTrieData::new(sorted_hashed_state, sorted_trie_updates);
2383
2384        let execution_output = Arc::new(BlockExecutionOutput {
2385            state: execution_output.bundle,
2386            result: BlockExecutionResult {
2387                receipts: execution_output.receipts.pop().unwrap_or_default(),
2388                requests: execution_output.requests.pop().unwrap_or_default(),
2389                gas_used: block.gas_used(),
2390                blob_gas_used: block.blob_gas_used().unwrap_or_default(),
2391            },
2392        });
2393
2394        Ok(ExecutedBlock::new(
2395            Arc::new(RecoveredBlock::new_sealed(block, senders)),
2396            execution_output,
2397            trie_data,
2398        ))
2399    }
2400
2401    /// Returns `true` if a block with the given hash is known, either in memory or in the
2402    /// database. This is a lightweight existence check that avoids constructing a full
2403    /// [`SealedHeader`].
2404    fn has_block_by_hash(&self, hash: B256) -> ProviderResult<bool> {
2405        if self.state.tree_state.contains_hash(&hash) {
2406            Ok(true)
2407        } else {
2408            self.provider.is_known(hash)
2409        }
2410    }
2411
2412    /// Return sealed block header from in-memory state or database by hash.
2413    fn sealed_header_by_hash(
2414        &self,
2415        hash: B256,
2416    ) -> ProviderResult<Option<SealedHeader<N::BlockHeader>>> {
2417        // check memory first
2418        let header = self.state.tree_state.sealed_header_by_hash(&hash);
2419
2420        if header.is_some() {
2421            Ok(header)
2422        } else {
2423            self.provider.sealed_header_by_hash(hash)
2424        }
2425    }
2426
2427    /// Return the parent hash of the lowest buffered ancestor for the requested block, if there
2428    /// are any buffered ancestors. If there are no buffered ancestors, and the block itself does
2429    /// not exist in the buffer, this returns the hash that is passed in.
2430    ///
2431    /// Returns the parent hash of the block itself if the block is buffered and has no other
2432    /// buffered ancestors.
2433    fn lowest_buffered_ancestor_or(&self, hash: B256) -> B256 {
2434        self.state
2435            .buffer
2436            .lowest_ancestor(&hash)
2437            .map(|block| block.parent_hash())
2438            .unwrap_or_else(|| hash)
2439    }
2440
2441    /// Returns whether block downloads should also attempt to fetch the blocks' access lists.
2442    ///
2443    /// This is the case once Amsterdam is active at the current canonical head, indicated by the
2444    /// head header carrying a block access list hash.
2445    ///
2446    /// This is a coarse gate: a download issued while the head is still pre-Amsterdam won't ask
2447    /// for access lists of blocks past the fork. Access list downloads are best-effort anyway, so
2448    /// those blocks simply take the regular execution path.
2449    fn should_download_access_lists(&self) -> bool {
2450        self.canonical_in_memory_state.get_canonical_head().block_access_list_hash().is_some()
2451    }
2452
2453    /// If validation fails, the response MUST contain the latest valid hash:
2454    ///
2455    ///   - The block hash of the ancestor of the invalid payload satisfying the following two
2456    ///     conditions:
2457    ///     - It is fully validated and deemed VALID
2458    ///     - Any other ancestor of the invalid payload with a higher blockNumber is INVALID
2459    ///   - 0x0000000000000000000000000000000000000000000000000000000000000000 if the above
2460    ///     conditions are satisfied by a `PoW` block.
2461    ///   - null if client software cannot determine the ancestor of the invalid payload satisfying
2462    ///     the above conditions.
2463    fn latest_valid_hash_for_invalid_payload(
2464        &mut self,
2465        parent_hash: B256,
2466    ) -> ProviderResult<Option<B256>> {
2467        // Check if parent exists in side chain or in canonical chain.
2468        if self.has_block_by_hash(parent_hash)? {
2469            return Ok(Some(parent_hash))
2470        }
2471
2472        // iterate over ancestors in the invalid cache
2473        // until we encounter the first valid ancestor
2474        let mut current_hash = parent_hash;
2475        let mut current_block = self.state.invalid_headers.get(&current_hash);
2476        while let Some(block_with_parent) = current_block {
2477            current_hash = block_with_parent.parent;
2478            current_block = self.state.invalid_headers.get(&current_hash);
2479
2480            // If current_header is None, then the current_hash does not have an invalid
2481            // ancestor in the cache, check its presence in blockchain tree
2482            if current_block.is_none() && self.has_block_by_hash(current_hash)? {
2483                return Ok(Some(current_hash))
2484            }
2485        }
2486        Ok(None)
2487    }
2488
2489    /// Prepares the invalid payload response for the given hash, checking the
2490    /// database for the parent hash and populating the payload status with the latest valid hash
2491    /// according to the engine api spec.
2492    fn prepare_invalid_response(&mut self, parent_hash: B256) -> ProviderResult<PayloadStatus> {
2493        let valid_parent_hash = match self.sealed_header_by_hash(parent_hash)? {
2494            // Edge case: the `latestValid` field is the zero hash if the parent block is the
2495            // terminal PoW block, which we need to identify by looking at the parent's block
2496            // difficulty
2497            Some(parent) if !parent.difficulty().is_zero() => Some(B256::ZERO),
2498            Some(_) => Some(parent_hash),
2499            None => self.latest_valid_hash_for_invalid_payload(parent_hash)?,
2500        };
2501
2502        Ok(PayloadStatus::from_status(PayloadStatusEnum::Invalid {
2503            validation_error: PayloadValidationError::LinksToRejectedPayload.to_string(),
2504        })
2505        .with_latest_valid_hash(valid_parent_hash.unwrap_or_default()))
2506    }
2507
2508    /// Returns true if the given hash is the last received sync target block.
2509    ///
2510    /// See [`ForkchoiceStateTracker::sync_target_state`]
2511    fn is_sync_target_head(&self, block_hash: B256) -> bool {
2512        if let Some(target) = self.state.forkchoice_state_tracker.sync_target_state() {
2513            return target.head_block_hash == block_hash
2514        }
2515        false
2516    }
2517
2518    /// Returns true if the given hash is part of the last received sync target fork choice update.
2519    ///
2520    /// See [`ForkchoiceStateTracker::sync_target_state`]
2521    fn is_any_sync_target(&self, block_hash: B256) -> bool {
2522        if let Some(target) = self.state.forkchoice_state_tracker.sync_target_state() {
2523            return target.contains(block_hash)
2524        }
2525        false
2526    }
2527
2528    /// Checks if the given `check` hash points to an invalid header, inserting the given `head`
2529    /// block into the invalid header cache if the `check` hash has a known invalid ancestor.
2530    ///
2531    /// Returns a payload status response according to the engine API spec if the block is known to
2532    /// be invalid.
2533    fn check_invalid_ancestor_with_head(
2534        &mut self,
2535        check: B256,
2536        head: &SealedBlock<N::Block>,
2537    ) -> ProviderResult<Option<PayloadStatus>> {
2538        // check if the check hash was previously marked as invalid
2539        let Some(header) = self.state.invalid_headers.get(&check) else { return Ok(None) };
2540
2541        Ok(Some(self.on_invalid_new_payload(head.clone(), header)?))
2542    }
2543
2544    /// Invoked when a new payload received is invalid.
2545    fn on_invalid_new_payload(
2546        &mut self,
2547        head: SealedBlock<N::Block>,
2548        invalid: BlockWithParent,
2549    ) -> ProviderResult<PayloadStatus> {
2550        // populate the latest valid hash field
2551        let status = self.prepare_invalid_response(invalid.parent)?;
2552
2553        // insert the head block into the invalid header cache
2554        self.state.invalid_headers.insert_with_invalid_ancestor(head.hash(), invalid);
2555        self.emit_event(ConsensusEngineEvent::InvalidBlock {
2556            block: Box::new(head),
2557            error: PayloadValidationError::LinksToRejectedPayload.to_string(),
2558        });
2559
2560        Ok(status)
2561    }
2562
2563    /// Finds any invalid ancestor for the given payload.
2564    ///
2565    /// This function first checks if the block itself is in the invalid headers cache (to
2566    /// avoid re-executing a known-invalid block). Then it walks up the chain of buffered
2567    /// ancestors and checks if any ancestor is marked as invalid.
2568    ///
2569    /// The check works by:
2570    /// 1. Checking if the block hash itself is in the `invalid_headers` map
2571    /// 2. Finding the lowest buffered ancestor for the given block hash
2572    /// 3. If the ancestor is the same as the block hash itself, using the parent hash instead
2573    /// 4. Checking if this ancestor is in the `invalid_headers` map
2574    ///
2575    /// Returns the invalid ancestor block info if found, or None if no invalid ancestor exists.
2576    fn find_invalid_ancestor(&mut self, payload: &T::ExecutionData) -> Option<BlockWithParent> {
2577        let parent_hash = payload.parent_hash();
2578        let block_hash = payload.block_hash();
2579
2580        // Check if the block itself is already known to be invalid, avoiding re-execution
2581        if let Some(entry) = self.state.invalid_headers.get(&block_hash) {
2582            return Some(entry);
2583        }
2584
2585        let mut lowest_buffered_ancestor = self.lowest_buffered_ancestor_or(block_hash);
2586        if lowest_buffered_ancestor == block_hash {
2587            lowest_buffered_ancestor = parent_hash;
2588        }
2589
2590        // Check if the block has an invalid ancestor
2591        self.state.invalid_headers.get(&lowest_buffered_ancestor)
2592    }
2593
2594    /// Handles a payload that has an invalid ancestor.
2595    ///
2596    /// This function validates the payload and processes it according to whether it's
2597    /// well-formed or malformed:
2598    /// 1. **Well-formed payload**: The payload is marked as invalid since it descends from a
2599    ///    known-bad block, which violates consensus rules
2600    /// 2. **Malformed payload**: Returns an appropriate error status since the payload cannot be
2601    ///    validated due to its own structural issues
2602    fn handle_invalid_ancestor_payload(
2603        &mut self,
2604        payload: T::ExecutionData,
2605        invalid: BlockWithParent,
2606    ) -> Result<PayloadStatus, InsertBlockFatalError> {
2607        let parent_hash = payload.parent_hash();
2608        let num_hash = payload.num_hash();
2609
2610        // Here we might have 2 cases
2611        // 1. the block is well formed and indeed links to an invalid header, meaning we should
2612        //    remember it as invalid
2613        // 2. the block is not well formed (i.e block hash is incorrect), and we should just return
2614        //    an error and forget it
2615        let block = match self.payload_validator.convert_payload_to_block(payload) {
2616            Ok(block) => block,
2617            Err(error) => return Ok(self.on_new_payload_error(error, num_hash, parent_hash)?),
2618        };
2619
2620        Ok(self.on_invalid_new_payload(block, invalid)?)
2621    }
2622
2623    /// Checks if the given `head` points to an invalid header, which requires a specific response
2624    /// to a forkchoice update.
2625    fn check_invalid_ancestor(&mut self, head: B256) -> ProviderResult<Option<PayloadStatus>> {
2626        // check if the head was previously marked as invalid
2627        let Some(header) = self.state.invalid_headers.get(&head) else { return Ok(None) };
2628
2629        // Try to prepare invalid response, but handle errors gracefully
2630        match self.prepare_invalid_response(header.parent) {
2631            Ok(status) => Ok(Some(status)),
2632            Err(err) => {
2633                debug!(target: "engine::tree", %err, "Failed to prepare invalid response for ancestor check");
2634                // Return a basic invalid status without latest valid hash
2635                Ok(Some(PayloadStatus::from_status(PayloadStatusEnum::Invalid {
2636                    validation_error: PayloadValidationError::LinksToRejectedPayload.to_string(),
2637                })))
2638            }
2639        }
2640    }
2641
2642    /// Validate if block is correct and satisfies all the consensus rules that concern the header
2643    /// and block body itself.
2644    fn validate_block(&self, block: &SealedBlock<N::Block>) -> Result<(), ConsensusError> {
2645        if let Err(e) = self.consensus.validate_header(block.sealed_header()) {
2646            error!(target: "engine::tree", ?block, "Failed to validate header {}: {e}", block.hash());
2647            return Err(e)
2648        }
2649
2650        if let Err(e) = self.consensus.validate_block_pre_execution(block) {
2651            error!(target: "engine::tree", ?block, "Failed to validate block {}: {e}", block.hash());
2652            return Err(e)
2653        }
2654
2655        Ok(())
2656    }
2657
2658    /// Attempts to connect any buffered blocks that are connected to the given parent hash.
2659    #[instrument(level = "debug", target = "engine::tree", skip(self))]
2660    fn try_connect_buffered_blocks(
2661        &mut self,
2662        parent: BlockNumHash,
2663    ) -> Result<(), InsertBlockFatalError> {
2664        let blocks = self.state.buffer.remove_block_with_children(&parent.hash);
2665
2666        if blocks.is_empty() {
2667            // nothing to append
2668            return Ok(())
2669        }
2670
2671        let now = Instant::now();
2672        let block_count = blocks.len();
2673        for child in blocks {
2674            let child_num_hash = child.num_hash();
2675            match self.insert_block(child) {
2676                Ok(res) => {
2677                    debug!(target: "engine::tree", child =?child_num_hash, ?res, "connected buffered block");
2678                    if self.is_any_sync_target(child_num_hash.hash) &&
2679                        matches!(res, InsertPayloadOk::Inserted(BlockStatus::Valid))
2680                    {
2681                        debug!(target: "engine::tree", child =?child_num_hash, "connected sync target block");
2682                        // we just inserted a block that we know is part of the canonical chain, so
2683                        // we can make it canonical
2684                        self.make_canonical(child_num_hash.hash)?;
2685                    }
2686                }
2687                Err(err) => {
2688                    if let InsertPayloadError::Block(err) = err {
2689                        debug!(target: "engine::tree", ?err, "failed to connect buffered block to tree");
2690                        if let Err(InsertBlockProcessingError::Fatal(fatal)) =
2691                            self.on_insert_block_error(err)
2692                        {
2693                            warn!(target: "engine::tree", %fatal, "fatal error occurred while connecting buffered blocks");
2694                        }
2695                    }
2696                }
2697            }
2698        }
2699
2700        debug!(target: "engine::tree", elapsed = ?now.elapsed(), %block_count, "connected buffered blocks");
2701        Ok(())
2702    }
2703
2704    /// Pre-validates the block and inserts it into the buffer.
2705    fn buffer_block(
2706        &mut self,
2707        block: SealedBlock<N::Block>,
2708    ) -> Result<(), InsertBlockError<N::Block>> {
2709        if let Err(err) = self.validate_block(&block) {
2710            return Err(InsertBlockError::consensus_error(err, block))
2711        }
2712        self.state.buffer.insert_block(block.into());
2713        Ok(())
2714    }
2715
2716    /// Returns true if the distance from the local tip to the block is greater than the configured
2717    /// threshold.
2718    ///
2719    /// If the `local_tip` is greater than the `block`, then this will return false.
2720    #[inline]
2721    const fn exceeds_backfill_run_threshold(&self, local_tip: u64, block: u64) -> bool {
2722        block > local_tip && block - local_tip > MIN_BLOCKS_FOR_PIPELINE_RUN
2723    }
2724
2725    /// Returns how far the local tip is from the given block. If the local tip is at the same
2726    /// height or its block number is greater than the given block, this returns None.
2727    #[inline]
2728    const fn distance_from_local_tip(&self, local_tip: u64, block: u64) -> Option<u64> {
2729        if block > local_tip {
2730            Some(block - local_tip)
2731        } else {
2732            None
2733        }
2734    }
2735
2736    /// Returns the block hash that backfill should target.
2737    ///
2738    /// Defaults to the finalized block hash. On OP Stack, the CL finalizes in large batches and the
2739    /// finalized hash can lag the canonical tip by a wide margin, so backfill targets the head.
2740    ///
2741    /// The zero-finalized optimistic-sync fallback for non-OP Stack chains is handled by
2742    /// [`Self::backfill_sync_target`].
2743    const fn backfill_target_hash(&self, state: ForkchoiceState) -> B256 {
2744        if self.engine_kind.is_opstack() {
2745            state.head_block_hash
2746        } else {
2747            state.finalized_block_hash
2748        }
2749    }
2750
2751    /// Returns the target hash to sync to if the distance from the local tip is greater than the
2752    /// threshold and we're not yet synced to the backfill target (see
2753    /// [`Self::backfill_target_hash`]).
2754    ///
2755    /// If this is invoked after a new block has been downloaded, the downloaded block could be
2756    /// the (missing) target block.
2757    fn backfill_sync_target(
2758        &self,
2759        canonical_tip_num: u64,
2760        target_block_number: u64,
2761        downloaded_block: Option<BlockNumHash>,
2762    ) -> Option<B256> {
2763        let state = self.state.forkchoice_state_tracker.sync_target_state()?;
2764        let target_hash = self.backfill_target_hash(state);
2765
2766        // check if the downloaded block is the tracked backfill target
2767        let exceeds_backfill_threshold = match downloaded_block.as_ref() {
2768            // if we downloaded the target block we can now check how far we're off
2769            Some(downloaded_block) if downloaded_block.hash == target_hash => {
2770                self.exceeds_backfill_run_threshold(canonical_tip_num, downloaded_block.number)
2771            }
2772            _ => match self.state.buffer.block(&target_hash) {
2773                // if we have buffered the target block, we should check how far we're off
2774                Some(buffered_target) => {
2775                    self.exceeds_backfill_run_threshold(canonical_tip_num, buffered_target.number())
2776                }
2777                // check if the distance exceeds the threshold for backfill sync
2778                None => self.exceeds_backfill_run_threshold(canonical_tip_num, target_block_number),
2779            },
2780        };
2781
2782        if !exceeds_backfill_threshold {
2783            return None
2784        }
2785
2786        // if we have already canonicalized the target block, we should skip backfill
2787        match self.provider.header_by_hash_or_number(target_hash.into()) {
2788            Err(err) => {
2789                warn!(target: "engine::tree", %err, "Failed to get backfill target block header");
2790                None
2791            }
2792            // we don't have the block yet and the distance exceeds the allowed threshold
2793            Ok(None) if !target_hash.is_zero() => Some(target_hash),
2794            Ok(None) => {
2795                // OPTIMISTIC SYNCING
2796                //
2797                // It can happen when the node is doing an
2798                // optimistic sync, where the CL has no knowledge of the finalized hash,
2799                // but is expecting the EL to sync as high
2800                // as possible before finalizing.
2801                //
2802                // This usually doesn't happen on ETH mainnet since CLs use the more
2803                // secure checkpoint syncing.
2804                //
2805                // However, optimism chains will do this. The risk of a reorg is however
2806                // low.
2807                debug!(target: "engine::tree", hash=?state.head_block_hash, "Setting head hash as an optimistic backfill target.");
2808                Some(state.head_block_hash)
2809            }
2810            // we're fully synced to the target block
2811            Ok(Some(_)) => None,
2812        }
2813    }
2814
2815    /// This method tries to detect whether on-disk and in-memory states have diverged. It might
2816    /// happen if a reorg is happening while we are persisting a block.
2817    fn find_disk_reorg(&self) -> ProviderResult<Option<u64>> {
2818        let mut canonical = self.state.tree_state.current_canonical_head;
2819        let mut persisted = self.persistence_state.last_persisted_block;
2820
2821        let parent_num_hash = |num_hash: NumHash| -> ProviderResult<NumHash> {
2822            Ok(self
2823                .sealed_header_by_hash(num_hash.hash)?
2824                .ok_or(ProviderError::BlockHashNotFound(num_hash.hash))?
2825                .parent_num_hash())
2826        };
2827
2828        // Happy path, canonical chain is ahead or equal to persisted chain.
2829        // Walk canonical chain back to make sure that it connects to persisted chain.
2830        while canonical.number > persisted.number {
2831            canonical = parent_num_hash(canonical)?;
2832        }
2833
2834        // If we've reached persisted tip by walking the canonical chain back, everything is fine.
2835        if canonical == persisted {
2836            return Ok(None);
2837        }
2838
2839        // At this point, we know that `persisted` block can't be reached by walking the canonical
2840        // chain back. In this case we need to truncate it to the first canonical block it connects
2841        // to.
2842
2843        // Firstly, walk back until we reach the same height as `canonical`.
2844        while persisted.number > canonical.number {
2845            persisted = parent_num_hash(persisted)?;
2846        }
2847
2848        debug_assert_eq!(persisted.number, canonical.number);
2849
2850        // Now walk both chains back until we find a common ancestor.
2851        while persisted.hash != canonical.hash {
2852            canonical = parent_num_hash(canonical)?;
2853            persisted = parent_num_hash(persisted)?;
2854        }
2855
2856        debug!(target: "engine::tree", remove_above=persisted.number, "on-disk reorg detected");
2857
2858        Ok(Some(persisted.number))
2859    }
2860
2861    /// Invoked when we the canonical chain has been updated.
2862    ///
2863    /// This is invoked on a valid forkchoice update, or if we can make the target block canonical.
2864    fn on_canonical_chain_update(&mut self, chain_update: NewCanonicalChain<N>) {
2865        trace!(target: "engine::tree", new_blocks = %chain_update.new_block_count(), reorged_blocks =  %chain_update.reorged_block_count(), "applying new chain update");
2866        let start = Instant::now();
2867
2868        // update the tracked canonical head
2869        self.state.tree_state.set_canonical_head(chain_update.tip().num_hash());
2870
2871        let tip = chain_update.tip().clone_sealed_header();
2872        let notification = chain_update.to_chain_notification();
2873
2874        // reinsert any missing reorged blocks
2875        if let NewCanonicalChain::Reorg { new, old } = &chain_update {
2876            let new_first = new.first().map(|first| first.recovered_block().num_hash());
2877            let old_first = old.first().map(|first| first.recovered_block().num_hash());
2878            trace!(target: "engine::tree", ?new_first, ?old_first, "Reorg detected, new and old first blocks");
2879
2880            self.state.set_pending_sparse_trie_prune(false);
2881            self.update_reorg_metrics(old.len(), old_first);
2882            self.reinsert_reorged_blocks(new.clone());
2883            self.reinsert_reorged_blocks(old.clone());
2884        }
2885
2886        // update the tracked in-memory state with the new chain
2887        self.canonical_in_memory_state.update_chain(chain_update);
2888        self.canonical_in_memory_state.set_canonical_head(tip.clone());
2889        self.payload_validator.on_canonical_head_changed(tip.hash(), &self.state);
2890
2891        // Update metrics based on new tip
2892        self.metrics.tree.canonical_chain_height.set(tip.number() as f64);
2893
2894        // sends an event to all active listeners about the new canonical chain
2895        self.canonical_in_memory_state.notify_canon_state(notification);
2896
2897        // emit event
2898        self.emit_event(ConsensusEngineEvent::CanonicalChainCommitted(
2899            Box::new(tip),
2900            start.elapsed(),
2901        ));
2902    }
2903
2904    /// This updates metrics based on the given reorg length and first reorged block number.
2905    fn update_reorg_metrics(&self, old_chain_length: usize, first_reorged_block: Option<NumHash>) {
2906        if let Some(first_reorged_block) = first_reorged_block.map(|block| block.number) {
2907            if let Some(finalized) = self.canonical_in_memory_state.get_finalized_num_hash() &&
2908                first_reorged_block <= finalized.number
2909            {
2910                self.metrics.tree.reorgs.finalized.increment(1);
2911            } else if let Some(safe) = self.canonical_in_memory_state.get_safe_num_hash() &&
2912                first_reorged_block <= safe.number
2913            {
2914                self.metrics.tree.reorgs.safe.increment(1);
2915            } else {
2916                self.metrics.tree.reorgs.head.increment(1);
2917            }
2918        } else {
2919            debug_unreachable!("Reorged chain doesn't have any blocks");
2920        }
2921        self.metrics.tree.latest_reorg_depth.set(old_chain_length as f64);
2922    }
2923
2924    /// This reinserts any blocks in the new chain that do not already exist in the tree
2925    fn reinsert_reorged_blocks(&mut self, new_chain: Vec<ExecutedBlock<N>>) {
2926        for block in new_chain {
2927            if self
2928                .state
2929                .tree_state
2930                .executed_block_by_hash(block.recovered_block().hash())
2931                .is_none()
2932            {
2933                trace!(target: "engine::tree", num=?block.recovered_block().number(), hash=?block.recovered_block().hash(), "Reinserting block into tree state");
2934                self.state.tree_state.insert_executed(block);
2935            }
2936        }
2937    }
2938
2939    /// This handles downloaded blocks that are shown to be disconnected from the canonical chain.
2940    ///
2941    /// This mainly compares the missing parent of the downloaded block with the current canonical
2942    /// tip, and decides whether or not backfill sync should be triggered.
2943    fn on_disconnected_downloaded_block(
2944        &self,
2945        downloaded_block: BlockNumHash,
2946        missing_parent: BlockNumHash,
2947        head: BlockNumHash,
2948    ) -> Option<TreeEvent> {
2949        // compare the missing parent with the canonical tip
2950        if let Some(target) =
2951            self.backfill_sync_target(head.number, missing_parent.number, Some(downloaded_block))
2952        {
2953            trace!(target: "engine::tree", %target, "triggering backfill on downloaded block");
2954            return Some(TreeEvent::BackfillAction(BackfillAction::Start(target.into())));
2955        }
2956
2957        // continue downloading the missing parent
2958        //
2959        // this happens if either:
2960        //  * the missing parent block num < canonical tip num
2961        //    * this case represents a missing block on a fork that is shorter than the canonical
2962        //      chain
2963        //  * the missing parent block num >= canonical tip num, but the number of missing blocks is
2964        //    less than the backfill threshold
2965        //    * this case represents a potentially long range of blocks to download and execute
2966        let request = if let Some(distance) =
2967            self.distance_from_local_tip(head.number, missing_parent.number)
2968        {
2969            trace!(target: "engine::tree", %distance, missing=?missing_parent, "downloading missing parent block range");
2970            DownloadRequest::block_range(missing_parent.hash, distance)
2971        } else {
2972            trace!(target: "engine::tree", missing=?missing_parent, "downloading missing parent block");
2973            // This happens when the missing parent is on an outdated
2974            // sidechain and we can only download the missing block itself
2975            DownloadRequest::single_block(missing_parent.hash)
2976        };
2977
2978        Some(TreeEvent::Download(request.with_access_lists(self.should_download_access_lists())))
2979    }
2980
2981    /// Handles a downloaded block that was successfully inserted as valid.
2982    ///
2983    /// If the block matches the sync target head, returns [`TreeAction::MakeCanonical`].
2984    /// If it matches a non-head sync target (safe or finalized), makes it canonical inline
2985    /// and triggers a download for the remaining blocks towards the actual head.
2986    /// Otherwise, tries to connect buffered blocks.
2987    fn on_valid_downloaded_block(
2988        &mut self,
2989        block_num_hash: BlockNumHash,
2990    ) -> Result<Option<TreeEvent>, InsertBlockFatalError> {
2991        // check if we just inserted a block that's part of sync targets,
2992        // i.e. head, safe, or finalized
2993        if let Some(sync_target) = self.state.forkchoice_state_tracker.sync_target_state() &&
2994            sync_target.contains(block_num_hash.hash)
2995        {
2996            debug!(target: "engine::tree", ?sync_target, "appended downloaded sync target block");
2997
2998            if sync_target.head_block_hash == block_num_hash.hash {
2999                // we just inserted the sync target head block, make it canonical
3000                return Ok(Some(TreeEvent::TreeAction(TreeAction::MakeCanonical {
3001                    sync_target_head: block_num_hash.hash,
3002                })))
3003            }
3004
3005            // This block is part of the sync target (safe or finalized) but not the
3006            // head. Make it canonical and try to connect any buffered children, then
3007            // continue downloading towards the actual head if needed.
3008            self.make_canonical(block_num_hash.hash)?;
3009            self.try_connect_buffered_blocks(block_num_hash)?;
3010
3011            // Check if we've reached the sync target head after connecting buffered
3012            // blocks (e.g. the head block may have already been buffered).
3013            if self.state.tree_state.canonical_block_hash() != sync_target.head_block_hash {
3014                let target = self.lowest_buffered_ancestor_or(sync_target.head_block_hash);
3015                trace!(target: "engine::tree", %target, "sync target head not yet reached, downloading head block");
3016                return Ok(Some(TreeEvent::Download(
3017                    DownloadRequest::single_block(target)
3018                        .with_access_lists(self.should_download_access_lists()),
3019                )))
3020            }
3021
3022            return Ok(None)
3023        }
3024        trace!(target: "engine::tree", "appended downloaded block");
3025        self.try_connect_buffered_blocks(block_num_hash)?;
3026        Ok(None)
3027    }
3028
3029    /// Invoked with a block downloaded from the network
3030    ///
3031    /// Returns an event with the appropriate action to take, such as:
3032    ///  - download more missing blocks
3033    ///  - try to canonicalize the target if the `block` is the tracked target (head) block.
3034    #[instrument(level = "debug", target = "engine::tree", skip_all, fields(block_hash = %block.hash(), block_num = %block.number()))]
3035    fn on_downloaded_block(
3036        &mut self,
3037        block: SealedBlockWithAccessList<N::Block>,
3038    ) -> Result<Option<TreeEvent>, InsertBlockFatalError> {
3039        let block_num_hash = block.num_hash();
3040        let lowest_buffered_ancestor = self.lowest_buffered_ancestor_or(block_num_hash.hash);
3041        if self.check_invalid_ancestor_with_head(lowest_buffered_ancestor, &block)?.is_some() {
3042            return Ok(None)
3043        }
3044
3045        if !self.backfill_sync_state.is_idle() {
3046            return Ok(None)
3047        }
3048
3049        // try to append the block
3050        match self.insert_block(block) {
3051            Ok(InsertPayloadOk::Inserted(BlockStatus::Valid)) => {
3052                return self.on_valid_downloaded_block(block_num_hash);
3053            }
3054            Ok(InsertPayloadOk::Inserted(BlockStatus::Disconnected { head, missing_ancestor })) => {
3055                // block is not connected to the canonical head, we need to download
3056                // its missing branch first
3057                return Ok(self.on_disconnected_downloaded_block(
3058                    block_num_hash,
3059                    missing_ancestor,
3060                    head,
3061                ))
3062            }
3063            Ok(InsertPayloadOk::AlreadySeen(_)) => {
3064                trace!(target: "engine::tree", "downloaded block already executed");
3065            }
3066            Err(err) => {
3067                if let InsertPayloadError::Block(err) = err {
3068                    debug!(target: "engine::tree", err=%err.kind(), "failed to insert downloaded block");
3069                    if let Err(InsertBlockProcessingError::Fatal(fatal)) =
3070                        self.on_insert_block_error(err)
3071                    {
3072                        warn!(target: "engine::tree", %fatal, "fatal error occurred while inserting downloaded block");
3073                    }
3074                }
3075            }
3076        }
3077        Ok(None)
3078    }
3079
3080    /// Inserts a payload into the tree and executes it.
3081    ///
3082    /// This function validates the payload's basic structure, then executes it using the
3083    /// payload validator. The execution includes running all transactions in the payload
3084    /// and validating the resulting state transitions.
3085    ///
3086    /// Returns `InsertPayloadOk` if the payload was successfully inserted and executed,
3087    /// or `InsertPayloadError` if validation or execution failed.
3088    fn insert_payload(
3089        &mut self,
3090        payload: T::ExecutionData,
3091    ) -> Result<InsertPayloadOk, InsertPayloadError<N::Block>> {
3092        self.insert_block_or_payload(
3093            payload.block_with_parent(),
3094            payload,
3095            |validator, payload, ctx| validator.validate_payload(payload, ctx),
3096            |this, payload| Ok(this.payload_validator.convert_payload_to_block(payload)?.into()),
3097        )
3098    }
3099
3100    fn insert_block(
3101        &mut self,
3102        block: SealedBlockWithAccessList<N::Block>,
3103    ) -> Result<InsertPayloadOk, InsertPayloadError<N::Block>> {
3104        self.insert_block_or_payload(
3105            block.block_with_parent(),
3106            block,
3107            |validator, block, ctx| validator.validate_block(block, ctx),
3108            |_, block| Ok(block),
3109        )
3110    }
3111
3112    /// Inserts a block or payload into the blockchain tree with full execution.
3113    ///
3114    /// This is a generic function that handles both blocks and payloads by accepting
3115    /// a block identifier, input data, and execution/validation functions. It performs
3116    /// comprehensive checks and execution:
3117    ///
3118    /// - Validates that the block doesn't already exist in the tree
3119    /// - Ensures parent state is available, buffering if necessary
3120    /// - Executes the block/payload using the provided execute function
3121    /// - Handles both canonical and fork chain insertions
3122    /// - Updates pending block state when appropriate
3123    /// - Emits consensus engine events and records metrics
3124    ///
3125    /// Returns `InsertPayloadOk::Inserted(BlockStatus::Valid)` on successful execution,
3126    /// `InsertPayloadOk::AlreadySeen` if the block already exists, or
3127    /// `InsertPayloadOk::Inserted(BlockStatus::Disconnected)` if parent state is missing.
3128    #[instrument(level = "debug", target = "engine::tree", skip_all, fields(?block_id))]
3129    fn insert_block_or_payload<Input, Err>(
3130        &mut self,
3131        block_id: BlockWithParent,
3132        input: Input,
3133        execute: impl FnOnce(&mut V, Input, TreeCtx<'_, N>) -> Result<ValidationOutput<N>, Err>,
3134        convert_to_block: impl FnOnce(
3135            &mut Self,
3136            Input,
3137        ) -> Result<SealedBlockWithAccessList<N::Block>, Err>,
3138    ) -> Result<InsertPayloadOk, Err>
3139    where
3140        Err: From<InsertBlockError<N::Block>>,
3141    {
3142        let block_insert_start = Instant::now();
3143        let block_num_hash = block_id.block;
3144        debug!(target: "engine::tree", block=?block_num_hash, parent = ?block_id.parent, "Inserting new block into tree");
3145
3146        // Check if block already exists - first in memory, then DB only if it could be persisted
3147        if self.state.tree_state.contains_hash(&block_num_hash.hash) {
3148            convert_to_block(self, input)?;
3149            return Ok(InsertPayloadOk::AlreadySeen(BlockStatus::Valid));
3150        }
3151
3152        // Only query DB if block could be persisted (number <= last persisted block).
3153        // New blocks from CL always have number > last persisted, so skip DB lookup for them.
3154        if block_num_hash.number <= self.persistence_state.last_persisted_block.number {
3155            match self.provider.sealed_header_by_hash(block_num_hash.hash) {
3156                Err(err) => {
3157                    let block = convert_to_block(self, input)?;
3158                    return Err(InsertBlockError::new(block.split().0, err.into()).into());
3159                }
3160                Ok(Some(_)) => {
3161                    convert_to_block(self, input)?;
3162                    return Ok(InsertPayloadOk::AlreadySeen(BlockStatus::Valid));
3163                }
3164                Ok(None) => {}
3165            }
3166        }
3167
3168        // Ensure that the parent state is available.
3169        if !self.state.tree_state.contains_hash(&block_id.parent) {
3170            let parent_exists = match self.provider.header(block_id.parent) {
3171                Ok(header) => header.is_some(),
3172                Err(err) => {
3173                    let block = convert_to_block(self, input)?;
3174                    return Err(InsertBlockError::new(block.split().0, err.into()).into());
3175                }
3176            };
3177
3178            if !parent_exists {
3179                let block = convert_to_block(self, input)?;
3180                // we don't have the state required to execute this block, buffering it and find the
3181                // missing parent block
3182                let missing_ancestor = self
3183                    .state
3184                    .buffer
3185                    .lowest_ancestor(&block.parent_hash())
3186                    .map(|block| block.parent_num_hash())
3187                    .unwrap_or_else(|| block.parent_num_hash());
3188
3189                self.state.buffer.insert_block(block);
3190
3191                return Ok(InsertPayloadOk::Inserted(BlockStatus::Disconnected {
3192                    head: self.state.tree_state.current_canonical_head,
3193                    missing_ancestor,
3194                }))
3195            }
3196        }
3197
3198        // determine whether we are on a fork chain by comparing the block number with the
3199        // canonical head. This is a simple check that is sufficient for the event emission below.
3200        // A block is considered a fork if its number is less than or equal to the canonical head,
3201        // as this indicates there's already a canonical block at that height.
3202        let is_fork = block_id.block.number <= self.state.tree_state.current_canonical_head.number;
3203
3204        let ctx = TreeCtx::new(&mut self.state, &self.canonical_in_memory_state);
3205
3206        let start = Instant::now();
3207
3208        let ValidationOutput {
3209            executed_block: executed,
3210            execution_timing_stats: timing_stats,
3211            raw_bal,
3212        } = execute(&mut self.payload_validator, input, ctx)?;
3213
3214        if let Some(raw_bal) = raw_bal {
3215            let num_hash = executed.recovered_block().num_hash();
3216            if let Err(err) = self.provider.bal_store().insert(num_hash, raw_bal) {
3217                warn!(
3218                    target: "engine::tree",
3219                    ?num_hash,
3220                    %err,
3221                    "Failed to store validated block access list"
3222                );
3223            }
3224        }
3225
3226        // Emit slow block event immediately after execution so it appears even when
3227        // persistence hasn't completed yet (e.g. blocks arriving faster than persistence).
3228        if let Some(stats) = timing_stats {
3229            if let Some(threshold) = self.config.slow_block_threshold() {
3230                let total_duration = stats.execution_duration + stats.state_hash_duration;
3231                if total_duration > threshold {
3232                    self.emit_event(ConsensusEngineEvent::SlowBlock(SlowBlockInfo {
3233                        stats: stats.clone(),
3234                        commit_duration: None,
3235                        total_duration,
3236                    }));
3237                }
3238            }
3239            self.execution_timing_stats.insert(executed.recovered_block().hash(), stats);
3240        }
3241
3242        let is_pending = self.state.tree_state.canonical_block_hash() ==
3243            executed.recovered_block().parent_hash();
3244        self.state.tree_state.insert_executed(executed.clone());
3245
3246        if is_pending {
3247            debug!(target: "engine::tree", pending=?block_num_hash, "updating pending block");
3248            self.canonical_in_memory_state.set_pending_block(executed.clone());
3249        }
3250
3251        self.metrics.engine.executed_blocks.set(self.state.tree_state.block_count() as f64);
3252
3253        // emit insert event
3254        let elapsed = start.elapsed();
3255        let engine_event = if is_fork {
3256            ConsensusEngineEvent::ForkBlockAdded(executed, elapsed)
3257        } else {
3258            ConsensusEngineEvent::CanonicalBlockAdded(executed, elapsed)
3259        };
3260        self.emit_event(EngineApiEvent::BeaconConsensus(engine_event));
3261
3262        self.metrics
3263            .engine
3264            .block_insert_total_duration
3265            .record(block_insert_start.elapsed().as_secs_f64());
3266        debug!(target: "engine::tree", block=?block_num_hash, "Finished inserting block");
3267        Ok(InsertPayloadOk::Inserted(BlockStatus::Valid))
3268    }
3269
3270    /// Handles an error that occurred while inserting a block.
3271    ///
3272    /// If this is a validation error this will mark the block as invalid.
3273    ///
3274    /// Returns the proper payload status response if the block is invalid.
3275    fn on_insert_block_error(
3276        &mut self,
3277        error: InsertBlockError<N::Block>,
3278    ) -> Result<PayloadStatus, InsertBlockProcessingError> {
3279        let (block, error) = error.split();
3280
3281        let validation_err = error.ensure_validation_error()?;
3282
3283        // If the error was due to an invalid payload, the payload is added to the
3284        // invalid headers cache and `Ok` with [PayloadStatusEnum::Invalid] is
3285        // returned.
3286        warn!(
3287            target: "engine::tree",
3288            invalid_hash=%block.hash(),
3289            invalid_number=block.number(),
3290            %validation_err,
3291            "Invalid block error on new payload",
3292        );
3293        // The Amsterdam Engine API requires `latestValidHash: null` for an undecodable BAL.
3294        // <https://github.com/ethereum/execution-apis/blob/df75e230befef0de56ee8833322ed714bacb479c/src/engine/amsterdam.md?plain=1#L129>
3295        let latest_valid_hash =
3296            if matches!(&validation_err, InsertBlockValidationError::BlockAccessListDecode(_)) {
3297                None
3298            } else {
3299                self.latest_valid_hash_for_invalid_payload(block.parent_hash())
3300                    .map_err(InsertBlockFatalError::from)?
3301            };
3302
3303        // keep track of the invalid header unless the consensus impl considers it transient
3304        let is_transient = match &validation_err {
3305            InsertBlockValidationError::Consensus(err) => self.consensus.is_transient_error(err),
3306            _ => false,
3307        };
3308        if is_transient {
3309            warn!(
3310                target: "engine::tree",
3311                invalid_hash=%block.hash(),
3312                invalid_number=block.number(),
3313                %validation_err,
3314                "Skipping invalid header cache insert for transient validation error",
3315            );
3316        } else {
3317            self.state.invalid_headers.insert(block.block_with_parent());
3318        }
3319        self.emit_event(EngineApiEvent::BeaconConsensus(ConsensusEngineEvent::InvalidBlock {
3320            block: Box::new(block),
3321            error: validation_err.to_string(),
3322        }));
3323
3324        Ok(PayloadStatus::new(
3325            PayloadStatusEnum::Invalid { validation_error: validation_err.to_string() },
3326            latest_valid_hash,
3327        ))
3328    }
3329
3330    /// Handles a [`NewPayloadError`] by converting it to a [`PayloadStatus`].
3331    fn on_new_payload_error(
3332        &mut self,
3333        error: NewPayloadError,
3334        payload_num_hash: NumHash,
3335        parent_hash: B256,
3336    ) -> ProviderResult<PayloadStatus> {
3337        error!(target: "engine::tree", payload=?payload_num_hash, %error, "Invalid payload");
3338        // we need to convert the error to a payload status (response to the CL)
3339
3340        let latest_valid_hash =
3341            if error.is_block_hash_mismatch() || error.is_invalid_versioned_hashes() {
3342                // Engine-API rules:
3343                // > `latestValidHash: null` if the blockHash validation has failed (<https://github.com/ethereum/execution-apis/blob/fe8e13c288c592ec154ce25c534e26cb7ce0530d/src/engine/shanghai.md?plain=1#L113>)
3344                // > `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>)
3345                None
3346            } else {
3347                self.latest_valid_hash_for_invalid_payload(parent_hash)?
3348            };
3349
3350        let status = PayloadStatusEnum::from(error);
3351        Ok(PayloadStatus::new(status, latest_valid_hash))
3352    }
3353
3354    /// Attempts to find the header for the given block hash if it is canonical.
3355    pub fn find_canonical_header(
3356        &self,
3357        hash: B256,
3358    ) -> Result<Option<SealedHeader<N::BlockHeader>>, ProviderError> {
3359        let mut canonical = self.canonical_in_memory_state.header_by_hash(hash);
3360
3361        if canonical.is_none() {
3362            canonical = self.provider.header(hash)?.map(|header| SealedHeader::new(header, hash));
3363        }
3364
3365        Ok(canonical)
3366    }
3367
3368    /// Updates the tracked finalized block if we have it.
3369    fn update_finalized_block(
3370        &self,
3371        finalized_block_hash: B256,
3372    ) -> Result<(), OnForkChoiceUpdated> {
3373        if finalized_block_hash.is_zero() {
3374            return Ok(())
3375        }
3376
3377        match self.find_canonical_header(finalized_block_hash) {
3378            Ok(None) => {
3379                debug!(target: "engine::tree", "Finalized block not found in canonical chain");
3380                // if the finalized block is not known, we can't update the finalized block
3381                return Err(OnForkChoiceUpdated::invalid_state())
3382            }
3383            Ok(Some(finalized)) => {
3384                if Some(finalized.num_hash()) !=
3385                    self.canonical_in_memory_state.get_finalized_num_hash()
3386                {
3387                    // we're also persisting the finalized block on disk so we can reload it on
3388                    // 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>
3389                    let _ = self.persistence.save_finalized_block_number(finalized.number());
3390                    self.canonical_in_memory_state.set_finalized(finalized.clone());
3391                    // Update finalized block height metric
3392                    self.metrics.tree.finalized_block_height.set(finalized.number() as f64);
3393                }
3394            }
3395            Err(err) => {
3396                error!(target: "engine::tree", %err, "Failed to fetch finalized block header");
3397            }
3398        }
3399
3400        Ok(())
3401    }
3402
3403    /// Updates the tracked safe block if we have it
3404    fn update_safe_block(&self, safe_block_hash: B256) -> Result<(), OnForkChoiceUpdated> {
3405        if safe_block_hash.is_zero() {
3406            return Ok(())
3407        }
3408
3409        match self.find_canonical_header(safe_block_hash) {
3410            Ok(None) => {
3411                debug!(target: "engine::tree", "Safe block not found in canonical chain");
3412                // if the safe block is not known, we can't update the safe block
3413                return Err(OnForkChoiceUpdated::invalid_state())
3414            }
3415            Ok(Some(safe)) => {
3416                if Some(safe.num_hash()) != self.canonical_in_memory_state.get_safe_num_hash() {
3417                    // we're also persisting the safe block on disk so we can reload it on
3418                    // 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>
3419                    let _ = self.persistence.save_safe_block_number(safe.number());
3420                    self.canonical_in_memory_state.set_safe(safe.clone());
3421                    // Update safe block height metric
3422                    self.metrics.tree.safe_block_height.set(safe.number() as f64);
3423                }
3424            }
3425            Err(err) => {
3426                error!(target: "engine::tree", %err, "Failed to fetch safe block header");
3427            }
3428        }
3429
3430        Ok(())
3431    }
3432
3433    /// Ensures that the given forkchoice state is consistent, assuming the head block has been
3434    /// made canonical.
3435    ///
3436    /// If the forkchoice state is consistent, this will return Ok(()). Otherwise, this will
3437    /// return an instance of [`OnForkChoiceUpdated`] that is INVALID.
3438    ///
3439    /// This also updates the safe and finalized blocks in the [`CanonicalInMemoryState`], if they
3440    /// are consistent with the head block.
3441    fn ensure_consistent_forkchoice_state(
3442        &self,
3443        state: ForkchoiceState,
3444    ) -> Result<(), OnForkChoiceUpdated> {
3445        // Ensure that the finalized block, if not zero, is known and in the canonical chain
3446        // after the head block is canonicalized.
3447        //
3448        // This ensures that the finalized block is consistent with the head block, i.e. the
3449        // finalized block is an ancestor of the head block.
3450        self.update_finalized_block(state.finalized_block_hash)?;
3451
3452        // Also ensure that the safe block, if not zero, is known and in the canonical chain
3453        // after the head block is canonicalized.
3454        //
3455        // This ensures that the safe block is consistent with the head block, i.e. the safe
3456        // block is an ancestor of the head block.
3457        self.update_safe_block(state.safe_block_hash)
3458    }
3459
3460    /// Validates the payload attributes with respect to the header and fork choice state.
3461    ///
3462    /// This is called during `engine_forkchoiceUpdated` when the CL provides payload attributes,
3463    /// indicating it wants the EL to start building a new block.
3464    ///
3465    /// Runs [`PayloadValidator::validate_payload_attributes_against_header`](reth_engine_primitives::PayloadValidator::validate_payload_attributes_against_header) to ensure
3466    /// `payloadAttributes.timestamp > headBlock.timestamp` per the Engine API spec.
3467    ///
3468    /// If validation passes, sends the attributes to the payload builder to start a new
3469    /// payload job. If it fails, returns `INVALID_PAYLOAD_ATTRIBUTES` without rolling back
3470    /// the forkchoice update.
3471    ///
3472    /// Note: At this point, the fork choice update is considered to be VALID, however, we can still
3473    /// return an error if the payload attributes are invalid.
3474    fn process_payload_attributes(
3475        &mut self,
3476        attributes: T::PayloadAttributes,
3477        head: &N::BlockHeader,
3478        state: ForkchoiceState,
3479    ) -> OnForkChoiceUpdated {
3480        if let Err(err) =
3481            self.payload_validator.validate_payload_attributes_against_header(&attributes, head)
3482        {
3483            warn!(target: "engine::tree", %err, ?head, "Invalid payload attributes");
3484            return OnForkChoiceUpdated::invalid_payload_attributes()
3485        }
3486
3487        // 8. Client software MUST begin a payload build process building on top of
3488        //    forkchoiceState.headBlockHash and identified via buildProcessId value if
3489        //    payloadAttributes is not null and the forkchoice state has been updated successfully.
3490        //    The build process is specified in the Payload building section.
3491
3492        // Acquire this before preparing resources because state-root setup can already start
3493        // workers that need the current in-memory overlay.
3494        let payload_build = self.payload_builds.acquire();
3495
3496        let resources = self
3497            .payload_validator
3498            .payload_builder_resources(
3499                state.head_block_hash,
3500                head,
3501                attributes.timestamp(),
3502                &mut self.state,
3503            )
3504            .with_lease(PayloadBuilderLease::new(payload_build));
3505
3506        // send the payload to the builder and return the receiver for the pending payload
3507        // id, initiating payload job is handled asynchronously
3508        let pending_payload_id = self.payload_builder.send_new_payload(BuildNewPayload {
3509            parent_hash: state.head_block_hash,
3510            attributes,
3511            resources,
3512        });
3513
3514        // Client software MUST respond to this method call in the following way:
3515        // {
3516        //      payloadStatus: {
3517        //          status: VALID,
3518        //          latestValidHash: forkchoiceState.headBlockHash,
3519        //          validationError: null
3520        //      },
3521        //      payloadId: buildProcessId
3522        // }
3523        //
3524        // if the payload is deemed VALID and the build process has begun.
3525        OnForkChoiceUpdated::updated_with_pending_payload_id(
3526            PayloadStatus::new(PayloadStatusEnum::Valid, Some(state.head_block_hash)),
3527            pending_payload_id,
3528        )
3529    }
3530
3531    /// Remove all blocks up to __and including__ the given block number.
3532    ///
3533    /// If a finalized hash is provided, the only non-canonical blocks which will be removed are
3534    /// those which have a fork point at or below the finalized hash.
3535    ///
3536    /// Canonical blocks below the upper bound will still be removed.
3537    pub(crate) fn remove_before(
3538        &mut self,
3539        upper_bound: BlockNumHash,
3540        finalized_hash: Option<B256>,
3541    ) -> ProviderResult<()> {
3542        // first fetch the finalized block number and then call the remove_before method on
3543        // tree_state
3544        let num = if let Some(hash) = finalized_hash {
3545            self.provider.block_number(hash)?.map(|number| BlockNumHash { number, hash })
3546        } else {
3547            None
3548        };
3549
3550        self.state.tree_state.remove_until(
3551            upper_bound,
3552            self.persistence_state.last_persisted_block.hash,
3553            num,
3554        );
3555        Ok(())
3556    }
3557}
3558
3559/// Events received in the main engine loop.
3560#[derive(Debug)]
3561enum LoopEvent<T, N>
3562where
3563    N: NodePrimitives,
3564    T: PayloadTypes,
3565{
3566    /// An engine API message was received.
3567    EngineMessage(FromEngine<EngineApiRequest<T, N>, N::Block>),
3568    /// A persistence task completed.
3569    PersistenceComplete {
3570        /// The unified result of the persistence operation.
3571        result: PersistenceResult,
3572        /// When the persistence operation started.
3573        start_time: Instant,
3574    },
3575    /// The last active payload job has finished, so suppressed persistence may resume.
3576    PayloadBuildFinished,
3577    /// A channel was disconnected.
3578    Disconnected,
3579}
3580
3581/// Tracks payload jobs that may access the current in-memory overlay.
3582#[derive(Clone, Debug)]
3583struct PayloadBuildTracker {
3584    active: Arc<AtomicUsize>,
3585    finished_tx: Sender<()>,
3586}
3587
3588impl PayloadBuildTracker {
3589    /// Creates a tracker and a receiver notified when its active count reaches zero.
3590    fn new() -> (Self, Receiver<()>) {
3591        let (finished_tx, finished_rx) = crossbeam_channel::bounded(1);
3592        (Self { active: Arc::new(AtomicUsize::new(0)), finished_tx }, finished_rx)
3593    }
3594
3595    /// Acquires a lease for one payload job.
3596    fn acquire(&self) -> PayloadBuildLease {
3597        self.active.fetch_add(1, Ordering::AcqRel);
3598        PayloadBuildLease {
3599            active: Arc::clone(&self.active),
3600            finished_tx: self.finished_tx.clone(),
3601        }
3602    }
3603
3604    /// Returns whether at least one payload job is active.
3605    fn is_active(&self) -> bool {
3606        self.active.load(Ordering::Acquire) != 0
3607    }
3608}
3609
3610/// A lease held for the lifetime of a payload job.
3611#[derive(Debug)]
3612struct PayloadBuildLease {
3613    active: Arc<AtomicUsize>,
3614    finished_tx: Sender<()>,
3615}
3616
3617impl Drop for PayloadBuildLease {
3618    fn drop(&mut self) {
3619        let previous = self.active.fetch_sub(1, Ordering::AcqRel);
3620        debug_assert!(previous > 0, "payload build lease count underflow");
3621
3622        if previous == 1 {
3623            // The bounded channel coalesces completion notifications. The engine always checks
3624            // the counter again before applying a pending handoff.
3625            let _ = self.finished_tx.try_send(());
3626        }
3627    }
3628}
3629
3630/// Block inclusion can be valid, accepted, or invalid. Invalid blocks are returned as an error
3631/// variant.
3632///
3633/// If we don't know the block's parent, we return `Disconnected`, as we can't claim that the block
3634/// is valid or not.
3635#[derive(Clone, Copy, Debug, Eq, PartialEq)]
3636pub enum BlockStatus {
3637    /// The block is valid: its parent state was available, so it was executed and inserted into
3638    /// the tree.
3639    ///
3640    /// Note: this does not imply the block extends the canonical chain. Blocks on a fork are
3641    /// executed and inserted the same way and report this status as well.
3642    Valid,
3643    /// The block may be valid and has an unknown missing ancestor.
3644    Disconnected {
3645        /// Current canonical head.
3646        head: BlockNumHash,
3647        /// The lowest ancestor block that is not connected to the canonical chain.
3648        missing_ancestor: BlockNumHash,
3649    },
3650}
3651
3652/// How a payload was inserted if it was valid.
3653///
3654/// If the payload was valid, but has already been seen, [`InsertPayloadOk::AlreadySeen`] is
3655/// returned, otherwise [`InsertPayloadOk::Inserted`] is returned.
3656#[derive(Clone, Copy, Debug, Eq, PartialEq)]
3657pub enum InsertPayloadOk {
3658    /// The payload was valid, but we have already seen it.
3659    AlreadySeen(BlockStatus),
3660    /// The payload was valid and inserted into the tree.
3661    Inserted(BlockStatus),
3662}
3663
3664/// Target for block persistence.
3665#[derive(Debug, Clone, Copy)]
3666enum PersistTarget {
3667    /// Persist up to `canonical_head - memory_block_buffer_target`.
3668    Threshold,
3669    /// Persist all blocks up to and including the canonical head.
3670    Head,
3671    /// Persist state/trie updates through the persisted block frontier.
3672    Persisted,
3673}
3674
3675/// Result of waiting for caches to become available.
3676#[derive(Debug, Clone, Copy, Default)]
3677pub struct CacheWaitDurations {
3678    /// Time spent waiting for the execution cache lock.
3679    pub execution_cache: Duration,
3680    /// Time spent waiting for the sparse trie lock.
3681    pub sparse_trie: Duration,
3682}
3683
3684/// Trait for types that can wait for caches to become available.
3685///
3686/// This is used by `reth_newPayload` endpoint to ensure that payload processing
3687/// waits for any ongoing operations to complete before starting.
3688pub trait WaitForCaches {
3689    /// Waits for cache updates to complete.
3690    ///
3691    /// Returns the time spent waiting for each cache separately.
3692    fn wait_for_caches(&self) -> CacheWaitDurations;
3693}