1use crate::{
2 backfill::{BackfillAction, BackfillSyncState},
3 chain::FromOrchestrator,
4 engine::{DownloadRequest, EngineApiEvent, EngineApiKind, EngineApiRequest, FromEngine},
5 persistence::PersistenceHandle,
6 tree::{error::InsertPayloadError, payload_validator::TreeCtx},
7};
8use alloy_consensus::BlockHeader;
9use alloy_eips::{eip1898::BlockWithParent, merge::EPOCH_SLOTS, BlockNumHash, NumHash};
10use alloy_primitives::{map::B256Map, B256};
11use alloy_rpc_types_engine::{
12 ForkchoiceState, PayloadStatus, PayloadStatusEnum, PayloadValidationError,
13};
14use error::{
15 InsertBlockError, InsertBlockFatalError, InsertBlockProcessingError, InsertBlockValidationError,
16};
17use reth_chain_state::{
18 CanonicalInMemoryState, ExecutedBlock, ExecutionTimingStats, NewCanonicalChain,
19};
20use reth_consensus::{Consensus, FullConsensus};
21use reth_engine_primitives::{
22 BeaconEngineMessage, ConsensusEngineEvent, ExecutionPayload, ForkchoiceStateTracker,
23 NewPayloadTimings, OnForkChoiceUpdated, SlowBlockInfo,
24};
25use reth_errors::{ConsensusError, ProviderResult};
26use reth_evm::ConfigureEvm;
27use reth_network_p2p::full_block::SealedBlockWithAccessList;
28use reth_payload_builder::{BuildNewPayload, PayloadBuilderHandle, PayloadBuilderLease};
29use reth_payload_primitives::{BuiltPayload, NewPayloadError, PayloadAttributes, PayloadTypes};
30use reth_primitives_traits::{
31 FastInstant as Instant, NodePrimitives, RecoveredBlock, SealedBlock, SealedHeader,
32};
33use reth_provider::{
34 BalProvider, BlockExecutionOutput, BlockExecutionResult, BlockReader, ChangeSetReader,
35 DatabaseProviderFactory, ProviderError, PruneCheckpointReader, SaveBlocksInput,
36 StageCheckpointReader, StateProviderFactory, StateReader, StorageChangeSetReader,
37 StorageSettingsCache, TransactionVariant,
38};
39use reth_revm::database::StateProviderDatabase;
40use reth_stages_api::ControlFlow;
41use reth_storage_overlay::OverlayManager;
42use reth_tasks::{spawn_os_thread, utils::increase_thread_priority};
43use reth_trie::ComputedTrieData;
44use revm::interpreter::debug_unreachable;
45use state::TreeState;
46use std::{
47 fmt::Debug,
48 ops,
49 sync::{
50 atomic::{AtomicUsize, Ordering},
51 Arc,
52 },
53 time::Duration,
54};
55
56use crossbeam_channel::{Receiver, Sender};
57use tokio::sync::{
58 mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
59 oneshot,
60};
61use tracing::*;
62
63mod block_buffer;
64pub mod error;
65pub mod instrumented_state;
66mod invalid_headers;
67mod metrics;
68pub mod payload_processor;
69pub mod payload_validator;
70mod persistence_state;
71pub mod precompile_cache;
72pub mod state_root_strategy;
73#[cfg(test)]
74mod tests;
75mod trie_updates;
76mod txpool_prewarm;
77pub mod types;
78
79use crate::{persistence::PersistenceResult, tree::error::AdvancePersistenceError};
80pub use block_buffer::BlockBuffer;
81pub use invalid_headers::InvalidHeaderCache;
82pub use metrics::EngineApiMetrics;
83pub use payload_processor::*;
84pub use payload_validator::{BasicEngineValidator, EngineValidator};
85pub use persistence_state::PersistenceState;
86pub use reth_engine_primitives::TreeConfig;
87pub use reth_execution_cache::{
88 CachedStateCacheMetrics, CachedStateMetrics, CachedStateMetricsSource, CachedStateProvider,
89 ExecutionCache, PayloadExecutionCache, SavedCache, TxPoolPrewarmCacheSnapshot,
90};
91pub use txpool_prewarm::{
92 Source as TxPoolPrewarmSource, Transaction as TxPoolPrewarmTransaction,
93 Transactions as TxPoolPrewarmTransactions,
94};
95pub use types::{ExecutionEnv, ValidationOutcome, ValidationOutput};
96
97pub mod state;
98
99pub(crate) const MIN_BLOCKS_FOR_PIPELINE_RUN: u64 = EPOCH_SLOTS;
109
110const CHANGESET_CACHE_RETENTION_BLOCKS: u64 = 64;
115
116#[derive(Debug)]
120pub struct EngineApiTreeState<N: NodePrimitives> {
121 tree_state: TreeState<N>,
123 pending_sparse_trie_prune: bool,
125 forkchoice_state_tracker: ForkchoiceStateTracker,
127 buffer: BlockBuffer<N::Block>,
129 invalid_headers: InvalidHeaderCache,
132}
133
134impl<N: NodePrimitives> EngineApiTreeState<N> {
135 fn new(
136 block_buffer_limit: u32,
137 max_invalid_header_cache_length: u32,
138 invalid_header_hit_eviction_threshold: u8,
139 canonical_block: BlockNumHash,
140 engine_kind: EngineApiKind,
141 overlay_manager: OverlayManager<N>,
142 ) -> Self {
143 Self {
144 invalid_headers: InvalidHeaderCache::new(
145 max_invalid_header_cache_length,
146 invalid_header_hit_eviction_threshold,
147 ),
148 buffer: BlockBuffer::new(block_buffer_limit),
149 tree_state: TreeState::new(canonical_block, engine_kind, overlay_manager),
150 pending_sparse_trie_prune: false,
151 forkchoice_state_tracker: ForkchoiceStateTracker::default(),
152 }
153 }
154
155 pub const fn tree_state(&self) -> &TreeState<N> {
157 &self.tree_state
158 }
159
160 pub const fn pending_sparse_trie_prune(&self) -> bool {
162 self.pending_sparse_trie_prune
163 }
164
165 pub const fn set_pending_sparse_trie_prune(&mut self, pending: bool) {
167 self.pending_sparse_trie_prune = pending;
168 }
169
170 pub fn take_sparse_trie_prune_blocks(
177 &mut self,
178 parent_hash: B256,
179 ) -> Option<Vec<ExecutedBlock<N>>> {
180 if !self.pending_sparse_trie_prune {
181 return None
182 }
183
184 self.pending_sparse_trie_prune = false;
185 Some(
186 self.tree_state
187 .blocks_by_hash(parent_hash)
188 .map(|(_, blocks)| blocks)
189 .unwrap_or_default(),
190 )
191 }
192
193 pub fn has_invalid_header(&mut self, hash: &B256) -> bool {
195 self.invalid_headers.get(hash).is_some()
196 }
197}
198
199#[derive(Debug)]
201pub struct TreeOutcome<T> {
202 pub outcome: T,
204 pub event: Option<TreeEvent>,
206 pub already_seen: bool,
209}
210
211impl<T> TreeOutcome<T> {
212 pub const fn new(outcome: T) -> Self {
214 Self { outcome, event: None, already_seen: false }
215 }
216
217 pub fn with_event(mut self, event: TreeEvent) -> Self {
219 self.event = Some(event);
220 self
221 }
222
223 pub const fn with_already_seen(mut self, value: bool) -> Self {
225 self.already_seen = value;
226 self
227 }
228}
229
230#[derive(Debug)]
232pub struct TryInsertPayloadResult {
233 pub status: PayloadStatus,
237 pub already_seen: bool,
239}
240
241impl TryInsertPayloadResult {
242 #[inline]
244 pub fn into_outcome(self) -> TreeOutcome<PayloadStatus> {
245 TreeOutcome::new(self.status).with_already_seen(self.already_seen)
246 }
247}
248
249#[derive(Debug)]
251pub enum TreeEvent {
252 TreeAction(TreeAction),
254 BackfillAction(BackfillAction),
256 Download(DownloadRequest),
258}
259
260impl TreeEvent {
261 const fn is_backfill_action(&self) -> bool {
263 matches!(self, Self::BackfillAction(_))
264 }
265}
266
267#[derive(Debug)]
269pub enum TreeAction {
270 MakeCanonical {
272 sync_target_head: B256,
274 },
275}
276
277pub struct EngineApiTreeHandler<N, P, T, V, C>
282where
283 N: NodePrimitives,
284 T: PayloadTypes,
285 C: ConfigureEvm<Primitives = N> + 'static,
286{
287 provider: P,
288 consensus: Arc<dyn FullConsensus<N>>,
289 payload_validator: V,
290 state: EngineApiTreeState<N>,
292 incoming_tx: Sender<FromEngine<EngineApiRequest<T, N>, N::Block>>,
301 incoming: Receiver<FromEngine<EngineApiRequest<T, N>, N::Block>>,
303 outgoing: UnboundedSender<EngineApiEvent<N>>,
305 persistence: PersistenceHandle<N>,
307 persistence_state: PersistenceState,
309 backfill_sync_state: BackfillSyncState,
311 canonical_in_memory_state: CanonicalInMemoryState<N>,
314 payload_builder: PayloadBuilderHandle<T>,
317 config: TreeConfig,
319 metrics: EngineApiMetrics,
321 engine_kind: EngineApiKind,
323 evm_config: C,
325 execution_timing_stats: B256Map<Box<ExecutionTimingStats>>,
329 payload_builds: PayloadBuildTracker,
331 payload_build_finished: Receiver<()>,
333 runtime: reth_tasks::Runtime,
335}
336
337impl<N, P: Debug, T: PayloadTypes + Debug, V: Debug, C> std::fmt::Debug
338 for EngineApiTreeHandler<N, P, T, V, C>
339where
340 N: NodePrimitives,
341 C: Debug + ConfigureEvm<Primitives = N>,
342{
343 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
344 f.debug_struct("EngineApiTreeHandler")
345 .field("provider", &self.provider)
346 .field("consensus", &self.consensus)
347 .field("payload_validator", &self.payload_validator)
348 .field("state", &self.state)
349 .field("incoming_tx", &self.incoming_tx)
350 .field("persistence", &self.persistence)
351 .field("persistence_state", &self.persistence_state)
352 .field("backfill_sync_state", &self.backfill_sync_state)
353 .field("canonical_in_memory_state", &self.canonical_in_memory_state)
354 .field("payload_builder", &self.payload_builder)
355 .field("config", &self.config)
356 .field("metrics", &self.metrics)
357 .field("engine_kind", &self.engine_kind)
358 .field("evm_config", &self.evm_config)
359 .field("execution_timing_stats", &self.execution_timing_stats.len())
360 .field("payload_builds_active", &self.payload_builds.is_active())
361 .field("runtime", &self.runtime)
362 .finish()
363 }
364}
365
366impl<N, P, T, V, C> EngineApiTreeHandler<N, P, T, V, C>
367where
368 N: NodePrimitives,
369 P: DatabaseProviderFactory
370 + BlockReader<Block = N::Block, Header = N::BlockHeader>
371 + StateProviderFactory
372 + StateReader<Receipt = N::Receipt>
373 + BalProvider
374 + Clone
375 + 'static,
376 P::Provider: BlockReader<Block = N::Block, Header = N::BlockHeader>
377 + PruneCheckpointReader
378 + StageCheckpointReader
379 + ChangeSetReader
380 + StorageChangeSetReader
381 + StorageSettingsCache
382 + 'static,
383 C: ConfigureEvm<Primitives = N> + 'static,
384 T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>,
385 V: EngineValidator<T> + WaitForCaches,
386{
387 #[expect(clippy::too_many_arguments)]
389 pub fn new(
390 provider: P,
391 consensus: Arc<dyn FullConsensus<N>>,
392 payload_validator: V,
393 outgoing: UnboundedSender<EngineApiEvent<N>>,
394 state: EngineApiTreeState<N>,
395 canonical_in_memory_state: CanonicalInMemoryState<N>,
396 persistence: PersistenceHandle<N>,
397 persistence_state: PersistenceState,
398 payload_builder: PayloadBuilderHandle<T>,
399 config: TreeConfig,
400 engine_kind: EngineApiKind,
401 evm_config: C,
402 runtime: reth_tasks::Runtime,
403 ) -> Self {
404 let (incoming_tx, incoming) = crossbeam_channel::unbounded();
405
406 let (payload_builds, payload_build_finished) = PayloadBuildTracker::new();
407
408 Self {
409 provider,
410 consensus,
411 payload_validator,
412 incoming,
413 outgoing,
414 persistence,
415 persistence_state,
416 backfill_sync_state: BackfillSyncState::Idle,
417 state,
418 canonical_in_memory_state,
419 payload_builder,
420 config,
421 metrics: Default::default(),
422 incoming_tx,
423 engine_kind,
424 evm_config,
425 execution_timing_stats: B256Map::default(),
426 payload_builds,
427 payload_build_finished,
428 runtime,
429 }
430 }
431
432 #[expect(clippy::complexity)]
438 pub fn spawn_new(
439 provider: P,
440 consensus: Arc<dyn FullConsensus<N>>,
441 payload_validator: V,
442 persistence: PersistenceHandle<N>,
443 payload_builder: PayloadBuilderHandle<T>,
444 canonical_in_memory_state: CanonicalInMemoryState<N>,
445 overlay_manager: OverlayManager<N>,
446 config: TreeConfig,
447 kind: EngineApiKind,
448 evm_config: C,
449 runtime: reth_tasks::Runtime,
450 ) -> (Sender<FromEngine<EngineApiRequest<T, N>, N::Block>>, UnboundedReceiver<EngineApiEvent<N>>)
451 {
452 let best_block_number = provider.best_block_number().unwrap_or(0);
453 let header = provider.sealed_header(best_block_number).ok().flatten().unwrap_or_default();
454
455 let persistence_state = PersistenceState {
456 last_persisted_block: BlockNumHash::new(best_block_number, header.hash()),
457 last_state_trie_persisted_block: BlockNumHash::new(best_block_number, header.hash()),
458 rx: None,
459 };
460
461 let (tx, outgoing) = unbounded_channel();
462 let state = EngineApiTreeState::new(
463 config.block_buffer_limit(),
464 config.max_invalid_header_cache_length(),
465 config.invalid_header_hit_eviction_threshold(),
466 header.num_hash(),
467 kind,
468 overlay_manager,
469 );
470
471 let task = Self::new(
472 provider,
473 consensus,
474 payload_validator,
475 tx,
476 state,
477 canonical_in_memory_state,
478 persistence,
479 persistence_state,
480 payload_builder,
481 config,
482 kind,
483 evm_config,
484 runtime,
485 );
486 let incoming = task.incoming_tx.clone();
487 spawn_os_thread("engine", || {
488 increase_thread_priority();
489 task.run()
490 });
491 (incoming, outgoing)
492 }
493
494 fn valid_outcome(state: ForkchoiceState) -> TreeOutcome<OnForkChoiceUpdated> {
496 TreeOutcome::new(OnForkChoiceUpdated::valid(PayloadStatus::new(
497 PayloadStatusEnum::Valid,
498 Some(state.head_block_hash),
499 )))
500 }
501
502 pub fn sender(&self) -> Sender<FromEngine<EngineApiRequest<T, N>, N::Block>> {
504 self.incoming_tx.clone()
505 }
506
507 const fn persistence_gap(&self) -> u64 {
510 self.state
511 .tree_state
512 .canonical_block_number()
513 .saturating_sub(self.persistence_state.last_persisted_block.number)
514 }
515
516 const fn persistence_backpressure_gap(&self) -> u64 {
518 self.persistence_gap().saturating_sub(self.config.memory_block_buffer_target())
519 }
520
521 const fn should_backpressure(&self) -> bool {
526 self.persistence_state.in_progress() &&
527 self.persistence_backpressure_gap() >=
528 self.config.persistence_backpressure_threshold()
529 }
530
531 pub fn run(mut self) {
535 loop {
536 match self.try_poll_persistence() {
561 Ok(true) => {
562 if let Err(err) = self.advance_persistence() {
563 error!(target: "engine::tree", %err, "Advancing persistence failed");
564 return
565 }
566 continue;
567 }
568 Ok(false) => {}
569 Err(err) => {
570 error!(target: "engine::tree", %err, "Polling persistence failed");
571 return
572 }
573 }
574
575 let event = if self.should_backpressure() {
576 self.metrics.engine.backpressure_active.set(1.0);
577 let stall_start = Instant::now();
578 let event = self.wait_for_persistence_event();
579 self.metrics.engine.backpressure_stall_duration.record(stall_start.elapsed());
580 event
581 } else {
582 self.metrics.engine.backpressure_active.set(0.0);
583 self.wait_for_event()
584 };
585
586 match event {
587 LoopEvent::EngineMessage(msg) => {
588 debug!(target: "engine::tree", %msg, "received new engine message");
589 match self.on_engine_message(msg) {
590 Ok(ops::ControlFlow::Break(())) => return,
591 Ok(ops::ControlFlow::Continue(())) => {}
592 Err(fatal) => {
593 error!(target: "engine::tree", %fatal, "insert block fatal error");
594 return
595 }
596 }
597 }
598 LoopEvent::PersistenceComplete { result, start_time } => {
599 if let Err(err) = self.on_persistence_complete(result, start_time) {
600 error!(target: "engine::tree", %err, "Persistence complete handling failed");
601 return
602 }
603 }
604 LoopEvent::PayloadBuildFinished => {}
605 LoopEvent::Disconnected => {
606 error!(target: "engine::tree", "Channel disconnected");
607 return
608 }
609 }
610
611 if let Err(err) = self.advance_persistence() {
616 error!(target: "engine::tree", %err, "Advancing persistence failed");
617 return
618 }
619 }
620 }
621
622 fn wait_for_persistence_event(&mut self) -> LoopEvent<T, N> {
628 let maybe_persistence = self.persistence_state.rx.take();
629
630 if let Some((persistence_rx, start_time, _action)) = maybe_persistence {
631 match persistence_rx.recv() {
632 Ok(result) => LoopEvent::PersistenceComplete { result, start_time },
633 Err(_) => LoopEvent::Disconnected,
634 }
635 } else {
636 self.wait_for_event()
637 }
638 }
639
640 fn wait_for_event(&mut self) -> LoopEvent<T, N> {
645 let maybe_persistence = self.persistence_state.rx.take();
647
648 if let Some((persistence_rx, start_time, action)) = maybe_persistence {
649 crossbeam_channel::select_biased! {
652 recv(persistence_rx) -> result => {
653 match result {
655 Ok(result) => LoopEvent::PersistenceComplete {
656 result,
657 start_time,
658 },
659 Err(_) => LoopEvent::Disconnected,
660 }
661 },
662 recv(self.payload_build_finished) -> result => {
663 self.persistence_state.rx = Some((persistence_rx, start_time, action));
665 match result {
666 Ok(()) => LoopEvent::PayloadBuildFinished,
667 Err(_) => LoopEvent::Disconnected,
668 }
669 },
670 recv(self.incoming) -> msg => {
671 self.persistence_state.rx = Some((persistence_rx, start_time, action));
673 match msg {
674 Ok(m) => LoopEvent::EngineMessage(m),
675 Err(_) => LoopEvent::Disconnected,
676 }
677 },
678 }
679 } else {
680 crossbeam_channel::select_biased! {
682 recv(self.payload_build_finished) -> result => match result {
683 Ok(()) => LoopEvent::PayloadBuildFinished,
684 Err(_) => LoopEvent::Disconnected,
685 },
686 recv(self.incoming) -> msg => match msg {
687 Ok(m) => LoopEvent::EngineMessage(m),
688 Err(_) => LoopEvent::Disconnected,
689 },
690 }
691 }
692 }
693
694 fn on_downloaded(
700 &mut self,
701 mut blocks: Vec<SealedBlockWithAccessList<N::Block>>,
702 ) -> Result<Option<TreeEvent>, InsertBlockFatalError> {
703 if blocks.is_empty() {
704 return Ok(None)
706 }
707
708 trace!(target: "engine::tree", block_count = %blocks.len(), "received downloaded blocks");
709 let batch = self.config.max_execute_block_batch_size().min(blocks.len());
710 for block in blocks.drain(..batch) {
711 if let Some(event) = self.on_downloaded_block(block)? {
712 let needs_backfill = event.is_backfill_action();
713 self.on_tree_event(event)?;
714 if needs_backfill {
715 return Ok(None)
717 }
718 }
719 }
720
721 if !blocks.is_empty() {
723 let _ = self.incoming_tx.send(FromEngine::DownloadedBlocks(blocks));
724 }
725
726 Ok(None)
727 }
728
729 #[instrument(
744 level = "debug",
745 target = "engine::tree",
746 skip_all,
747 fields(block_hash = %payload.block_hash(), block_num = %payload.block_number()),
748 )]
749 fn on_new_payload(
750 &mut self,
751 payload: T::ExecutionData,
752 ) -> Result<TreeOutcome<PayloadStatus>, InsertBlockProcessingError> {
753 let _thread_resource_usage =
754 self.metrics.engine.new_payload.measure_thread_resource_usage();
755 trace!(target: "engine::tree", "invoked new payload");
756
757 let start = Instant::now();
759
760 let num_hash = payload.num_hash();
787 let engine_event = ConsensusEngineEvent::BlockReceived(num_hash);
788 self.emit_event(EngineApiEvent::BeaconConsensus(engine_event));
789
790 let block_hash = num_hash.hash;
791
792 if let Some(invalid) = self.find_invalid_ancestor(&payload) {
794 let status = self.handle_invalid_ancestor_payload(payload, invalid)?;
795 return Ok(TreeOutcome::new(status));
796 }
797
798 self.metrics.block_validation.record_payload_validation(start.elapsed().as_secs_f64());
800
801 let mut outcome = if self.backfill_sync_state.is_idle() {
802 self.try_insert_payload(payload)?.into_outcome()
803 } else {
804 TreeOutcome::new(self.try_buffer_payload(payload)?)
805 };
806
807 if outcome.outcome.is_valid() && self.is_sync_target_head(block_hash) {
809 if self.state.tree_state.canonical_block_hash() != block_hash {
811 outcome = outcome.with_event(TreeEvent::TreeAction(TreeAction::MakeCanonical {
812 sync_target_head: block_hash,
813 }));
814 }
815 }
816
817 self.metrics.block_validation.total_duration.record(start.elapsed().as_secs_f64());
819
820 Ok(outcome)
821 }
822
823 #[instrument(level = "debug", target = "engine::tree", skip_all)]
825 fn try_insert_payload(
826 &mut self,
827 payload: T::ExecutionData,
828 ) -> Result<TryInsertPayloadResult, InsertBlockProcessingError> {
829 let block_hash = payload.block_hash();
830 let num_hash = payload.num_hash();
831 let parent_hash = payload.parent_hash();
832 let mut latest_valid_hash = None;
833
834 match self.insert_payload(payload) {
835 Ok(status) => {
836 let (status, already_seen) = match status {
837 InsertPayloadOk::Inserted(BlockStatus::Valid) => {
838 latest_valid_hash = Some(block_hash);
839 self.try_connect_buffered_blocks(num_hash)?;
840 (PayloadStatusEnum::Valid, false)
841 }
842 InsertPayloadOk::AlreadySeen(BlockStatus::Valid) => {
843 latest_valid_hash = Some(block_hash);
844 (PayloadStatusEnum::Valid, true)
845 }
846 InsertPayloadOk::Inserted(BlockStatus::Disconnected { .. }) => {
847 (PayloadStatusEnum::Syncing, false)
848 }
849 InsertPayloadOk::AlreadySeen(BlockStatus::Disconnected { .. }) => {
850 (PayloadStatusEnum::Syncing, true)
852 }
853 };
854
855 Ok(TryInsertPayloadResult {
856 status: PayloadStatus::new(status, latest_valid_hash),
857 already_seen,
858 })
859 }
860 Err(error) => {
861 let status = match error {
862 InsertPayloadError::Block(error) => self.on_insert_block_error(error)?,
863 InsertPayloadError::Payload(error) => self
864 .on_new_payload_error(error, num_hash, parent_hash)
865 .map_err(InsertBlockFatalError::from)?,
866 };
867
868 Ok(TryInsertPayloadResult { status, already_seen: false })
869 }
870 }
871 }
872
873 fn try_buffer_payload(
882 &mut self,
883 payload: T::ExecutionData,
884 ) -> Result<PayloadStatus, InsertBlockProcessingError> {
885 let parent_hash = payload.parent_hash();
886 let num_hash = payload.num_hash();
887
888 match self.payload_validator.convert_payload_to_block(payload) {
889 Ok(block) => {
891 if let Err(error) = self.buffer_block(block) {
892 self.on_insert_block_error(error)
893 } else {
894 Ok(PayloadStatus::from_status(PayloadStatusEnum::Syncing))
895 }
896 }
897 Err(error) => Ok(self
898 .on_new_payload_error(error, num_hash, parent_hash)
899 .map_err(InsertBlockFatalError::from)?),
900 }
901 }
902
903 fn on_new_head(&self, new_head: B256) -> ProviderResult<Option<NewCanonicalChain<N>>> {
910 let Some(new_head_block) = self.state.tree_state.blocks_by_hash.get(&new_head) else {
912 debug!(target: "engine::tree", new_head=?new_head, "New head block not found in inmemory tree state");
913 self.metrics.engine.executed_new_block_cache_miss.increment(1);
914 return Ok(None)
915 };
916
917 let new_head_number = new_head_block.recovered_block().number();
918 let mut current_canonical_number = self.state.tree_state.current_canonical_head.number;
919
920 let mut new_chain = vec![new_head_block.clone()];
921 let mut current_hash = new_head_block.recovered_block().parent_hash();
922 let mut current_number = new_head_number - 1;
923
924 while current_number > current_canonical_number {
929 if let Some(block) = self.state.tree_state.executed_block_by_hash(current_hash).cloned()
930 {
931 current_hash = block.recovered_block().parent_hash();
932 current_number -= 1;
933 new_chain.push(block);
934 } else {
935 warn!(target: "engine::tree", current_hash=?current_hash, "Sidechain block not found in TreeState");
936 return Ok(None)
939 }
940 }
941
942 if current_hash == self.state.tree_state.current_canonical_head.hash {
945 new_chain.reverse();
946
947 return Ok(Some(NewCanonicalChain::Commit { new: new_chain }))
949 }
950
951 let mut old_chain = Vec::new();
953 let mut old_hash = self.state.tree_state.current_canonical_head.hash;
954
955 while current_canonical_number > current_number {
958 let block = self.canonical_block_by_hash(old_hash)?;
959 old_hash = block.recovered_block().parent_hash();
960 old_chain.push(block);
961 current_canonical_number -= 1;
962 }
963
964 debug_assert_eq!(current_number, current_canonical_number);
966
967 while old_hash != current_hash {
970 let block = self.canonical_block_by_hash(old_hash)?;
971 old_hash = block.recovered_block().parent_hash();
972 old_chain.push(block);
973
974 if let Some(block) = self.state.tree_state.executed_block_by_hash(current_hash).cloned()
975 {
976 current_hash = block.recovered_block().parent_hash();
977 new_chain.push(block);
978 } else {
979 warn!(target: "engine::tree", invalid_hash=?current_hash, "New chain block not found in TreeState");
981 return Ok(None)
982 }
983 }
984 new_chain.reverse();
985 old_chain.reverse();
986
987 Ok(Some(NewCanonicalChain::Reorg { new: new_chain, old: old_chain }))
988 }
989
990 fn update_latest_block_to_canonical_ancestor(
1002 &mut self,
1003 canonical_header: &SealedHeader<N::BlockHeader>,
1004 ) -> ProviderResult<()> {
1005 debug!(target: "engine::tree", head = ?canonical_header.num_hash(), "Update latest block to canonical ancestor");
1006 let current_head_number = self.state.tree_state.canonical_block_number();
1007 let new_head_number = canonical_header.number();
1008 let new_head_hash = canonical_header.hash();
1009
1010 self.state.tree_state.set_canonical_head(canonical_header.num_hash());
1012
1013 if new_head_number < current_head_number {
1015 debug!(
1016 target: "engine::tree",
1017 current_head = current_head_number,
1018 new_head = new_head_number,
1019 new_head_hash = ?new_head_hash,
1020 "FCU unwind detected: reverting to canonical ancestor"
1021 );
1022
1023 self.handle_canonical_chain_unwind(current_head_number, canonical_header)
1024 } else {
1025 debug!(
1026 target: "engine::tree",
1027 previous_head = current_head_number,
1028 new_head = new_head_number,
1029 new_head_hash = ?new_head_hash,
1030 "Advancing latest block to canonical ancestor"
1031 );
1032 self.handle_chain_advance_or_same_height(canonical_header)
1033 }
1034 }
1035
1036 fn handle_canonical_chain_unwind(
1039 &self,
1040 current_head_number: u64,
1041 canonical_header: &SealedHeader<N::BlockHeader>,
1042 ) -> ProviderResult<()> {
1043 let new_head_number = canonical_header.number();
1044 debug!(
1045 target: "engine::tree",
1046 from = current_head_number,
1047 to = new_head_number,
1048 "Handling unwind: collecting blocks to remove from in-memory state"
1049 );
1050
1051 let old_blocks =
1053 self.collect_blocks_for_canonical_unwind(new_head_number, current_head_number);
1054
1055 self.apply_canonical_ancestor_via_reorg(canonical_header, old_blocks)
1057 }
1058
1059 fn collect_blocks_for_canonical_unwind(
1061 &self,
1062 new_head_number: u64,
1063 current_head_number: u64,
1064 ) -> Vec<ExecutedBlock<N>> {
1065 let mut old_blocks =
1066 Vec::with_capacity((current_head_number.saturating_sub(new_head_number)) as usize);
1067
1068 for block_num in (new_head_number + 1)..=current_head_number {
1069 if let Some(block_state) = self.canonical_in_memory_state.state_by_number(block_num) {
1070 let executed_block = block_state.block_ref().clone();
1071 old_blocks.push(executed_block);
1072 debug!(
1073 target: "engine::tree",
1074 block_number = block_num,
1075 "Collected block for removal from in-memory state"
1076 );
1077 }
1078 }
1079
1080 if old_blocks.is_empty() {
1081 debug!(
1082 target: "engine::tree",
1083 "No blocks found in memory to remove, will clear and reset state"
1084 );
1085 }
1086
1087 old_blocks
1088 }
1089
1090 fn apply_canonical_ancestor_via_reorg(
1092 &self,
1093 canonical_header: &SealedHeader<N::BlockHeader>,
1094 old_blocks: Vec<ExecutedBlock<N>>,
1095 ) -> ProviderResult<()> {
1096 let new_head_hash = canonical_header.hash();
1097 let new_head_number = canonical_header.number();
1098
1099 let executed_block = self.canonical_block_by_hash(new_head_hash)?;
1101 self.canonical_in_memory_state
1103 .update_chain(NewCanonicalChain::Reorg { new: vec![executed_block], old: old_blocks });
1104
1105 self.canonical_in_memory_state.set_canonical_head(canonical_header.clone());
1108
1109 debug!(
1110 target: "engine::tree",
1111 block_number = new_head_number,
1112 block_hash = ?new_head_hash,
1113 "Successfully loaded canonical ancestor into memory via reorg"
1114 );
1115
1116 Ok(())
1117 }
1118
1119 fn handle_chain_advance_or_same_height(
1121 &self,
1122 canonical_header: &SealedHeader<N::BlockHeader>,
1123 ) -> ProviderResult<()> {
1124 self.ensure_block_in_memory(canonical_header.number(), canonical_header.hash())?;
1126
1127 self.canonical_in_memory_state.set_canonical_head(canonical_header.clone());
1129
1130 Ok(())
1131 }
1132
1133 fn ensure_block_in_memory(&self, block_number: u64, block_hash: B256) -> ProviderResult<()> {
1135 if self.canonical_in_memory_state.state_by_number(block_number).is_some() {
1137 return Ok(());
1138 }
1139
1140 let executed_block = self.canonical_block_by_hash(block_hash)?;
1142 self.canonical_in_memory_state
1143 .update_chain(NewCanonicalChain::Commit { new: vec![executed_block] });
1144
1145 debug!(
1146 target: "engine::tree",
1147 block_number,
1148 block_hash = ?block_hash,
1149 "Added canonical block to in-memory state"
1150 );
1151
1152 Ok(())
1153 }
1154
1155 #[instrument(level = "debug", target = "engine::tree", skip_all, fields(head = % state.head_block_hash, safe = % state.safe_block_hash,finalized = % state.finalized_block_hash))]
1164 fn on_forkchoice_updated(
1165 &mut self,
1166 state: ForkchoiceState,
1167 attrs: Option<T::PayloadAttributes>,
1168 ) -> ProviderResult<TreeOutcome<OnForkChoiceUpdated>> {
1169 trace!(target: "engine::tree", ?attrs, "invoked forkchoice update");
1170
1171 self.record_forkchoice_metrics();
1173
1174 if let Some(early_result) = self.validate_forkchoice_state(state)? {
1176 return Ok(TreeOutcome::new(early_result));
1177 }
1178
1179 if let Some(result) = self.handle_canonical_head(state, &attrs)? {
1181 return Ok(result);
1182 }
1183
1184 if let Some(result) = self.apply_chain_update(state, &attrs)? {
1187 return Ok(result);
1188 }
1189
1190 self.handle_missing_block(state)
1192 }
1193
1194 fn record_forkchoice_metrics(&self) {
1196 self.canonical_in_memory_state.on_forkchoice_update_received();
1197 }
1198
1199 fn validate_forkchoice_state(
1204 &mut self,
1205 state: ForkchoiceState,
1206 ) -> ProviderResult<Option<OnForkChoiceUpdated>> {
1207 if state.head_block_hash.is_zero() {
1208 return Ok(Some(OnForkChoiceUpdated::invalid_state()));
1209 }
1210
1211 let lowest_buffered_ancestor_fcu = self.lowest_buffered_ancestor_or(state.head_block_hash);
1214 if let Some(status) = self.check_invalid_ancestor(lowest_buffered_ancestor_fcu)? {
1215 return Ok(Some(OnForkChoiceUpdated::with_invalid(status)));
1216 }
1217
1218 if !self.backfill_sync_state.is_idle() {
1219 trace!(target: "engine::tree", "Pipeline is syncing, skipping forkchoice update");
1222 return Ok(Some(OnForkChoiceUpdated::syncing()));
1223 }
1224
1225 Ok(None)
1226 }
1227
1228 fn handle_canonical_head(
1234 &mut self,
1235 state: ForkchoiceState,
1236 attrs: &Option<T::PayloadAttributes>, ) -> ProviderResult<Option<TreeOutcome<OnForkChoiceUpdated>>> {
1238 if self.state.tree_state.canonical_block_hash() != state.head_block_hash {
1253 return Ok(None);
1254 }
1255
1256 trace!(target: "engine::tree", "fcu head hash is already canonical");
1257
1258 if let Err(outcome) = self.ensure_consistent_forkchoice_state(state) {
1260 return Ok(Some(TreeOutcome::new(outcome)));
1262 }
1263
1264 self.payload_validator.on_canonical_head_changed(state.head_block_hash, &self.state);
1265
1266 if let Some(attr) = attrs {
1268 let tip = self
1269 .sealed_header_by_hash(self.state.tree_state.canonical_block_hash())?
1270 .ok_or_else(|| {
1271 ProviderError::HeaderNotFound(state.head_block_hash.into())
1274 })?;
1275 let updated = self.process_payload_attributes(attr.clone(), &tip, state);
1277 return Ok(Some(TreeOutcome::new(updated)));
1278 }
1279
1280 Ok(Some(Self::valid_outcome(state)))
1282 }
1283
1284 fn apply_chain_update(
1296 &mut self,
1297 state: ForkchoiceState,
1298 attrs: &Option<T::PayloadAttributes>,
1299 ) -> ProviderResult<Option<TreeOutcome<OnForkChoiceUpdated>>> {
1300 if let Ok(Some(canonical_header)) = self.find_canonical_header(state.head_block_hash) {
1302 debug!(target: "engine::tree", head = canonical_header.number(), "fcu head block is already canonical");
1303
1304 let always_trigger_payload_job = self.engine_kind.is_opstack() ||
1307 self.config.always_process_payload_attributes_on_canonical_head();
1308
1309 if !always_trigger_payload_job &&
1318 self.canonical_in_memory_state
1319 .get_finalized_num_hash()
1320 .is_some_and(|finalized| canonical_header.number() < finalized.number)
1321 {
1322 debug!(target: "engine::tree", head = canonical_header.number(), "rejecting canonical ancestor fcu below the finalized block");
1323 return Ok(Some(TreeOutcome::new(OnForkChoiceUpdated::too_deep_reorg())));
1324 }
1325
1326 if always_trigger_payload_job && self.config.unwind_canonical_header() {
1332 self.update_latest_block_to_canonical_ancestor(&canonical_header)?;
1333 }
1334
1335 if let Some(attr) = attrs {
1340 debug!(target: "engine::tree", head = canonical_header.number(), "handling payload attributes for canonical head");
1341 let updated =
1343 self.process_payload_attributes(attr.clone(), &canonical_header, state);
1344 return Ok(Some(TreeOutcome::new(updated)));
1345 }
1346
1347 return Ok(Some(Self::valid_outcome(state)));
1350 }
1351
1352 if let Some(chain_update) = self.on_new_head(state.head_block_hash)? {
1354 let tip = chain_update.tip().clone_sealed_header();
1355 self.on_canonical_chain_update(chain_update);
1356
1357 if let Err(outcome) = self.ensure_consistent_forkchoice_state(state) {
1359 return Ok(Some(TreeOutcome::new(outcome)));
1361 }
1362
1363 if let Some(attr) = attrs {
1364 let updated = self.process_payload_attributes(attr.clone(), &tip, state);
1366 return Ok(Some(TreeOutcome::new(updated)));
1367 }
1368
1369 return Ok(Some(Self::valid_outcome(state)));
1370 }
1371
1372 Ok(None)
1373 }
1374
1375 fn handle_missing_block(
1380 &self,
1381 state: ForkchoiceState,
1382 ) -> ProviderResult<TreeOutcome<OnForkChoiceUpdated>> {
1383 let target = if self.state.forkchoice_state_tracker.is_empty() &&
1390 !state.safe_block_hash.is_zero() &&
1392 self.find_canonical_header(state.safe_block_hash).ok().flatten().is_none()
1393 {
1394 debug!(target: "engine::tree", "missing safe block on initial FCU, downloading safe block");
1395 state.safe_block_hash
1396 } else {
1397 state.head_block_hash
1398 };
1399
1400 let target = self.lowest_buffered_ancestor_or(target);
1401 trace!(target: "engine::tree", %target, "downloading missing block");
1402
1403 Ok(TreeOutcome::new(OnForkChoiceUpdated::valid(PayloadStatus::from_status(
1404 PayloadStatusEnum::Syncing,
1405 )))
1406 .with_event(TreeEvent::Download(
1407 DownloadRequest::single_block(target)
1408 .with_access_lists(self.should_download_access_lists()),
1409 )))
1410 }
1411
1412 fn remove_blocks(&mut self, new_tip_num: u64) {
1415 debug!(target: "engine::tree", ?new_tip_num, last_persisted_block_number=?self.persistence_state.last_persisted_block.number, "Removing blocks using persistence task");
1416 if new_tip_num < self.persistence_state.last_persisted_block.number {
1417 debug!(target: "engine::tree", ?new_tip_num, "Starting remove blocks job");
1418 self.state.set_pending_sparse_trie_prune(false);
1419 let (tx, rx) = crossbeam_channel::bounded(1);
1420 let _ = self.persistence.remove_blocks_above(new_tip_num, tx);
1421 self.persistence_state.start_remove(new_tip_num, rx);
1422 }
1423 }
1424
1425 fn persist_blocks(&mut self, input: SaveBlocksInput<N>) {
1428 let highest_num_hash = input.last_block();
1429 debug!(target: "engine::tree", count=input.persist_rest_blocks().len(), blocks = ?input.persist_rest_blocks().iter().map(|block| block.recovered_block().num_hash()).collect::<Vec<_>>(), "Persisting blocks");
1430
1431 let (tx, rx) = crossbeam_channel::bounded(1);
1432 let _ = self.persistence.save_blocks(input, tx);
1433
1434 self.persistence_state.start_save(highest_num_hash, rx);
1435 }
1436
1437 fn advance_persistence(&mut self) -> Result<(), AdvancePersistenceError> {
1442 if !self.persistence_state.in_progress() {
1443 let payload_build_active = self.payload_builds.is_active();
1444 if let Some(new_tip_num) = self.find_disk_reorg()? {
1445 self.remove_blocks(new_tip_num)
1446 } else if self.backfill_sync_state.is_pending_revalidation() &&
1447 !payload_build_active &&
1448 self.persistence_state.last_state_trie_persisted_block !=
1449 self.persistence_state.last_persisted_block
1450 {
1451 let Some(input) = self.get_save_blocks_input(PersistTarget::Persisted) else {
1452 return Err(AdvancePersistenceError::StateTrieCatchupUnavailable)
1453 };
1454 self.persist_blocks(input);
1455 } else if self.backfill_sync_state.is_pending_revalidation() && !payload_build_active {
1456 self.revalidate_pending_backfill()?;
1457 } else if let Some(input) = self.get_save_blocks_input(PersistTarget::Threshold) {
1458 self.persist_blocks(input);
1459 }
1460 }
1461
1462 Ok(())
1463 }
1464
1465 fn finish_termination(
1470 &mut self,
1471 pending_termination: oneshot::Sender<()>,
1472 ) -> Result<(), AdvancePersistenceError> {
1473 trace!(target: "engine::tree", "finishing termination, persisting remaining blocks");
1474 let result = self.persist_until_complete();
1475 let _ = pending_termination.send(());
1476 result
1477 }
1478
1479 fn persist_until_complete(&mut self) -> Result<(), AdvancePersistenceError> {
1481 loop {
1482 if let Some((rx, start_time, action)) = self.persistence_state.rx.take() {
1484 debug!(target: "engine::tree", ?action, "waiting for in-flight persistence");
1485 let result = rx.recv().map_err(|_| AdvancePersistenceError::ChannelClosed)?;
1486 self.on_persistence_complete(result, start_time)?;
1487 continue
1488 }
1489
1490 if let Some(new_tip_num) = self.find_disk_reorg()? {
1494 self.remove_blocks(new_tip_num);
1495 continue
1496 }
1497
1498 let Some(input) = self.get_save_blocks_input(PersistTarget::Head) else {
1499 debug!(target: "engine::tree", "persistence complete, signaling termination");
1500 return Ok(())
1501 };
1502
1503 debug!(target: "engine::tree", count = input.persist_rest_blocks().len(), "persisting remaining blocks before shutdown");
1504 self.persist_blocks(input);
1505 }
1506 }
1507
1508 fn try_poll_persistence(&mut self) -> Result<bool, AdvancePersistenceError> {
1512 let Some((rx, start_time, action)) = self.persistence_state.rx.take() else {
1513 return Ok(false);
1514 };
1515
1516 match rx.try_recv() {
1517 Ok(result) => {
1518 self.on_persistence_complete(result, start_time)?;
1519 Ok(true)
1520 }
1521 Err(crossbeam_channel::TryRecvError::Empty) => {
1522 self.persistence_state.rx = Some((rx, start_time, action));
1524 Ok(false)
1525 }
1526 Err(crossbeam_channel::TryRecvError::Disconnected) => {
1527 Err(AdvancePersistenceError::ChannelClosed)
1528 }
1529 }
1530 }
1531
1532 fn on_persistence_complete(
1534 &mut self,
1535 result: PersistenceResult,
1536 start_time: Instant,
1537 ) -> Result<(), AdvancePersistenceError> {
1538 self.metrics.engine.persistence_duration.record(start_time.elapsed());
1539
1540 let PersistenceResult { last_block, last_state_trie_block, commit_duration } = result;
1541 debug_assert!(
1542 last_state_trie_block.number <= last_block.number,
1543 "state/trie frontier cannot exceed the last persisted block"
1544 );
1545
1546 debug!(target: "engine::tree", ?last_block, ?last_state_trie_block, elapsed=?start_time.elapsed(), "Finished persisting, calling finish");
1547 self.persistence_state.finish(last_block, last_state_trie_block);
1548
1549 let last_block_number = last_block.number;
1550
1551 let min_threshold = last_block_number.saturating_sub(CHANGESET_CACHE_RETENTION_BLOCKS);
1555 let eviction_threshold =
1556 if let Some(finalized) = self.canonical_in_memory_state.get_finalized_num_hash() {
1557 finalized.number.min(min_threshold)
1559 } else {
1560 min_threshold
1562 };
1563 debug!(
1564 target: "engine::tree",
1565 last_persisted = last_block_number,
1566 finalized_number = ?self.canonical_in_memory_state.get_finalized_num_hash().map(|f| f.number),
1567 eviction_threshold,
1568 "Evicting changesets below threshold"
1569 );
1570 self.state.tree_state.overlay_manager.evict_cached_changesets(eviction_threshold);
1571
1572 self.on_new_persisted_block()?;
1573
1574 self.purge_timing_stats(last_block_number, commit_duration);
1575
1576 Ok(())
1577 }
1578
1579 fn on_engine_message(
1583 &mut self,
1584 msg: FromEngine<EngineApiRequest<T, N>, N::Block>,
1585 ) -> Result<ops::ControlFlow<()>, InsertBlockFatalError> {
1586 match msg {
1587 FromEngine::Event(event) => match event {
1588 FromOrchestrator::BackfillSyncStarted => {
1589 debug!(target: "engine::tree", "received backfill sync started event");
1590 self.backfill_sync_state = BackfillSyncState::Active;
1591 }
1592 FromOrchestrator::BackfillSyncFinished(ctrl) => {
1593 self.on_backfill_sync_finished(ctrl)?;
1594 }
1595 FromOrchestrator::Terminate { tx } => {
1596 debug!(target: "engine::tree", "received terminate request");
1597 if let Err(err) = self.finish_termination(tx) {
1598 error!(target: "engine::tree", %err, "Termination failed");
1599 }
1600 return Ok(ops::ControlFlow::Break(()))
1601 }
1602 },
1603 FromEngine::Request(request) => {
1604 match request {
1605 EngineApiRequest::InsertExecutedBlock(payload) => {
1606 let block_num_hash = payload.recovered_block.num_hash();
1607 if block_num_hash.number <= self.state.tree_state.canonical_block_number() {
1608 return Ok(ops::ControlFlow::Continue(()))
1610 }
1611
1612 if self.state.tree_state.contains_hash(&block_num_hash.hash) {
1613 return Ok(ops::ControlFlow::Continue(()))
1615 }
1616
1617 debug!(target: "engine::tree", block=?block_num_hash, "inserting already executed block");
1618 let now = Instant::now();
1619
1620 let block = match self.payload_validator.on_inserted_executed_block(payload)
1621 {
1622 Ok(block) => block,
1623 Err(err) => {
1624 warn!(target: "engine::tree", %err, block=?block_num_hash, "Failed to insert already executed block");
1625 return Ok(ops::ControlFlow::Continue(()))
1626 }
1627 };
1628
1629 let is_pending = self.state.tree_state.canonical_block_hash() ==
1630 block.recovered_block().parent_hash();
1631 self.state.tree_state.insert_executed(block.clone());
1632
1633 if is_pending {
1634 debug!(target: "engine::tree", pending=?block_num_hash, "updating pending block");
1635 self.canonical_in_memory_state.set_pending_block(block.clone());
1636 }
1637
1638 self.metrics.engine.inserted_already_executed_blocks.increment(1);
1639 self.emit_event(EngineApiEvent::BeaconConsensus(
1640 ConsensusEngineEvent::CanonicalBlockAdded(block, now.elapsed()),
1641 ));
1642 }
1643 EngineApiRequest::Beacon(request) => {
1644 match request {
1645 BeaconEngineMessage::ForkchoiceUpdated { state, payload_attrs, tx } => {
1646 let has_attrs = payload_attrs.is_some();
1647
1648 let start = Instant::now();
1649 let mut output = self.on_forkchoice_updated(state, payload_attrs);
1650
1651 if let Ok(res) = &mut output {
1652 self.state
1654 .forkchoice_state_tracker
1655 .set_latest(state, res.outcome.forkchoice_status());
1656
1657 self.emit_event(ConsensusEngineEvent::ForkchoiceUpdated(
1659 state,
1660 res.outcome.forkchoice_status(),
1661 ));
1662
1663 self.on_maybe_tree_event(res.event.take())?;
1665 }
1666
1667 if let Err(ref err) = output {
1668 error!(target: "engine::tree", %err, ?state, "Error processing forkchoice update");
1669 }
1670
1671 self.metrics.engine.forkchoice_updated.update_response_metrics(
1672 start,
1673 &mut self.metrics.engine.new_payload.latest_finish_at,
1674 has_attrs,
1675 &output,
1676 );
1677
1678 if let Err(err) =
1679 tx.send(output.map(|o| o.outcome).map_err(Into::into))
1680 {
1681 self.metrics
1682 .engine
1683 .failed_forkchoice_updated_response_deliveries
1684 .increment(1);
1685 warn!(target: "engine::tree", ?state, elapsed=?start.elapsed(), "Failed to deliver forkchoiceUpdated response, receiver dropped (request cancelled): {err:?}");
1686 }
1687 }
1688 BeaconEngineMessage::NewPayload { payload, tx } => {
1689 let start = Instant::now();
1690 let gas_used = payload.gas_used();
1691 let num_hash = payload.num_hash();
1692 let mut output = self.on_new_payload(payload);
1693 self.metrics.engine.new_payload.update_response_metrics(
1694 start,
1695 &mut self.metrics.engine.forkchoice_updated.latest_finish_at,
1696 &output,
1697 gas_used,
1698 );
1699
1700 let maybe_event =
1701 output.as_mut().ok().and_then(|out| out.event.take());
1702
1703 if let Err(err) =
1705 tx.send(output.map(|o| o.outcome).map_err(Into::into))
1706 {
1707 warn!(target: "engine::tree", payload=?num_hash, elapsed=?start.elapsed(), "Failed to deliver newPayload response, receiver dropped (request cancelled): {err:?}");
1708 self.metrics
1709 .engine
1710 .failed_new_payload_response_deliveries
1711 .increment(1);
1712 }
1713
1714 self.on_maybe_tree_event(maybe_event)?;
1716 }
1717 BeaconEngineMessage::RethNewPayload {
1718 payload,
1719 wait_for_persistence,
1720 wait_for_caches,
1721 tx,
1722 enqueued_at,
1723 } => {
1724 debug!(
1725 target: "engine::tree",
1726 wait_for_persistence,
1727 wait_for_caches,
1728 "Processing reth_newPayload"
1729 );
1730
1731 let backpressure_wait = enqueued_at.elapsed();
1732
1733 let explicit_persistence_wait = if wait_for_persistence {
1734 let pending_persistence = self.persistence_state.rx.take();
1735 if let Some((rx, start_time, _action)) = pending_persistence {
1736 let (persistence_tx, persistence_rx) =
1737 std::sync::mpsc::channel();
1738 self.runtime.spawn_blocking_named(
1739 "wait-persist",
1740 move || {
1741 let start = Instant::now();
1742 let result = rx
1743 .recv()
1744 .expect("persistence state channel closed");
1745 let _ = persistence_tx.send((
1746 result,
1747 start_time,
1748 start.elapsed(),
1749 ));
1750 },
1751 );
1752 let (result, start_time, wait_duration) = persistence_rx
1753 .recv()
1754 .expect("persistence result channel closed");
1755 let _ = self.on_persistence_complete(result, start_time);
1756 wait_duration
1757 } else {
1758 Duration::ZERO
1759 }
1760 } else {
1761 Duration::ZERO
1762 };
1763
1764 let cache_wait = wait_for_caches
1765 .then(|| self.payload_validator.wait_for_caches());
1766
1767 let start = Instant::now();
1768 let gas_used = payload.gas_used();
1769 let num_hash = payload.num_hash();
1770 let mut output = self.on_new_payload(payload);
1771 let latency = start.elapsed();
1772 self.metrics.engine.new_payload.update_response_metrics(
1773 start,
1774 &mut self.metrics.engine.forkchoice_updated.latest_finish_at,
1775 &output,
1776 gas_used,
1777 );
1778
1779 let maybe_event =
1780 output.as_mut().ok().and_then(|out| out.event.take());
1781
1782 let timings = NewPayloadTimings {
1783 latency,
1784 persistence_wait: backpressure_wait + explicit_persistence_wait,
1785 execution_cache_wait: cache_wait
1786 .map(|wait| wait.execution_cache),
1787 sparse_trie_wait: cache_wait.map(|wait| wait.sparse_trie),
1788 };
1789 if let Err(err) = tx
1790 .send(output.map(|o| (o.outcome, timings)).map_err(Into::into))
1791 {
1792 error!(
1793 target: "engine::tree",
1794 payload=?num_hash,
1795 elapsed=?latency,
1796 "Failed to send event: {err:?}"
1797 );
1798 self.metrics
1799 .engine
1800 .failed_new_payload_response_deliveries
1801 .increment(1);
1802 }
1803
1804 self.on_maybe_tree_event(maybe_event)?;
1805 }
1806 }
1807 }
1808 }
1809 }
1810 FromEngine::DownloadedBlocks(blocks) => {
1811 if let Some(event) = self.on_downloaded(blocks)? {
1812 self.on_tree_event(event)?;
1813 }
1814 }
1815 }
1816 Ok(ops::ControlFlow::Continue(()))
1817 }
1818
1819 fn on_backfill_sync_finished(
1833 &mut self,
1834 ctrl: ControlFlow,
1835 ) -> Result<(), InsertBlockFatalError> {
1836 debug!(target: "engine::tree", "received backfill sync finished event");
1837 self.backfill_sync_state = BackfillSyncState::Idle;
1838
1839 let backfill_height = if let ControlFlow::Unwind { bad_block, target } = &ctrl {
1841 warn!(target: "engine::tree", invalid_block=?bad_block, "Bad block detected in unwind");
1842 self.state.invalid_headers.insert(**bad_block);
1844
1845 Some(*target)
1847 } else {
1848 ctrl.block_number()
1850 };
1851
1852 let Some(backfill_height) = backfill_height else { return Ok(()) };
1854
1855 let Some(backfill_num_hash) = self
1861 .provider
1862 .block_hash(backfill_height)?
1863 .map(|hash| BlockNumHash { hash, number: backfill_height })
1864 else {
1865 debug!(target: "engine::tree", ?ctrl, "Backfill block not found");
1866 return Ok(())
1867 };
1868
1869 if ctrl.is_unwind() {
1870 self.state.set_pending_sparse_trie_prune(false);
1873 self.state.tree_state.reset(backfill_num_hash)
1874 } else {
1875 self.state.tree_state.remove_until(
1876 backfill_num_hash,
1877 self.persistence_state.last_persisted_block.hash,
1878 Some(backfill_num_hash),
1879 );
1880 }
1881
1882 self.metrics.engine.executed_blocks.set(self.state.tree_state.block_count() as f64);
1883 self.metrics.tree.canonical_chain_height.set(backfill_height as f64);
1884
1885 self.state.buffer.remove_old_blocks(backfill_height);
1887 self.purge_timing_stats(backfill_height, None);
1888 self.canonical_in_memory_state.clear_state();
1891
1892 if let Ok(Some(new_head)) = self.provider.sealed_header(backfill_height) {
1893 self.state.tree_state.set_canonical_head(new_head.num_hash());
1896 self.persistence_state.finish(new_head.num_hash(), new_head.num_hash());
1897
1898 self.canonical_in_memory_state.set_canonical_head(new_head);
1900
1901 if !ctrl.is_unwind() {
1905 self.on_canonicalized_sync_target(backfill_num_hash.hash);
1906 }
1907 }
1908
1909 let Some(sync_target_state) = self.state.forkchoice_state_tracker.sync_target_state()
1912 else {
1913 return Ok(())
1914 };
1915 if !self.engine_kind.is_opstack() && sync_target_state.finalized_block_hash.is_zero() {
1916 return Ok(())
1918 }
1919 let target_hash = self.backfill_target_hash(sync_target_state);
1920 if target_hash.is_zero() {
1921 return Ok(())
1922 }
1923 let newest_target = self.state.buffer.block(&target_hash).map(|block| block.number());
1925
1926 if let Some(backfill_target) =
1932 ctrl.block_number().zip(newest_target).and_then(|(progress, target_number)| {
1933 self.backfill_sync_target(progress, target_number, None)
1936 })
1937 {
1938 self.emit_event(EngineApiEvent::BackfillAction(BackfillAction::Start(
1940 backfill_target.into(),
1941 )));
1942 return Ok(())
1943 };
1944
1945 if let Some(lowest_buffered) =
1947 self.state.buffer.lowest_ancestor(&sync_target_state.head_block_hash)
1948 {
1949 let current_head_num = self.state.tree_state.current_canonical_head.number;
1950 let target_head_num = lowest_buffered.number();
1951
1952 if let Some(distance) = self.distance_from_local_tip(current_head_num, target_head_num)
1953 {
1954 debug!(
1956 target: "engine::tree",
1957 %current_head_num,
1958 %target_head_num,
1959 %distance,
1960 "Backfill complete, downloading remaining blocks to reach FCU target"
1961 );
1962
1963 self.emit_event(EngineApiEvent::Download(
1964 DownloadRequest::block_range(lowest_buffered.parent_hash(), distance)
1965 .with_access_lists(self.should_download_access_lists()),
1966 ));
1967 return Ok(());
1968 }
1969 } else {
1970 debug!(
1973 target: "engine::tree",
1974 head_hash = %sync_target_state.head_block_hash,
1975 "Backfill complete but head block not buffered, requesting download"
1976 );
1977 self.emit_event(EngineApiEvent::Download(
1978 DownloadRequest::single_block(sync_target_state.head_block_hash)
1979 .with_access_lists(self.should_download_access_lists()),
1980 ));
1981 return Ok(());
1982 }
1983
1984 self.try_connect_buffered_blocks(self.state.tree_state.current_canonical_head)
1986 }
1987
1988 fn make_canonical(&mut self, target: B256) -> ProviderResult<()> {
1992 if let Some(chain_update) = self.on_new_head(target)? {
1993 self.on_canonical_chain_update(chain_update);
1994 }
1995
1996 self.on_canonicalized_sync_target(target);
1997
1998 Ok(())
1999 }
2000
2001 fn on_canonicalized_sync_target(&mut self, target: B256) {
2003 let Some(sync_target_state) = self
2004 .state
2005 .forkchoice_state_tracker
2006 .sync_target_state()
2007 .filter(|state| state.head_block_hash == target)
2008 else {
2009 return;
2010 };
2011
2012 if let Err(outcome) = self.ensure_consistent_forkchoice_state(sync_target_state) {
2013 debug!(
2014 target: "engine::tree",
2015 head = %sync_target_state.head_block_hash,
2016 safe = %sync_target_state.safe_block_hash,
2017 finalized = %sync_target_state.finalized_block_hash,
2018 ?outcome,
2019 "Canonicalized sync target head before safe/finalized could be applied"
2020 );
2021 return;
2022 }
2023
2024 self.state.forkchoice_state_tracker.promote_sync_target_to_valid(sync_target_state);
2025 }
2026
2027 fn on_maybe_tree_event(&mut self, event: Option<TreeEvent>) -> ProviderResult<()> {
2029 if let Some(event) = event {
2030 self.on_tree_event(event)?;
2031 }
2032
2033 Ok(())
2034 }
2035
2036 fn on_tree_event(&mut self, event: TreeEvent) -> ProviderResult<()> {
2040 match event {
2041 TreeEvent::TreeAction(action) => match action {
2042 TreeAction::MakeCanonical { sync_target_head } => {
2043 self.make_canonical(sync_target_head)?;
2044 }
2045 },
2046 TreeEvent::BackfillAction(action) => {
2047 self.emit_event(EngineApiEvent::BackfillAction(action));
2048 }
2049 TreeEvent::Download(action) => {
2050 self.emit_event(EngineApiEvent::Download(action));
2051 }
2052 }
2053
2054 Ok(())
2055 }
2056
2057 fn purge_timing_stats(&mut self, below_number: u64, commit_duration: Option<Duration>) {
2064 let threshold = self.config.slow_block_threshold();
2065 let check_slow = commit_duration.is_some() && threshold.is_some();
2066
2067 let keys_to_remove: Vec<B256> = self
2069 .execution_timing_stats
2070 .iter()
2071 .filter(|(_, stats)| stats.block_number <= below_number)
2072 .map(|(k, _)| *k)
2073 .collect();
2074
2075 for key in keys_to_remove {
2076 let stats = self.execution_timing_stats.remove(&key).expect("key just found");
2077 if check_slow {
2078 let commit_dur = commit_duration.expect("checked above");
2079 let total_duration =
2081 stats.execution_duration + stats.state_hash_duration + commit_dur;
2082
2083 if total_duration > threshold.expect("checked above") {
2084 self.emit_event(ConsensusEngineEvent::SlowBlock(SlowBlockInfo {
2085 stats,
2086 commit_duration: Some(commit_dur),
2087 total_duration,
2088 }));
2089 }
2090 }
2091 }
2092 }
2093
2094 fn revalidate_pending_backfill(&mut self) -> ProviderResult<()> {
2096 debug_assert!(self.backfill_sync_state.is_pending_revalidation());
2097
2098 let sync_target_state = self.state.forkchoice_state_tracker.sync_target_state();
2099 let backfill_target = if let Some(state) = sync_target_state {
2100 let configured_target = self.backfill_target_hash(state);
2101 let target_hash =
2102 if configured_target.is_zero() { state.head_block_hash } else { configured_target };
2103 let target_number = if let Some(block) = self.state.buffer.block(&target_hash) {
2104 Some(block.number())
2105 } else {
2106 self.sealed_header_by_hash(target_hash)?.map(|header| header.number())
2107 };
2108
2109 target_number.and_then(|target_number| {
2110 self.backfill_sync_target(
2111 self.state.tree_state.canonical_block_number(),
2112 target_number,
2113 None,
2114 )
2115 })
2116 } else {
2117 None
2118 };
2119
2120 if let Some(target) = backfill_target {
2121 self.dispatch_backfill_action(BackfillAction::Start(target.into()));
2122 return Ok(())
2123 }
2124
2125 self.backfill_sync_state = BackfillSyncState::Idle;
2126 debug!(target: "engine::tree", "dropping deferred backfill after re-evaluation");
2127
2128 if let Some(state) = sync_target_state &&
2131 state.head_block_hash != self.state.tree_state.canonical_block_hash()
2132 {
2133 let target = self.lowest_buffered_ancestor_or(state.head_block_hash);
2134 self.send_event(EngineApiEvent::Download(DownloadRequest::single_block(target)));
2135 }
2136
2137 Ok(())
2138 }
2139
2140 fn emit_event(&mut self, event: impl Into<EngineApiEvent<N>>) {
2142 let event = event.into();
2143
2144 if let EngineApiEvent::BackfillAction(action) = event {
2145 debug_assert_eq!(
2146 self.backfill_sync_state,
2147 BackfillSyncState::Idle,
2148 "backfill action should only be emitted when backfill is idle"
2149 );
2150
2151 let persistence_in_progress = self.persistence_state.in_progress();
2152 let state_trie_needs_catchup = self.persistence_state.last_state_trie_persisted_block !=
2153 self.persistence_state.last_persisted_block;
2154 if self.payload_builds.is_active() ||
2155 persistence_in_progress ||
2156 state_trie_needs_catchup
2157 {
2158 debug!(
2162 target: "engine::tree",
2163 last_persisted_block = self.persistence_state.last_persisted_block.number,
2164 last_state_trie_persisted_block = self
2165 .persistence_state
2166 .last_state_trie_persisted_block
2167 .number,
2168 "deferring backfill until persistence and payload jobs drain"
2169 );
2170 self.backfill_sync_state = BackfillSyncState::PendingRevalidation;
2171 return
2172 }
2173
2174 self.dispatch_backfill_action(action);
2175 return
2176 }
2177
2178 self.send_event(event);
2179 }
2180
2181 fn dispatch_backfill_action(&mut self, action: BackfillAction) {
2183 debug_assert!(
2184 self.backfill_sync_state.is_idle() ||
2185 self.backfill_sync_state.is_pending_revalidation(),
2186 "backfill action can only be dispatched while idle or pending revalidation"
2187 );
2188 self.backfill_sync_state = BackfillSyncState::Pending;
2189 self.metrics.engine.pipeline_runs.increment(1);
2190 debug!(target: "engine::tree", "emitting backfill action event");
2191 self.send_event(EngineApiEvent::BackfillAction(action));
2192 }
2193
2194 fn send_event(&self, event: EngineApiEvent<N>) {
2196 let _ = self.outgoing.send(event).inspect_err(
2197 |err| error!(target: "engine::tree", "Failed to send internal event: {err:?}"),
2198 );
2199 }
2200
2201 fn get_save_blocks_input(&self, target: PersistTarget) -> Option<SaveBlocksInput<N>> {
2208 debug_assert!(!self.persistence_state.in_progress());
2211
2212 let prev_partial_state_trie = self.persistence_state.last_state_trie_persisted_block.number;
2213 let prev_db_tip = self.persistence_state.last_persisted_block.number;
2214 let canonical_head_number = self.state.tree_state.canonical_block_number();
2215
2216 let (new_db_tip, new_partial_state_trie) = match target {
2217 PersistTarget::Head => (canonical_head_number, canonical_head_number),
2218 PersistTarget::Persisted => {
2219 debug_assert!(self.backfill_sync_state.is_pending_revalidation());
2222 debug_assert!(!self.payload_builds.is_active());
2223 (prev_db_tip, prev_db_tip)
2224 }
2225 PersistTarget::Threshold => {
2226 if (self.config.suppress_persistence_during_build() &&
2227 self.payload_builds.is_active()) ||
2228 !self.backfill_sync_state.is_idle()
2229 {
2230 return None
2231 }
2232
2233 let persistence_threshold =
2234 usize::try_from(self.config.persistence_threshold()).unwrap_or(usize::MAX);
2235 if self.canonical_in_memory_state.canonical_chain().count() <= persistence_threshold
2236 {
2237 return None
2238 }
2239
2240 let new_db_tip =
2241 canonical_head_number.saturating_sub(self.config.memory_block_buffer_target());
2242 if new_db_tip <= prev_db_tip {
2243 return None
2244 }
2245
2246 let new_partial_state_trie = new_db_tip
2247 .saturating_sub(self.config.num_state_masking_blocks())
2248 .max(prev_partial_state_trie);
2249 (new_db_tip, new_partial_state_trie)
2250 }
2251 };
2252
2253 debug_assert!(
2254 new_db_tip >= prev_db_tip,
2255 "disk reorg must be resolved before saving blocks"
2256 );
2257 debug_assert!(
2258 new_partial_state_trie >= prev_partial_state_trie,
2259 "disk reorg must be resolved before saving state/trie"
2260 );
2261
2262 if new_db_tip == prev_db_tip && new_partial_state_trie == prev_partial_state_trie {
2263 return None
2264 }
2265
2266 let mut blocks = Vec::new();
2267 let mut current_hash = self.state.tree_state.canonical_block_hash();
2268
2269 debug!(
2270 target: "engine::tree",
2271 ?current_hash,
2272 ?prev_partial_state_trie,
2273 ?prev_db_tip,
2274 ?canonical_head_number,
2275 ?new_partial_state_trie,
2276 ?new_db_tip,
2277 target = ?target,
2278 "Returning save input"
2279 );
2280 while let Some(block) = self.state.tree_state.blocks_by_hash.get(¤t_hash) {
2281 if block.recovered_block().number() <= prev_partial_state_trie {
2282 break;
2283 }
2284
2285 if block.recovered_block().number() <= new_db_tip {
2286 blocks.push(block.clone());
2287 }
2288
2289 current_hash = block.recovered_block().parent_hash();
2290 }
2291
2292 blocks.reverse();
2294
2295 Some(SaveBlocksInput::new(
2296 blocks,
2297 prev_db_tip,
2298 prev_partial_state_trie,
2299 new_db_tip,
2300 new_partial_state_trie,
2301 ))
2302 }
2303
2304 fn on_new_persisted_block(&mut self) -> ProviderResult<()> {
2312 let in_memory_persisted_block = self.persistence_state.last_state_trie_persisted_block;
2313
2314 if let Some(remove_above) = self.find_disk_reorg()? {
2317 self.remove_blocks(remove_above);
2318 return Ok(())
2319 }
2320
2321 let finalized = self.state.forkchoice_state_tracker.last_valid_finalized();
2322 self.remove_before(in_memory_persisted_block, finalized)?;
2323 self.canonical_in_memory_state.remove_persisted_blocks_until(
2324 self.persistence_state.last_persisted_block,
2325 in_memory_persisted_block.number,
2326 );
2327 self.state.set_pending_sparse_trie_prune(self.should_prune_sparse_trie());
2328 Ok(())
2329 }
2330
2331 const fn should_prune_sparse_trie(&self) -> bool {
2333 self.config.use_state_root_task()
2334 }
2335
2336 #[instrument(level = "debug", target = "engine::tree", skip(self))]
2343 fn canonical_block_by_hash(&self, hash: B256) -> ProviderResult<ExecutedBlock<N>> {
2344 trace!(target: "engine::tree", ?hash, "Fetching executed block by hash");
2345 if let Some(block) = self.state.tree_state.executed_block_by_hash(hash) {
2347 return Ok(block.clone())
2348 }
2349
2350 let (block, senders) = self
2351 .provider
2352 .sealed_block_with_senders(hash.into(), TransactionVariant::WithHash)?
2353 .ok_or_else(|| ProviderError::HeaderNotFound(hash.into()))?
2354 .split_sealed();
2355 let mut execution_output = self
2356 .provider
2357 .get_state(block.header().number())?
2358 .ok_or_else(|| ProviderError::StateForNumberNotFound(block.header().number()))?;
2359 let bundle_state = execution_output.state();
2360 let hashed_state = self
2364 .provider
2365 .state_by_block_hash(block.parent_hash())?
2366 .hashed_post_state(bundle_state)?;
2367
2368 debug!(
2369 target: "engine::tree",
2370 number = ?block.number(),
2371 "computing block trie updates",
2372 );
2373 let db_provider = self.provider.database_provider_ro()?;
2374 let trie_updates = self
2375 .state
2376 .tree_state
2377 .overlay_manager
2378 .compute_block_trie_updates(&db_provider, block.number())?;
2379
2380 let sorted_hashed_state = Arc::new(hashed_state.into_sorted());
2381 let sorted_trie_updates = Arc::new(trie_updates);
2382 let trie_data = ComputedTrieData::new(sorted_hashed_state, sorted_trie_updates);
2383
2384 let execution_output = Arc::new(BlockExecutionOutput {
2385 state: execution_output.bundle,
2386 result: BlockExecutionResult {
2387 receipts: execution_output.receipts.pop().unwrap_or_default(),
2388 requests: execution_output.requests.pop().unwrap_or_default(),
2389 gas_used: block.gas_used(),
2390 blob_gas_used: block.blob_gas_used().unwrap_or_default(),
2391 },
2392 });
2393
2394 Ok(ExecutedBlock::new(
2395 Arc::new(RecoveredBlock::new_sealed(block, senders)),
2396 execution_output,
2397 trie_data,
2398 ))
2399 }
2400
2401 fn has_block_by_hash(&self, hash: B256) -> ProviderResult<bool> {
2405 if self.state.tree_state.contains_hash(&hash) {
2406 Ok(true)
2407 } else {
2408 self.provider.is_known(hash)
2409 }
2410 }
2411
2412 fn sealed_header_by_hash(
2414 &self,
2415 hash: B256,
2416 ) -> ProviderResult<Option<SealedHeader<N::BlockHeader>>> {
2417 let header = self.state.tree_state.sealed_header_by_hash(&hash);
2419
2420 if header.is_some() {
2421 Ok(header)
2422 } else {
2423 self.provider.sealed_header_by_hash(hash)
2424 }
2425 }
2426
2427 fn lowest_buffered_ancestor_or(&self, hash: B256) -> B256 {
2434 self.state
2435 .buffer
2436 .lowest_ancestor(&hash)
2437 .map(|block| block.parent_hash())
2438 .unwrap_or_else(|| hash)
2439 }
2440
2441 fn should_download_access_lists(&self) -> bool {
2450 self.canonical_in_memory_state.get_canonical_head().block_access_list_hash().is_some()
2451 }
2452
2453 fn latest_valid_hash_for_invalid_payload(
2464 &mut self,
2465 parent_hash: B256,
2466 ) -> ProviderResult<Option<B256>> {
2467 if self.has_block_by_hash(parent_hash)? {
2469 return Ok(Some(parent_hash))
2470 }
2471
2472 let mut current_hash = parent_hash;
2475 let mut current_block = self.state.invalid_headers.get(¤t_hash);
2476 while let Some(block_with_parent) = current_block {
2477 current_hash = block_with_parent.parent;
2478 current_block = self.state.invalid_headers.get(¤t_hash);
2479
2480 if current_block.is_none() && self.has_block_by_hash(current_hash)? {
2483 return Ok(Some(current_hash))
2484 }
2485 }
2486 Ok(None)
2487 }
2488
2489 fn prepare_invalid_response(&mut self, parent_hash: B256) -> ProviderResult<PayloadStatus> {
2493 let valid_parent_hash = match self.sealed_header_by_hash(parent_hash)? {
2494 Some(parent) if !parent.difficulty().is_zero() => Some(B256::ZERO),
2498 Some(_) => Some(parent_hash),
2499 None => self.latest_valid_hash_for_invalid_payload(parent_hash)?,
2500 };
2501
2502 Ok(PayloadStatus::from_status(PayloadStatusEnum::Invalid {
2503 validation_error: PayloadValidationError::LinksToRejectedPayload.to_string(),
2504 })
2505 .with_latest_valid_hash(valid_parent_hash.unwrap_or_default()))
2506 }
2507
2508 fn is_sync_target_head(&self, block_hash: B256) -> bool {
2512 if let Some(target) = self.state.forkchoice_state_tracker.sync_target_state() {
2513 return target.head_block_hash == block_hash
2514 }
2515 false
2516 }
2517
2518 fn is_any_sync_target(&self, block_hash: B256) -> bool {
2522 if let Some(target) = self.state.forkchoice_state_tracker.sync_target_state() {
2523 return target.contains(block_hash)
2524 }
2525 false
2526 }
2527
2528 fn check_invalid_ancestor_with_head(
2534 &mut self,
2535 check: B256,
2536 head: &SealedBlock<N::Block>,
2537 ) -> ProviderResult<Option<PayloadStatus>> {
2538 let Some(header) = self.state.invalid_headers.get(&check) else { return Ok(None) };
2540
2541 Ok(Some(self.on_invalid_new_payload(head.clone(), header)?))
2542 }
2543
2544 fn on_invalid_new_payload(
2546 &mut self,
2547 head: SealedBlock<N::Block>,
2548 invalid: BlockWithParent,
2549 ) -> ProviderResult<PayloadStatus> {
2550 let status = self.prepare_invalid_response(invalid.parent)?;
2552
2553 self.state.invalid_headers.insert_with_invalid_ancestor(head.hash(), invalid);
2555 self.emit_event(ConsensusEngineEvent::InvalidBlock {
2556 block: Box::new(head),
2557 error: PayloadValidationError::LinksToRejectedPayload.to_string(),
2558 });
2559
2560 Ok(status)
2561 }
2562
2563 fn find_invalid_ancestor(&mut self, payload: &T::ExecutionData) -> Option<BlockWithParent> {
2577 let parent_hash = payload.parent_hash();
2578 let block_hash = payload.block_hash();
2579
2580 if let Some(entry) = self.state.invalid_headers.get(&block_hash) {
2582 return Some(entry);
2583 }
2584
2585 let mut lowest_buffered_ancestor = self.lowest_buffered_ancestor_or(block_hash);
2586 if lowest_buffered_ancestor == block_hash {
2587 lowest_buffered_ancestor = parent_hash;
2588 }
2589
2590 self.state.invalid_headers.get(&lowest_buffered_ancestor)
2592 }
2593
2594 fn handle_invalid_ancestor_payload(
2603 &mut self,
2604 payload: T::ExecutionData,
2605 invalid: BlockWithParent,
2606 ) -> Result<PayloadStatus, InsertBlockFatalError> {
2607 let parent_hash = payload.parent_hash();
2608 let num_hash = payload.num_hash();
2609
2610 let block = match self.payload_validator.convert_payload_to_block(payload) {
2616 Ok(block) => block,
2617 Err(error) => return Ok(self.on_new_payload_error(error, num_hash, parent_hash)?),
2618 };
2619
2620 Ok(self.on_invalid_new_payload(block, invalid)?)
2621 }
2622
2623 fn check_invalid_ancestor(&mut self, head: B256) -> ProviderResult<Option<PayloadStatus>> {
2626 let Some(header) = self.state.invalid_headers.get(&head) else { return Ok(None) };
2628
2629 match self.prepare_invalid_response(header.parent) {
2631 Ok(status) => Ok(Some(status)),
2632 Err(err) => {
2633 debug!(target: "engine::tree", %err, "Failed to prepare invalid response for ancestor check");
2634 Ok(Some(PayloadStatus::from_status(PayloadStatusEnum::Invalid {
2636 validation_error: PayloadValidationError::LinksToRejectedPayload.to_string(),
2637 })))
2638 }
2639 }
2640 }
2641
2642 fn validate_block(&self, block: &SealedBlock<N::Block>) -> Result<(), ConsensusError> {
2645 if let Err(e) = self.consensus.validate_header(block.sealed_header()) {
2646 error!(target: "engine::tree", ?block, "Failed to validate header {}: {e}", block.hash());
2647 return Err(e)
2648 }
2649
2650 if let Err(e) = self.consensus.validate_block_pre_execution(block) {
2651 error!(target: "engine::tree", ?block, "Failed to validate block {}: {e}", block.hash());
2652 return Err(e)
2653 }
2654
2655 Ok(())
2656 }
2657
2658 #[instrument(level = "debug", target = "engine::tree", skip(self))]
2660 fn try_connect_buffered_blocks(
2661 &mut self,
2662 parent: BlockNumHash,
2663 ) -> Result<(), InsertBlockFatalError> {
2664 let blocks = self.state.buffer.remove_block_with_children(&parent.hash);
2665
2666 if blocks.is_empty() {
2667 return Ok(())
2669 }
2670
2671 let now = Instant::now();
2672 let block_count = blocks.len();
2673 for child in blocks {
2674 let child_num_hash = child.num_hash();
2675 match self.insert_block(child) {
2676 Ok(res) => {
2677 debug!(target: "engine::tree", child =?child_num_hash, ?res, "connected buffered block");
2678 if self.is_any_sync_target(child_num_hash.hash) &&
2679 matches!(res, InsertPayloadOk::Inserted(BlockStatus::Valid))
2680 {
2681 debug!(target: "engine::tree", child =?child_num_hash, "connected sync target block");
2682 self.make_canonical(child_num_hash.hash)?;
2685 }
2686 }
2687 Err(err) => {
2688 if let InsertPayloadError::Block(err) = err {
2689 debug!(target: "engine::tree", ?err, "failed to connect buffered block to tree");
2690 if let Err(InsertBlockProcessingError::Fatal(fatal)) =
2691 self.on_insert_block_error(err)
2692 {
2693 warn!(target: "engine::tree", %fatal, "fatal error occurred while connecting buffered blocks");
2694 }
2695 }
2696 }
2697 }
2698 }
2699
2700 debug!(target: "engine::tree", elapsed = ?now.elapsed(), %block_count, "connected buffered blocks");
2701 Ok(())
2702 }
2703
2704 fn buffer_block(
2706 &mut self,
2707 block: SealedBlock<N::Block>,
2708 ) -> Result<(), InsertBlockError<N::Block>> {
2709 if let Err(err) = self.validate_block(&block) {
2710 return Err(InsertBlockError::consensus_error(err, block))
2711 }
2712 self.state.buffer.insert_block(block.into());
2713 Ok(())
2714 }
2715
2716 #[inline]
2721 const fn exceeds_backfill_run_threshold(&self, local_tip: u64, block: u64) -> bool {
2722 block > local_tip && block - local_tip > MIN_BLOCKS_FOR_PIPELINE_RUN
2723 }
2724
2725 #[inline]
2728 const fn distance_from_local_tip(&self, local_tip: u64, block: u64) -> Option<u64> {
2729 if block > local_tip {
2730 Some(block - local_tip)
2731 } else {
2732 None
2733 }
2734 }
2735
2736 const fn backfill_target_hash(&self, state: ForkchoiceState) -> B256 {
2744 if self.engine_kind.is_opstack() {
2745 state.head_block_hash
2746 } else {
2747 state.finalized_block_hash
2748 }
2749 }
2750
2751 fn backfill_sync_target(
2758 &self,
2759 canonical_tip_num: u64,
2760 target_block_number: u64,
2761 downloaded_block: Option<BlockNumHash>,
2762 ) -> Option<B256> {
2763 let state = self.state.forkchoice_state_tracker.sync_target_state()?;
2764 let target_hash = self.backfill_target_hash(state);
2765
2766 let exceeds_backfill_threshold = match downloaded_block.as_ref() {
2768 Some(downloaded_block) if downloaded_block.hash == target_hash => {
2770 self.exceeds_backfill_run_threshold(canonical_tip_num, downloaded_block.number)
2771 }
2772 _ => match self.state.buffer.block(&target_hash) {
2773 Some(buffered_target) => {
2775 self.exceeds_backfill_run_threshold(canonical_tip_num, buffered_target.number())
2776 }
2777 None => self.exceeds_backfill_run_threshold(canonical_tip_num, target_block_number),
2779 },
2780 };
2781
2782 if !exceeds_backfill_threshold {
2783 return None
2784 }
2785
2786 match self.provider.header_by_hash_or_number(target_hash.into()) {
2788 Err(err) => {
2789 warn!(target: "engine::tree", %err, "Failed to get backfill target block header");
2790 None
2791 }
2792 Ok(None) if !target_hash.is_zero() => Some(target_hash),
2794 Ok(None) => {
2795 debug!(target: "engine::tree", hash=?state.head_block_hash, "Setting head hash as an optimistic backfill target.");
2808 Some(state.head_block_hash)
2809 }
2810 Ok(Some(_)) => None,
2812 }
2813 }
2814
2815 fn find_disk_reorg(&self) -> ProviderResult<Option<u64>> {
2818 let mut canonical = self.state.tree_state.current_canonical_head;
2819 let mut persisted = self.persistence_state.last_persisted_block;
2820
2821 let parent_num_hash = |num_hash: NumHash| -> ProviderResult<NumHash> {
2822 Ok(self
2823 .sealed_header_by_hash(num_hash.hash)?
2824 .ok_or(ProviderError::BlockHashNotFound(num_hash.hash))?
2825 .parent_num_hash())
2826 };
2827
2828 while canonical.number > persisted.number {
2831 canonical = parent_num_hash(canonical)?;
2832 }
2833
2834 if canonical == persisted {
2836 return Ok(None);
2837 }
2838
2839 while persisted.number > canonical.number {
2845 persisted = parent_num_hash(persisted)?;
2846 }
2847
2848 debug_assert_eq!(persisted.number, canonical.number);
2849
2850 while persisted.hash != canonical.hash {
2852 canonical = parent_num_hash(canonical)?;
2853 persisted = parent_num_hash(persisted)?;
2854 }
2855
2856 debug!(target: "engine::tree", remove_above=persisted.number, "on-disk reorg detected");
2857
2858 Ok(Some(persisted.number))
2859 }
2860
2861 fn on_canonical_chain_update(&mut self, chain_update: NewCanonicalChain<N>) {
2865 trace!(target: "engine::tree", new_blocks = %chain_update.new_block_count(), reorged_blocks = %chain_update.reorged_block_count(), "applying new chain update");
2866 let start = Instant::now();
2867
2868 self.state.tree_state.set_canonical_head(chain_update.tip().num_hash());
2870
2871 let tip = chain_update.tip().clone_sealed_header();
2872 let notification = chain_update.to_chain_notification();
2873
2874 if let NewCanonicalChain::Reorg { new, old } = &chain_update {
2876 let new_first = new.first().map(|first| first.recovered_block().num_hash());
2877 let old_first = old.first().map(|first| first.recovered_block().num_hash());
2878 trace!(target: "engine::tree", ?new_first, ?old_first, "Reorg detected, new and old first blocks");
2879
2880 self.state.set_pending_sparse_trie_prune(false);
2881 self.update_reorg_metrics(old.len(), old_first);
2882 self.reinsert_reorged_blocks(new.clone());
2883 self.reinsert_reorged_blocks(old.clone());
2884 }
2885
2886 self.canonical_in_memory_state.update_chain(chain_update);
2888 self.canonical_in_memory_state.set_canonical_head(tip.clone());
2889 self.payload_validator.on_canonical_head_changed(tip.hash(), &self.state);
2890
2891 self.metrics.tree.canonical_chain_height.set(tip.number() as f64);
2893
2894 self.canonical_in_memory_state.notify_canon_state(notification);
2896
2897 self.emit_event(ConsensusEngineEvent::CanonicalChainCommitted(
2899 Box::new(tip),
2900 start.elapsed(),
2901 ));
2902 }
2903
2904 fn update_reorg_metrics(&self, old_chain_length: usize, first_reorged_block: Option<NumHash>) {
2906 if let Some(first_reorged_block) = first_reorged_block.map(|block| block.number) {
2907 if let Some(finalized) = self.canonical_in_memory_state.get_finalized_num_hash() &&
2908 first_reorged_block <= finalized.number
2909 {
2910 self.metrics.tree.reorgs.finalized.increment(1);
2911 } else if let Some(safe) = self.canonical_in_memory_state.get_safe_num_hash() &&
2912 first_reorged_block <= safe.number
2913 {
2914 self.metrics.tree.reorgs.safe.increment(1);
2915 } else {
2916 self.metrics.tree.reorgs.head.increment(1);
2917 }
2918 } else {
2919 debug_unreachable!("Reorged chain doesn't have any blocks");
2920 }
2921 self.metrics.tree.latest_reorg_depth.set(old_chain_length as f64);
2922 }
2923
2924 fn reinsert_reorged_blocks(&mut self, new_chain: Vec<ExecutedBlock<N>>) {
2926 for block in new_chain {
2927 if self
2928 .state
2929 .tree_state
2930 .executed_block_by_hash(block.recovered_block().hash())
2931 .is_none()
2932 {
2933 trace!(target: "engine::tree", num=?block.recovered_block().number(), hash=?block.recovered_block().hash(), "Reinserting block into tree state");
2934 self.state.tree_state.insert_executed(block);
2935 }
2936 }
2937 }
2938
2939 fn on_disconnected_downloaded_block(
2944 &self,
2945 downloaded_block: BlockNumHash,
2946 missing_parent: BlockNumHash,
2947 head: BlockNumHash,
2948 ) -> Option<TreeEvent> {
2949 if let Some(target) =
2951 self.backfill_sync_target(head.number, missing_parent.number, Some(downloaded_block))
2952 {
2953 trace!(target: "engine::tree", %target, "triggering backfill on downloaded block");
2954 return Some(TreeEvent::BackfillAction(BackfillAction::Start(target.into())));
2955 }
2956
2957 let request = if let Some(distance) =
2967 self.distance_from_local_tip(head.number, missing_parent.number)
2968 {
2969 trace!(target: "engine::tree", %distance, missing=?missing_parent, "downloading missing parent block range");
2970 DownloadRequest::block_range(missing_parent.hash, distance)
2971 } else {
2972 trace!(target: "engine::tree", missing=?missing_parent, "downloading missing parent block");
2973 DownloadRequest::single_block(missing_parent.hash)
2976 };
2977
2978 Some(TreeEvent::Download(request.with_access_lists(self.should_download_access_lists())))
2979 }
2980
2981 fn on_valid_downloaded_block(
2988 &mut self,
2989 block_num_hash: BlockNumHash,
2990 ) -> Result<Option<TreeEvent>, InsertBlockFatalError> {
2991 if let Some(sync_target) = self.state.forkchoice_state_tracker.sync_target_state() &&
2994 sync_target.contains(block_num_hash.hash)
2995 {
2996 debug!(target: "engine::tree", ?sync_target, "appended downloaded sync target block");
2997
2998 if sync_target.head_block_hash == block_num_hash.hash {
2999 return Ok(Some(TreeEvent::TreeAction(TreeAction::MakeCanonical {
3001 sync_target_head: block_num_hash.hash,
3002 })))
3003 }
3004
3005 self.make_canonical(block_num_hash.hash)?;
3009 self.try_connect_buffered_blocks(block_num_hash)?;
3010
3011 if self.state.tree_state.canonical_block_hash() != sync_target.head_block_hash {
3014 let target = self.lowest_buffered_ancestor_or(sync_target.head_block_hash);
3015 trace!(target: "engine::tree", %target, "sync target head not yet reached, downloading head block");
3016 return Ok(Some(TreeEvent::Download(
3017 DownloadRequest::single_block(target)
3018 .with_access_lists(self.should_download_access_lists()),
3019 )))
3020 }
3021
3022 return Ok(None)
3023 }
3024 trace!(target: "engine::tree", "appended downloaded block");
3025 self.try_connect_buffered_blocks(block_num_hash)?;
3026 Ok(None)
3027 }
3028
3029 #[instrument(level = "debug", target = "engine::tree", skip_all, fields(block_hash = %block.hash(), block_num = %block.number()))]
3035 fn on_downloaded_block(
3036 &mut self,
3037 block: SealedBlockWithAccessList<N::Block>,
3038 ) -> Result<Option<TreeEvent>, InsertBlockFatalError> {
3039 let block_num_hash = block.num_hash();
3040 let lowest_buffered_ancestor = self.lowest_buffered_ancestor_or(block_num_hash.hash);
3041 if self.check_invalid_ancestor_with_head(lowest_buffered_ancestor, &block)?.is_some() {
3042 return Ok(None)
3043 }
3044
3045 if !self.backfill_sync_state.is_idle() {
3046 return Ok(None)
3047 }
3048
3049 match self.insert_block(block) {
3051 Ok(InsertPayloadOk::Inserted(BlockStatus::Valid)) => {
3052 return self.on_valid_downloaded_block(block_num_hash);
3053 }
3054 Ok(InsertPayloadOk::Inserted(BlockStatus::Disconnected { head, missing_ancestor })) => {
3055 return Ok(self.on_disconnected_downloaded_block(
3058 block_num_hash,
3059 missing_ancestor,
3060 head,
3061 ))
3062 }
3063 Ok(InsertPayloadOk::AlreadySeen(_)) => {
3064 trace!(target: "engine::tree", "downloaded block already executed");
3065 }
3066 Err(err) => {
3067 if let InsertPayloadError::Block(err) = err {
3068 debug!(target: "engine::tree", err=%err.kind(), "failed to insert downloaded block");
3069 if let Err(InsertBlockProcessingError::Fatal(fatal)) =
3070 self.on_insert_block_error(err)
3071 {
3072 warn!(target: "engine::tree", %fatal, "fatal error occurred while inserting downloaded block");
3073 }
3074 }
3075 }
3076 }
3077 Ok(None)
3078 }
3079
3080 fn insert_payload(
3089 &mut self,
3090 payload: T::ExecutionData,
3091 ) -> Result<InsertPayloadOk, InsertPayloadError<N::Block>> {
3092 self.insert_block_or_payload(
3093 payload.block_with_parent(),
3094 payload,
3095 |validator, payload, ctx| validator.validate_payload(payload, ctx),
3096 |this, payload| Ok(this.payload_validator.convert_payload_to_block(payload)?.into()),
3097 )
3098 }
3099
3100 fn insert_block(
3101 &mut self,
3102 block: SealedBlockWithAccessList<N::Block>,
3103 ) -> Result<InsertPayloadOk, InsertPayloadError<N::Block>> {
3104 self.insert_block_or_payload(
3105 block.block_with_parent(),
3106 block,
3107 |validator, block, ctx| validator.validate_block(block, ctx),
3108 |_, block| Ok(block),
3109 )
3110 }
3111
3112 #[instrument(level = "debug", target = "engine::tree", skip_all, fields(?block_id))]
3129 fn insert_block_or_payload<Input, Err>(
3130 &mut self,
3131 block_id: BlockWithParent,
3132 input: Input,
3133 execute: impl FnOnce(&mut V, Input, TreeCtx<'_, N>) -> Result<ValidationOutput<N>, Err>,
3134 convert_to_block: impl FnOnce(
3135 &mut Self,
3136 Input,
3137 ) -> Result<SealedBlockWithAccessList<N::Block>, Err>,
3138 ) -> Result<InsertPayloadOk, Err>
3139 where
3140 Err: From<InsertBlockError<N::Block>>,
3141 {
3142 let block_insert_start = Instant::now();
3143 let block_num_hash = block_id.block;
3144 debug!(target: "engine::tree", block=?block_num_hash, parent = ?block_id.parent, "Inserting new block into tree");
3145
3146 if self.state.tree_state.contains_hash(&block_num_hash.hash) {
3148 convert_to_block(self, input)?;
3149 return Ok(InsertPayloadOk::AlreadySeen(BlockStatus::Valid));
3150 }
3151
3152 if block_num_hash.number <= self.persistence_state.last_persisted_block.number {
3155 match self.provider.sealed_header_by_hash(block_num_hash.hash) {
3156 Err(err) => {
3157 let block = convert_to_block(self, input)?;
3158 return Err(InsertBlockError::new(block.split().0, err.into()).into());
3159 }
3160 Ok(Some(_)) => {
3161 convert_to_block(self, input)?;
3162 return Ok(InsertPayloadOk::AlreadySeen(BlockStatus::Valid));
3163 }
3164 Ok(None) => {}
3165 }
3166 }
3167
3168 if !self.state.tree_state.contains_hash(&block_id.parent) {
3170 let parent_exists = match self.provider.header(block_id.parent) {
3171 Ok(header) => header.is_some(),
3172 Err(err) => {
3173 let block = convert_to_block(self, input)?;
3174 return Err(InsertBlockError::new(block.split().0, err.into()).into());
3175 }
3176 };
3177
3178 if !parent_exists {
3179 let block = convert_to_block(self, input)?;
3180 let missing_ancestor = self
3183 .state
3184 .buffer
3185 .lowest_ancestor(&block.parent_hash())
3186 .map(|block| block.parent_num_hash())
3187 .unwrap_or_else(|| block.parent_num_hash());
3188
3189 self.state.buffer.insert_block(block);
3190
3191 return Ok(InsertPayloadOk::Inserted(BlockStatus::Disconnected {
3192 head: self.state.tree_state.current_canonical_head,
3193 missing_ancestor,
3194 }))
3195 }
3196 }
3197
3198 let is_fork = block_id.block.number <= self.state.tree_state.current_canonical_head.number;
3203
3204 let ctx = TreeCtx::new(&mut self.state, &self.canonical_in_memory_state);
3205
3206 let start = Instant::now();
3207
3208 let ValidationOutput {
3209 executed_block: executed,
3210 execution_timing_stats: timing_stats,
3211 raw_bal,
3212 } = execute(&mut self.payload_validator, input, ctx)?;
3213
3214 if let Some(raw_bal) = raw_bal {
3215 let num_hash = executed.recovered_block().num_hash();
3216 if let Err(err) = self.provider.bal_store().insert(num_hash, raw_bal) {
3217 warn!(
3218 target: "engine::tree",
3219 ?num_hash,
3220 %err,
3221 "Failed to store validated block access list"
3222 );
3223 }
3224 }
3225
3226 if let Some(stats) = timing_stats {
3229 if let Some(threshold) = self.config.slow_block_threshold() {
3230 let total_duration = stats.execution_duration + stats.state_hash_duration;
3231 if total_duration > threshold {
3232 self.emit_event(ConsensusEngineEvent::SlowBlock(SlowBlockInfo {
3233 stats: stats.clone(),
3234 commit_duration: None,
3235 total_duration,
3236 }));
3237 }
3238 }
3239 self.execution_timing_stats.insert(executed.recovered_block().hash(), stats);
3240 }
3241
3242 let is_pending = self.state.tree_state.canonical_block_hash() ==
3243 executed.recovered_block().parent_hash();
3244 self.state.tree_state.insert_executed(executed.clone());
3245
3246 if is_pending {
3247 debug!(target: "engine::tree", pending=?block_num_hash, "updating pending block");
3248 self.canonical_in_memory_state.set_pending_block(executed.clone());
3249 }
3250
3251 self.metrics.engine.executed_blocks.set(self.state.tree_state.block_count() as f64);
3252
3253 let elapsed = start.elapsed();
3255 let engine_event = if is_fork {
3256 ConsensusEngineEvent::ForkBlockAdded(executed, elapsed)
3257 } else {
3258 ConsensusEngineEvent::CanonicalBlockAdded(executed, elapsed)
3259 };
3260 self.emit_event(EngineApiEvent::BeaconConsensus(engine_event));
3261
3262 self.metrics
3263 .engine
3264 .block_insert_total_duration
3265 .record(block_insert_start.elapsed().as_secs_f64());
3266 debug!(target: "engine::tree", block=?block_num_hash, "Finished inserting block");
3267 Ok(InsertPayloadOk::Inserted(BlockStatus::Valid))
3268 }
3269
3270 fn on_insert_block_error(
3276 &mut self,
3277 error: InsertBlockError<N::Block>,
3278 ) -> Result<PayloadStatus, InsertBlockProcessingError> {
3279 let (block, error) = error.split();
3280
3281 let validation_err = error.ensure_validation_error()?;
3282
3283 warn!(
3287 target: "engine::tree",
3288 invalid_hash=%block.hash(),
3289 invalid_number=block.number(),
3290 %validation_err,
3291 "Invalid block error on new payload",
3292 );
3293 let latest_valid_hash =
3296 if matches!(&validation_err, InsertBlockValidationError::BlockAccessListDecode(_)) {
3297 None
3298 } else {
3299 self.latest_valid_hash_for_invalid_payload(block.parent_hash())
3300 .map_err(InsertBlockFatalError::from)?
3301 };
3302
3303 let is_transient = match &validation_err {
3305 InsertBlockValidationError::Consensus(err) => self.consensus.is_transient_error(err),
3306 _ => false,
3307 };
3308 if is_transient {
3309 warn!(
3310 target: "engine::tree",
3311 invalid_hash=%block.hash(),
3312 invalid_number=block.number(),
3313 %validation_err,
3314 "Skipping invalid header cache insert for transient validation error",
3315 );
3316 } else {
3317 self.state.invalid_headers.insert(block.block_with_parent());
3318 }
3319 self.emit_event(EngineApiEvent::BeaconConsensus(ConsensusEngineEvent::InvalidBlock {
3320 block: Box::new(block),
3321 error: validation_err.to_string(),
3322 }));
3323
3324 Ok(PayloadStatus::new(
3325 PayloadStatusEnum::Invalid { validation_error: validation_err.to_string() },
3326 latest_valid_hash,
3327 ))
3328 }
3329
3330 fn on_new_payload_error(
3332 &mut self,
3333 error: NewPayloadError,
3334 payload_num_hash: NumHash,
3335 parent_hash: B256,
3336 ) -> ProviderResult<PayloadStatus> {
3337 error!(target: "engine::tree", payload=?payload_num_hash, %error, "Invalid payload");
3338 let latest_valid_hash =
3341 if error.is_block_hash_mismatch() || error.is_invalid_versioned_hashes() {
3342 None
3346 } else {
3347 self.latest_valid_hash_for_invalid_payload(parent_hash)?
3348 };
3349
3350 let status = PayloadStatusEnum::from(error);
3351 Ok(PayloadStatus::new(status, latest_valid_hash))
3352 }
3353
3354 pub fn find_canonical_header(
3356 &self,
3357 hash: B256,
3358 ) -> Result<Option<SealedHeader<N::BlockHeader>>, ProviderError> {
3359 let mut canonical = self.canonical_in_memory_state.header_by_hash(hash);
3360
3361 if canonical.is_none() {
3362 canonical = self.provider.header(hash)?.map(|header| SealedHeader::new(header, hash));
3363 }
3364
3365 Ok(canonical)
3366 }
3367
3368 fn update_finalized_block(
3370 &self,
3371 finalized_block_hash: B256,
3372 ) -> Result<(), OnForkChoiceUpdated> {
3373 if finalized_block_hash.is_zero() {
3374 return Ok(())
3375 }
3376
3377 match self.find_canonical_header(finalized_block_hash) {
3378 Ok(None) => {
3379 debug!(target: "engine::tree", "Finalized block not found in canonical chain");
3380 return Err(OnForkChoiceUpdated::invalid_state())
3382 }
3383 Ok(Some(finalized)) => {
3384 if Some(finalized.num_hash()) !=
3385 self.canonical_in_memory_state.get_finalized_num_hash()
3386 {
3387 let _ = self.persistence.save_finalized_block_number(finalized.number());
3390 self.canonical_in_memory_state.set_finalized(finalized.clone());
3391 self.metrics.tree.finalized_block_height.set(finalized.number() as f64);
3393 }
3394 }
3395 Err(err) => {
3396 error!(target: "engine::tree", %err, "Failed to fetch finalized block header");
3397 }
3398 }
3399
3400 Ok(())
3401 }
3402
3403 fn update_safe_block(&self, safe_block_hash: B256) -> Result<(), OnForkChoiceUpdated> {
3405 if safe_block_hash.is_zero() {
3406 return Ok(())
3407 }
3408
3409 match self.find_canonical_header(safe_block_hash) {
3410 Ok(None) => {
3411 debug!(target: "engine::tree", "Safe block not found in canonical chain");
3412 return Err(OnForkChoiceUpdated::invalid_state())
3414 }
3415 Ok(Some(safe)) => {
3416 if Some(safe.num_hash()) != self.canonical_in_memory_state.get_safe_num_hash() {
3417 let _ = self.persistence.save_safe_block_number(safe.number());
3420 self.canonical_in_memory_state.set_safe(safe.clone());
3421 self.metrics.tree.safe_block_height.set(safe.number() as f64);
3423 }
3424 }
3425 Err(err) => {
3426 error!(target: "engine::tree", %err, "Failed to fetch safe block header");
3427 }
3428 }
3429
3430 Ok(())
3431 }
3432
3433 fn ensure_consistent_forkchoice_state(
3442 &self,
3443 state: ForkchoiceState,
3444 ) -> Result<(), OnForkChoiceUpdated> {
3445 self.update_finalized_block(state.finalized_block_hash)?;
3451
3452 self.update_safe_block(state.safe_block_hash)
3458 }
3459
3460 fn process_payload_attributes(
3475 &mut self,
3476 attributes: T::PayloadAttributes,
3477 head: &N::BlockHeader,
3478 state: ForkchoiceState,
3479 ) -> OnForkChoiceUpdated {
3480 if let Err(err) =
3481 self.payload_validator.validate_payload_attributes_against_header(&attributes, head)
3482 {
3483 warn!(target: "engine::tree", %err, ?head, "Invalid payload attributes");
3484 return OnForkChoiceUpdated::invalid_payload_attributes()
3485 }
3486
3487 let payload_build = self.payload_builds.acquire();
3495
3496 let resources = self
3497 .payload_validator
3498 .payload_builder_resources(
3499 state.head_block_hash,
3500 head,
3501 attributes.timestamp(),
3502 &mut self.state,
3503 )
3504 .with_lease(PayloadBuilderLease::new(payload_build));
3505
3506 let pending_payload_id = self.payload_builder.send_new_payload(BuildNewPayload {
3509 parent_hash: state.head_block_hash,
3510 attributes,
3511 resources,
3512 });
3513
3514 OnForkChoiceUpdated::updated_with_pending_payload_id(
3526 PayloadStatus::new(PayloadStatusEnum::Valid, Some(state.head_block_hash)),
3527 pending_payload_id,
3528 )
3529 }
3530
3531 pub(crate) fn remove_before(
3538 &mut self,
3539 upper_bound: BlockNumHash,
3540 finalized_hash: Option<B256>,
3541 ) -> ProviderResult<()> {
3542 let num = if let Some(hash) = finalized_hash {
3545 self.provider.block_number(hash)?.map(|number| BlockNumHash { number, hash })
3546 } else {
3547 None
3548 };
3549
3550 self.state.tree_state.remove_until(
3551 upper_bound,
3552 self.persistence_state.last_persisted_block.hash,
3553 num,
3554 );
3555 Ok(())
3556 }
3557}
3558
3559#[derive(Debug)]
3561enum LoopEvent<T, N>
3562where
3563 N: NodePrimitives,
3564 T: PayloadTypes,
3565{
3566 EngineMessage(FromEngine<EngineApiRequest<T, N>, N::Block>),
3568 PersistenceComplete {
3570 result: PersistenceResult,
3572 start_time: Instant,
3574 },
3575 PayloadBuildFinished,
3577 Disconnected,
3579}
3580
3581#[derive(Clone, Debug)]
3583struct PayloadBuildTracker {
3584 active: Arc<AtomicUsize>,
3585 finished_tx: Sender<()>,
3586}
3587
3588impl PayloadBuildTracker {
3589 fn new() -> (Self, Receiver<()>) {
3591 let (finished_tx, finished_rx) = crossbeam_channel::bounded(1);
3592 (Self { active: Arc::new(AtomicUsize::new(0)), finished_tx }, finished_rx)
3593 }
3594
3595 fn acquire(&self) -> PayloadBuildLease {
3597 self.active.fetch_add(1, Ordering::AcqRel);
3598 PayloadBuildLease {
3599 active: Arc::clone(&self.active),
3600 finished_tx: self.finished_tx.clone(),
3601 }
3602 }
3603
3604 fn is_active(&self) -> bool {
3606 self.active.load(Ordering::Acquire) != 0
3607 }
3608}
3609
3610#[derive(Debug)]
3612struct PayloadBuildLease {
3613 active: Arc<AtomicUsize>,
3614 finished_tx: Sender<()>,
3615}
3616
3617impl Drop for PayloadBuildLease {
3618 fn drop(&mut self) {
3619 let previous = self.active.fetch_sub(1, Ordering::AcqRel);
3620 debug_assert!(previous > 0, "payload build lease count underflow");
3621
3622 if previous == 1 {
3623 let _ = self.finished_tx.try_send(());
3626 }
3627 }
3628}
3629
3630#[derive(Clone, Copy, Debug, Eq, PartialEq)]
3636pub enum BlockStatus {
3637 Valid,
3643 Disconnected {
3645 head: BlockNumHash,
3647 missing_ancestor: BlockNumHash,
3649 },
3650}
3651
3652#[derive(Clone, Copy, Debug, Eq, PartialEq)]
3657pub enum InsertPayloadOk {
3658 AlreadySeen(BlockStatus),
3660 Inserted(BlockStatus),
3662}
3663
3664#[derive(Debug, Clone, Copy)]
3666enum PersistTarget {
3667 Threshold,
3669 Head,
3671 Persisted,
3673}
3674
3675#[derive(Debug, Clone, Copy, Default)]
3677pub struct CacheWaitDurations {
3678 pub execution_cache: Duration,
3680 pub sparse_trie: Duration,
3682}
3683
3684pub trait WaitForCaches {
3689 fn wait_for_caches(&self) -> CacheWaitDurations;
3693}