Skip to main content

reth_engine_tree/tree/
mod.rs

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