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, 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::{HashedPostState, KeccakKeyHasher, SortedTrieData};
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 state_root_strategy;
72#[cfg(test)]
73mod tests;
74mod trie_updates;
75mod txpool_prewarm;
76pub mod types;
77
78use crate::{persistence::PersistenceResult, tree::error::AdvancePersistenceError};
79pub use block_buffer::BlockBuffer;
80pub use invalid_headers::InvalidHeaderCache;
81pub use metrics::EngineApiMetrics;
82pub use payload_processor::*;
83pub use payload_validator::{BasicEngineValidator, EngineValidator};
84pub use persistence_state::PersistenceState;
85pub use reth_engine_primitives::TreeConfig;
86pub use reth_execution_cache::{
87 precompile_cache, CachedStateCacheMetrics, CachedStateMetrics, CachedStateMetricsSource,
88 CachedStateProvider, ExecutionCache, PayloadExecutionCache, SavedCache,
89 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
99const CHANGESET_CACHE_RETENTION_BLOCKS: u64 = 64;
104
105#[derive(Debug)]
109pub struct EngineApiTreeState<N: NodePrimitives> {
110 tree_state: TreeState<N>,
112 pending_sparse_trie_prune: bool,
114 forkchoice_state_tracker: ForkchoiceStateTracker,
116 buffer: BlockBuffer<N::Block>,
118 invalid_headers: InvalidHeaderCache,
121}
122
123impl<N: NodePrimitives> EngineApiTreeState<N> {
124 fn new(
125 block_buffer_limit: u32,
126 max_invalid_header_cache_length: u32,
127 invalid_header_hit_eviction_threshold: u8,
128 canonical_block: BlockNumHash,
129 engine_kind: EngineApiKind,
130 overlay_manager: OverlayManager<N>,
131 ) -> Self {
132 Self {
133 invalid_headers: InvalidHeaderCache::new(
134 max_invalid_header_cache_length,
135 invalid_header_hit_eviction_threshold,
136 ),
137 buffer: BlockBuffer::new(block_buffer_limit),
138 tree_state: TreeState::new(canonical_block, engine_kind, overlay_manager),
139 pending_sparse_trie_prune: false,
140 forkchoice_state_tracker: ForkchoiceStateTracker::default(),
141 }
142 }
143
144 pub const fn tree_state(&self) -> &TreeState<N> {
146 &self.tree_state
147 }
148
149 pub const fn pending_sparse_trie_prune(&self) -> bool {
151 self.pending_sparse_trie_prune
152 }
153
154 pub const fn set_pending_sparse_trie_prune(&mut self, pending: bool) {
156 self.pending_sparse_trie_prune = pending;
157 }
158
159 pub fn take_sparse_trie_prune_blocks(
166 &mut self,
167 parent_hash: B256,
168 ) -> Option<Vec<ExecutedBlock<N>>> {
169 if !self.pending_sparse_trie_prune {
170 return None
171 }
172
173 self.pending_sparse_trie_prune = false;
174 Some(
175 self.tree_state
176 .blocks_by_hash(parent_hash)
177 .map(|(_, blocks)| blocks)
178 .unwrap_or_default(),
179 )
180 }
181
182 pub fn has_invalid_header(&mut self, hash: &B256) -> bool {
184 self.invalid_headers.get(hash).is_some()
185 }
186}
187
188#[derive(Debug)]
190pub struct TreeOutcome<T> {
191 pub outcome: T,
193 pub event: Option<TreeEvent>,
195 pub already_seen: bool,
198}
199
200impl<T> TreeOutcome<T> {
201 pub const fn new(outcome: T) -> Self {
203 Self { outcome, event: None, already_seen: false }
204 }
205
206 pub fn with_event(mut self, event: TreeEvent) -> Self {
208 self.event = Some(event);
209 self
210 }
211
212 pub const fn with_already_seen(mut self, value: bool) -> Self {
214 self.already_seen = value;
215 self
216 }
217}
218
219#[derive(Debug)]
221pub struct TryInsertPayloadResult {
222 pub status: PayloadStatus,
226 pub already_seen: bool,
228}
229
230impl TryInsertPayloadResult {
231 #[inline]
233 pub fn into_outcome(self) -> TreeOutcome<PayloadStatus> {
234 TreeOutcome::new(self.status).with_already_seen(self.already_seen)
235 }
236}
237
238#[derive(Debug)]
240pub enum TreeEvent {
241 TreeAction(TreeAction),
243 BackfillAction(BackfillAction),
245 Download(DownloadRequest),
247}
248
249impl TreeEvent {
250 const fn is_backfill_action(&self) -> bool {
252 matches!(self, Self::BackfillAction(_))
253 }
254}
255
256#[derive(Debug)]
258pub enum TreeAction {
259 MakeCanonical {
261 sync_target_head: B256,
263 },
264}
265
266pub struct EngineApiTreeHandler<N, P, T, V, C>
271where
272 N: NodePrimitives,
273 T: PayloadTypes,
274 C: ConfigureEvm<Primitives = N> + 'static,
275{
276 provider: P,
277 consensus: Arc<dyn FullConsensus<N>>,
278 payload_validator: V,
279 state: EngineApiTreeState<N>,
281 incoming_tx: Sender<FromEngine<EngineApiRequest<T, N>, N::Block>>,
290 incoming: Receiver<FromEngine<EngineApiRequest<T, N>, N::Block>>,
292 outgoing: UnboundedSender<EngineApiEvent<N>>,
294 persistence: PersistenceHandle<N>,
296 persistence_state: PersistenceState,
298 backfill_sync_state: BackfillSyncState,
300 canonical_in_memory_state: CanonicalInMemoryState<N>,
303 payload_builder: PayloadBuilderHandle<T>,
306 config: TreeConfig,
308 metrics: EngineApiMetrics,
310 engine_kind: EngineApiKind,
312 evm_config: C,
314 execution_timing_stats: B256Map<Box<ExecutionTimingStats>>,
318 payload_builds: PayloadBuildTracker,
320 payload_build_finished: Receiver<()>,
322 runtime: reth_tasks::Runtime,
324}
325
326impl<N, P: Debug, T: PayloadTypes, V: Debug, C> std::fmt::Debug
327 for EngineApiTreeHandler<N, P, T, V, C>
328where
329 N: NodePrimitives,
330 C: ConfigureEvm<Primitives = N>,
331{
332 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
333 f.debug_struct("EngineApiTreeHandler")
334 .field("provider", &self.provider)
335 .field("consensus", &self.consensus)
336 .field("payload_validator", &self.payload_validator)
337 .field("state", &self.state)
338 .field("incoming_tx", &self.incoming_tx)
339 .field("persistence", &self.persistence)
340 .field("persistence_state", &self.persistence_state)
341 .field("backfill_sync_state", &self.backfill_sync_state)
342 .field("canonical_in_memory_state", &self.canonical_in_memory_state)
343 .field("payload_builder", &self.payload_builder)
344 .field("config", &self.config)
345 .field("metrics", &self.metrics)
346 .field("engine_kind", &self.engine_kind)
347 .field("evm_config", &self.evm_config)
348 .field("execution_timing_stats", &self.execution_timing_stats.len())
349 .field("payload_builds_active", &self.payload_builds.is_active())
350 .field("runtime", &self.runtime)
351 .finish()
352 }
353}
354
355impl<N, P, T, V, C> EngineApiTreeHandler<N, P, T, V, C>
356where
357 N: NodePrimitives,
358 P: DatabaseProviderFactory
359 + BlockReader<Block = N::Block, Header = N::BlockHeader>
360 + StateProviderFactory
361 + StateReader<Receipt = N::Receipt>
362 + BalProvider
363 + Clone
364 + 'static,
365 P::Provider: BlockReader<Block = N::Block, Header = N::BlockHeader>
366 + PruneCheckpointReader
367 + StageCheckpointReader
368 + ChangeSetReader
369 + StorageChangeSetReader
370 + StorageSettingsCache
371 + 'static,
372 C: ConfigureEvm<Primitives = N> + 'static,
373 T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>,
374 V: EngineValidator<T> + WaitForCaches,
375{
376 #[expect(clippy::too_many_arguments)]
378 pub fn new(
379 provider: P,
380 consensus: Arc<dyn FullConsensus<N>>,
381 payload_validator: V,
382 outgoing: UnboundedSender<EngineApiEvent<N>>,
383 state: EngineApiTreeState<N>,
384 canonical_in_memory_state: CanonicalInMemoryState<N>,
385 persistence: PersistenceHandle<N>,
386 persistence_state: PersistenceState,
387 payload_builder: PayloadBuilderHandle<T>,
388 config: TreeConfig,
389 engine_kind: EngineApiKind,
390 evm_config: C,
391 runtime: reth_tasks::Runtime,
392 ) -> Self {
393 let (incoming_tx, incoming) = crossbeam_channel::unbounded();
394
395 let (payload_builds, payload_build_finished) = PayloadBuildTracker::new();
396
397 Self {
398 provider,
399 consensus,
400 payload_validator,
401 incoming,
402 outgoing,
403 persistence,
404 persistence_state,
405 backfill_sync_state: BackfillSyncState::Idle,
406 state,
407 canonical_in_memory_state,
408 payload_builder,
409 config,
410 metrics: Default::default(),
411 incoming_tx,
412 engine_kind,
413 evm_config,
414 execution_timing_stats: B256Map::default(),
415 payload_builds,
416 payload_build_finished,
417 runtime,
418 }
419 }
420
421 #[expect(clippy::complexity)]
427 pub fn spawn_new(
428 provider: P,
429 consensus: Arc<dyn FullConsensus<N>>,
430 payload_validator: V,
431 persistence: PersistenceHandle<N>,
432 payload_builder: PayloadBuilderHandle<T>,
433 canonical_in_memory_state: CanonicalInMemoryState<N>,
434 overlay_manager: OverlayManager<N>,
435 config: TreeConfig,
436 kind: EngineApiKind,
437 evm_config: C,
438 runtime: reth_tasks::Runtime,
439 ) -> (Sender<FromEngine<EngineApiRequest<T, N>, N::Block>>, UnboundedReceiver<EngineApiEvent<N>>)
440 {
441 let best_block_number = provider.best_block_number().unwrap_or(0);
442 let header = provider.sealed_header(best_block_number).ok().flatten().unwrap_or_default();
443
444 let persistence_state = PersistenceState {
445 last_persisted_block: BlockNumHash::new(best_block_number, header.hash()),
446 last_state_trie_persisted_block: BlockNumHash::new(best_block_number, header.hash()),
447 rx: None,
448 };
449
450 let (tx, outgoing) = unbounded_channel();
451 let state = EngineApiTreeState::new(
452 config.block_buffer_limit(),
453 config.max_invalid_header_cache_length(),
454 config.invalid_header_hit_eviction_threshold(),
455 header.num_hash(),
456 kind,
457 overlay_manager,
458 );
459
460 let task = Self::new(
461 provider,
462 consensus,
463 payload_validator,
464 tx,
465 state,
466 canonical_in_memory_state,
467 persistence,
468 persistence_state,
469 payload_builder,
470 config,
471 kind,
472 evm_config,
473 runtime,
474 );
475 let incoming = task.incoming_tx.clone();
476 spawn_os_thread("engine", || {
477 increase_thread_priority();
478 task.run()
479 });
480 (incoming, outgoing)
481 }
482
483 fn valid_outcome(state: ForkchoiceState) -> TreeOutcome<OnForkChoiceUpdated> {
485 TreeOutcome::new(OnForkChoiceUpdated::valid(PayloadStatus::new(
486 PayloadStatusEnum::Valid,
487 Some(state.head_block_hash),
488 )))
489 }
490
491 pub fn sender(&self) -> Sender<FromEngine<EngineApiRequest<T, N>, N::Block>> {
493 self.incoming_tx.clone()
494 }
495
496 fn persistence_gap(&self) -> u64 {
499 self.canonical_in_memory_state.canonical_chain().count() as u64
500 }
501
502 fn persistence_backpressure_gap(&self) -> u64 {
504 self.persistence_gap().saturating_sub(self.config.memory_block_buffer_target())
505 }
506
507 fn should_backpressure(&self) -> bool {
512 self.persistence_state.in_progress() &&
513 self.persistence_backpressure_gap() >=
514 self.config.persistence_backpressure_threshold()
515 }
516
517 pub fn run(mut self) {
521 let terminated = self.run_until_exit();
522 drop(self);
523 if let Some(tx) = terminated {
524 let _ = tx.send(());
525 }
526 }
527
528 fn run_until_exit(&mut self) -> Option<oneshot::Sender<()>> {
529 loop {
530 match self.try_poll_persistence() {
555 Ok(true) => {
556 if let Err(err) = self.advance_persistence() {
557 error!(target: "engine::tree", %err, "Advancing persistence failed");
558 return None
559 }
560 continue;
561 }
562 Ok(false) => {}
563 Err(err) => {
564 error!(target: "engine::tree", %err, "Polling persistence failed");
565 return None
566 }
567 }
568
569 let event = if self.should_backpressure() {
570 self.metrics.engine.backpressure_active.set(1.0);
571 let stall_start = Instant::now();
572 let event = self.wait_for_persistence_event();
573 self.metrics.engine.backpressure_stall_duration.record(stall_start.elapsed());
574 event
575 } else {
576 self.metrics.engine.backpressure_active.set(0.0);
577 self.wait_for_event()
578 };
579
580 match event {
581 LoopEvent::EngineMessage(msg) => {
582 debug!(target: "engine::tree", %msg, "received new engine message");
583 match self.on_engine_message(msg) {
584 Ok(ops::ControlFlow::Break(tx)) => return Some(tx),
585 Ok(ops::ControlFlow::Continue(())) => {}
586 Err(fatal) => {
587 error!(target: "engine::tree", %fatal, "insert block fatal error");
588 return None
589 }
590 }
591 }
592 LoopEvent::PersistenceComplete { result, start_time } => {
593 if let Err(err) = self.on_persistence_complete(result, start_time) {
594 error!(target: "engine::tree", %err, "Persistence complete handling failed");
595 return None
596 }
597 }
598 LoopEvent::PayloadBuildFinished => {}
599 LoopEvent::Disconnected => {
600 error!(target: "engine::tree", "Channel disconnected");
601 return None
602 }
603 }
604
605 if let Err(err) = self.advance_persistence() {
610 error!(target: "engine::tree", %err, "Advancing persistence failed");
611 return None
612 }
613 }
614 }
615
616 fn wait_for_persistence_event(&mut self) -> LoopEvent<T, N> {
622 let maybe_persistence = self.persistence_state.rx.take();
623
624 if let Some((persistence_rx, start_time, _action)) = maybe_persistence {
625 match persistence_rx.recv() {
626 Ok(result) => LoopEvent::PersistenceComplete { result, start_time },
627 Err(_) => LoopEvent::Disconnected,
628 }
629 } else {
630 self.wait_for_event()
631 }
632 }
633
634 fn wait_for_event(&mut self) -> LoopEvent<T, N> {
639 let maybe_persistence = self.persistence_state.rx.take();
641
642 if let Some((persistence_rx, start_time, action)) = maybe_persistence {
643 crossbeam_channel::select_biased! {
646 recv(persistence_rx) -> result => {
647 match result {
649 Ok(result) => LoopEvent::PersistenceComplete {
650 result,
651 start_time,
652 },
653 Err(_) => LoopEvent::Disconnected,
654 }
655 },
656 recv(self.payload_build_finished) -> result => {
657 self.persistence_state.rx = Some((persistence_rx, start_time, action));
659 match result {
660 Ok(()) => LoopEvent::PayloadBuildFinished,
661 Err(_) => LoopEvent::Disconnected,
662 }
663 },
664 recv(self.incoming) -> msg => {
665 self.persistence_state.rx = Some((persistence_rx, start_time, action));
667 match msg {
668 Ok(m) => LoopEvent::EngineMessage(m),
669 Err(_) => LoopEvent::Disconnected,
670 }
671 },
672 }
673 } else {
674 crossbeam_channel::select_biased! {
676 recv(self.payload_build_finished) -> result => match result {
677 Ok(()) => LoopEvent::PayloadBuildFinished,
678 Err(_) => LoopEvent::Disconnected,
679 },
680 recv(self.incoming) -> msg => match msg {
681 Ok(m) => LoopEvent::EngineMessage(m),
682 Err(_) => LoopEvent::Disconnected,
683 },
684 }
685 }
686 }
687
688 fn on_downloaded(
694 &mut self,
695 mut blocks: Vec<SealedBlockWithAccessList<N::Block>>,
696 ) -> Result<Option<TreeEvent>, InsertBlockFatalError> {
697 if blocks.is_empty() {
698 return Ok(None)
700 }
701
702 trace!(target: "engine::tree", block_count = %blocks.len(), "received downloaded blocks");
703 let batch = self.config.max_execute_block_batch_size().min(blocks.len());
704 for block in blocks.drain(..batch) {
705 if let Some(event) = self.on_downloaded_block(block)? {
706 let needs_backfill = event.is_backfill_action();
707 self.on_tree_event(event)?;
708 if needs_backfill {
709 return Ok(None)
711 }
712 }
713 }
714
715 if !blocks.is_empty() {
717 let _ = self.incoming_tx.send(FromEngine::DownloadedBlocks(blocks));
718 }
719
720 Ok(None)
721 }
722
723 #[instrument(
738 level = "debug",
739 target = "engine::tree",
740 skip_all,
741 fields(block_hash = %payload.block_hash(), block_num = %payload.block_number()),
742 )]
743 fn on_new_payload(
744 &mut self,
745 payload: T::ExecutionData,
746 ) -> Result<TreeOutcome<PayloadStatus>, InsertBlockProcessingError> {
747 let _thread_resource_usage =
748 self.metrics.engine.new_payload.measure_thread_resource_usage();
749 trace!(target: "engine::tree", "invoked new payload");
750
751 let start = Instant::now();
753
754 let num_hash = payload.num_hash();
781 let engine_event = ConsensusEngineEvent::BlockReceived(num_hash);
782 self.emit_event(EngineApiEvent::BeaconConsensus(engine_event));
783
784 let block_hash = num_hash.hash;
785
786 if let Some(invalid) = self.find_invalid_ancestor(&payload) {
788 let status = self.handle_invalid_ancestor_payload(payload, invalid)?;
789 return Ok(TreeOutcome::new(status));
790 }
791
792 self.metrics.block_validation.record_payload_validation(start.elapsed().as_secs_f64());
794
795 let mut outcome = if self.backfill_sync_state.is_idle() {
796 self.try_insert_payload(payload)?.into_outcome()
797 } else {
798 TreeOutcome::new(self.try_buffer_payload(payload)?)
799 };
800
801 if outcome.outcome.is_valid() && self.is_sync_target_head(block_hash) {
803 if self.state.tree_state.canonical_block_hash() != block_hash {
805 outcome = outcome.with_event(TreeEvent::TreeAction(TreeAction::MakeCanonical {
806 sync_target_head: block_hash,
807 }));
808 }
809 }
810
811 self.metrics.block_validation.total_duration.record(start.elapsed().as_secs_f64());
813
814 Ok(outcome)
815 }
816
817 #[instrument(level = "debug", target = "engine::tree", skip_all)]
819 fn try_insert_payload(
820 &mut self,
821 payload: T::ExecutionData,
822 ) -> Result<TryInsertPayloadResult, InsertBlockProcessingError> {
823 let block_hash = payload.block_hash();
824 let num_hash = payload.num_hash();
825 let parent_hash = payload.parent_hash();
826 let mut latest_valid_hash = None;
827
828 match self.insert_payload(payload) {
829 Ok(status) => {
830 let (status, already_seen) = match status {
831 InsertPayloadOk::Inserted(BlockStatus::Valid) => {
832 latest_valid_hash = Some(block_hash);
833 self.try_connect_buffered_blocks(num_hash)?;
834 (PayloadStatusEnum::Valid, false)
835 }
836 InsertPayloadOk::AlreadySeen(BlockStatus::Valid) => {
837 latest_valid_hash = Some(block_hash);
838 (PayloadStatusEnum::Valid, true)
839 }
840 InsertPayloadOk::Inserted(BlockStatus::Disconnected { .. }) => {
841 (PayloadStatusEnum::Syncing, false)
842 }
843 InsertPayloadOk::AlreadySeen(BlockStatus::Disconnected { .. }) => {
844 (PayloadStatusEnum::Syncing, true)
846 }
847 };
848
849 Ok(TryInsertPayloadResult {
850 status: PayloadStatus::new(status, latest_valid_hash),
851 already_seen,
852 })
853 }
854 Err(error) => {
855 let status = match error {
856 InsertPayloadError::Block(error) => self.on_insert_block_error(error)?,
857 InsertPayloadError::Payload(error) => self
858 .on_new_payload_error(error, num_hash, parent_hash)
859 .map_err(InsertBlockFatalError::from)?,
860 };
861
862 Ok(TryInsertPayloadResult { status, already_seen: false })
863 }
864 }
865 }
866
867 fn try_buffer_payload(
876 &mut self,
877 payload: T::ExecutionData,
878 ) -> Result<PayloadStatus, InsertBlockProcessingError> {
879 let parent_hash = payload.parent_hash();
880 let num_hash = payload.num_hash();
881
882 match self.payload_validator.convert_payload_to_block(payload) {
883 Ok(block) => {
885 if let Err(error) = self.buffer_block(block) {
886 self.on_insert_block_error(error)
887 } else {
888 Ok(PayloadStatus::from_status(PayloadStatusEnum::Syncing))
889 }
890 }
891 Err(error) => Ok(self
892 .on_new_payload_error(error, num_hash, parent_hash)
893 .map_err(InsertBlockFatalError::from)?),
894 }
895 }
896
897 fn on_new_head(&self, new_head: B256) -> ProviderResult<Option<NewCanonicalChain<N>>> {
904 let Some(new_head_block) = self.state.tree_state.blocks_by_hash.get(&new_head) else {
906 debug!(target: "engine::tree", new_head=?new_head, "New head block not found in inmemory tree state");
907 self.metrics.engine.executed_new_block_cache_miss.increment(1);
908 return Ok(None)
909 };
910
911 let new_head_number = new_head_block.recovered_block().number();
912 let mut current_canonical_number = self.state.tree_state.current_canonical_head.number;
913
914 let mut new_chain = vec![new_head_block.clone()];
915 let mut current_hash = new_head_block.recovered_block().parent_hash();
916 let mut current_number = new_head_number - 1;
917
918 while current_number > current_canonical_number {
923 if let Some(block) = self.state.tree_state.executed_block_by_hash(current_hash).cloned()
924 {
925 current_hash = block.recovered_block().parent_hash();
926 current_number -= 1;
927 new_chain.push(block);
928 } else {
929 warn!(target: "engine::tree", current_hash=?current_hash, "Sidechain block not found in TreeState");
930 return Ok(None)
933 }
934 }
935
936 if current_hash == self.state.tree_state.current_canonical_head.hash {
939 new_chain.reverse();
940
941 return Ok(Some(NewCanonicalChain::Commit { new: new_chain }))
943 }
944
945 let mut old_chain = Vec::new();
947 let mut old_hash = self.state.tree_state.current_canonical_head.hash;
948
949 while current_canonical_number > current_number {
952 let block = self.canonical_block_by_hash(old_hash)?;
953 old_hash = block.recovered_block().parent_hash();
954 old_chain.push(block);
955 current_canonical_number -= 1;
956 }
957
958 debug_assert_eq!(current_number, current_canonical_number);
960
961 while old_hash != current_hash {
964 let block = self.canonical_block_by_hash(old_hash)?;
965 old_hash = block.recovered_block().parent_hash();
966 old_chain.push(block);
967
968 if let Some(block) = self.state.tree_state.executed_block_by_hash(current_hash).cloned()
969 {
970 current_hash = block.recovered_block().parent_hash();
971 new_chain.push(block);
972 } else {
973 warn!(target: "engine::tree", invalid_hash=?current_hash, "New chain block not found in TreeState");
975 return Ok(None)
976 }
977 }
978 new_chain.reverse();
979 old_chain.reverse();
980
981 Ok(Some(NewCanonicalChain::Reorg { new: new_chain, old: old_chain }))
982 }
983
984 fn update_latest_block_to_canonical_ancestor(
996 &mut self,
997 canonical_header: &SealedHeader<N::BlockHeader>,
998 ) -> ProviderResult<()> {
999 debug!(target: "engine::tree", head = ?canonical_header.num_hash(), "Update latest block to canonical ancestor");
1000 let current_head_number = self.state.tree_state.canonical_block_number();
1001 let new_head_number = canonical_header.number();
1002 let new_head_hash = canonical_header.hash();
1003
1004 self.state.tree_state.set_canonical_head(canonical_header.num_hash());
1006
1007 if new_head_number < current_head_number {
1009 debug!(
1010 target: "engine::tree",
1011 current_head = current_head_number,
1012 new_head = new_head_number,
1013 new_head_hash = ?new_head_hash,
1014 "FCU unwind detected: reverting to canonical ancestor"
1015 );
1016
1017 self.handle_canonical_chain_unwind(current_head_number, canonical_header)
1018 } else {
1019 debug!(
1020 target: "engine::tree",
1021 previous_head = current_head_number,
1022 new_head = new_head_number,
1023 new_head_hash = ?new_head_hash,
1024 "Advancing latest block to canonical ancestor"
1025 );
1026 self.handle_chain_advance_or_same_height(canonical_header)
1027 }
1028 }
1029
1030 fn handle_canonical_chain_unwind(
1033 &self,
1034 current_head_number: u64,
1035 canonical_header: &SealedHeader<N::BlockHeader>,
1036 ) -> ProviderResult<()> {
1037 let new_head_number = canonical_header.number();
1038 debug!(
1039 target: "engine::tree",
1040 from = current_head_number,
1041 to = new_head_number,
1042 "Handling unwind: collecting blocks to remove from in-memory state"
1043 );
1044
1045 let old_blocks =
1047 self.collect_blocks_for_canonical_unwind(new_head_number, current_head_number);
1048
1049 self.apply_canonical_ancestor_via_reorg(canonical_header, old_blocks)
1051 }
1052
1053 fn collect_blocks_for_canonical_unwind(
1055 &self,
1056 new_head_number: u64,
1057 current_head_number: u64,
1058 ) -> Vec<ExecutedBlock<N>> {
1059 let mut old_blocks =
1060 Vec::with_capacity((current_head_number.saturating_sub(new_head_number)) as usize);
1061
1062 for block_num in (new_head_number + 1)..=current_head_number {
1063 if let Some(block_state) = self.canonical_in_memory_state.state_by_number(block_num) {
1064 let executed_block = block_state.block_ref().clone();
1065 old_blocks.push(executed_block);
1066 debug!(
1067 target: "engine::tree",
1068 block_number = block_num,
1069 "Collected block for removal from in-memory state"
1070 );
1071 }
1072 }
1073
1074 if old_blocks.is_empty() {
1075 debug!(
1076 target: "engine::tree",
1077 "No blocks found in memory to remove, will clear and reset state"
1078 );
1079 }
1080
1081 old_blocks
1082 }
1083
1084 fn apply_canonical_ancestor_via_reorg(
1086 &self,
1087 canonical_header: &SealedHeader<N::BlockHeader>,
1088 old_blocks: Vec<ExecutedBlock<N>>,
1089 ) -> ProviderResult<()> {
1090 let new_head_hash = canonical_header.hash();
1091 let new_head_number = canonical_header.number();
1092
1093 let executed_block = self.canonical_block_by_hash(new_head_hash)?;
1095 self.canonical_in_memory_state
1097 .update_chain(NewCanonicalChain::Reorg { new: vec![executed_block], old: old_blocks });
1098
1099 self.canonical_in_memory_state.set_canonical_head(canonical_header.clone());
1102
1103 debug!(
1104 target: "engine::tree",
1105 block_number = new_head_number,
1106 block_hash = ?new_head_hash,
1107 "Successfully loaded canonical ancestor into memory via reorg"
1108 );
1109
1110 Ok(())
1111 }
1112
1113 fn handle_chain_advance_or_same_height(
1115 &self,
1116 canonical_header: &SealedHeader<N::BlockHeader>,
1117 ) -> ProviderResult<()> {
1118 self.ensure_block_in_memory(canonical_header.number(), canonical_header.hash())?;
1120
1121 self.canonical_in_memory_state.set_canonical_head(canonical_header.clone());
1123
1124 Ok(())
1125 }
1126
1127 fn ensure_block_in_memory(&self, block_number: u64, block_hash: B256) -> ProviderResult<()> {
1129 if self.canonical_in_memory_state.state_by_number(block_number).is_some() {
1131 return Ok(());
1132 }
1133
1134 let executed_block = self.canonical_block_by_hash(block_hash)?;
1136 self.canonical_in_memory_state
1137 .update_chain(NewCanonicalChain::Commit { new: vec![executed_block] });
1138
1139 debug!(
1140 target: "engine::tree",
1141 block_number,
1142 block_hash = ?block_hash,
1143 "Added canonical block to in-memory state"
1144 );
1145
1146 Ok(())
1147 }
1148
1149 #[instrument(level = "debug", target = "engine::tree", skip_all, fields(head = % state.head_block_hash, safe = % state.safe_block_hash,finalized = % state.finalized_block_hash))]
1158 fn on_forkchoice_updated(
1159 &mut self,
1160 state: ForkchoiceState,
1161 attrs: Option<T::PayloadAttributes>,
1162 ) -> ProviderResult<TreeOutcome<OnForkChoiceUpdated>> {
1163 trace!(target: "engine::tree", ?attrs, "invoked forkchoice update");
1164
1165 self.record_forkchoice_metrics();
1167
1168 if let Some(early_result) = self.validate_forkchoice_state(state)? {
1170 return Ok(TreeOutcome::new(early_result));
1171 }
1172
1173 if let Some(result) = self.handle_canonical_head(state, &attrs)? {
1175 return Ok(result);
1176 }
1177
1178 if let Some(result) = self.apply_chain_update(state, &attrs)? {
1181 return Ok(result);
1182 }
1183
1184 self.handle_missing_block(state)
1186 }
1187
1188 fn record_forkchoice_metrics(&self) {
1190 self.canonical_in_memory_state.on_forkchoice_update_received();
1191 }
1192
1193 fn validate_forkchoice_state(
1198 &mut self,
1199 state: ForkchoiceState,
1200 ) -> ProviderResult<Option<OnForkChoiceUpdated>> {
1201 if state.head_block_hash.is_zero() {
1202 return Ok(Some(OnForkChoiceUpdated::invalid_state()));
1203 }
1204
1205 let lowest_buffered_ancestor_fcu = self.lowest_buffered_ancestor_or(state.head_block_hash);
1208 if let Some(status) = self.check_invalid_ancestor(lowest_buffered_ancestor_fcu)? {
1209 return Ok(Some(OnForkChoiceUpdated::with_invalid(status)));
1210 }
1211
1212 if !self.backfill_sync_state.is_idle() {
1213 if self.backfill_sync_state.is_pending() || self.backfill_sync_state.is_active() {
1217 self.send_event(EngineApiEvent::BackfillAction(BackfillAction::UpdateTarget(
1218 state.head_block_hash,
1219 )));
1220 self.forward_finalized(state.finalized_block_hash);
1221 }
1222 trace!(target: "engine::tree", "Pipeline is syncing, skipping forkchoice update");
1225 return Ok(Some(OnForkChoiceUpdated::syncing()));
1226 }
1227
1228 Ok(None)
1229 }
1230
1231 fn handle_canonical_head(
1237 &mut self,
1238 state: ForkchoiceState,
1239 attrs: &Option<T::PayloadAttributes>, ) -> ProviderResult<Option<TreeOutcome<OnForkChoiceUpdated>>> {
1241 if self.state.tree_state.canonical_block_hash() != state.head_block_hash {
1256 return Ok(None);
1257 }
1258
1259 trace!(target: "engine::tree", "fcu head hash is already canonical");
1260
1261 if !self.is_consistent_forkchoice_state(state, None)? {
1262 return Ok(Some(TreeOutcome::new(OnForkChoiceUpdated::invalid_state())));
1263 }
1264
1265 if let Err(outcome) = self.ensure_consistent_forkchoice_state(state) {
1267 return Ok(Some(TreeOutcome::new(outcome)));
1269 }
1270
1271 self.payload_validator.on_canonical_head_changed(state.head_block_hash, &self.state);
1272
1273 if let Some(attr) = attrs {
1275 let tip = self
1276 .sealed_header_by_hash(self.state.tree_state.canonical_block_hash())?
1277 .ok_or_else(|| {
1278 ProviderError::HeaderNotFound(state.head_block_hash.into())
1281 })?;
1282 let updated = self.process_payload_attributes(attr.clone(), &tip, state);
1284 return Ok(Some(TreeOutcome::new(updated)));
1285 }
1286
1287 Ok(Some(Self::valid_outcome(state)))
1289 }
1290
1291 fn apply_chain_update(
1303 &mut self,
1304 state: ForkchoiceState,
1305 attrs: &Option<T::PayloadAttributes>,
1306 ) -> ProviderResult<Option<TreeOutcome<OnForkChoiceUpdated>>> {
1307 if let Ok(Some(canonical_header)) = self.find_canonical_header(state.head_block_hash) {
1309 debug!(target: "engine::tree", head = canonical_header.number(), "fcu head block is already canonical");
1310
1311 let always_trigger_payload_job = self.engine_kind.is_opstack() ||
1314 self.config.always_process_payload_attributes_on_canonical_head();
1315
1316 if !always_trigger_payload_job &&
1325 self.canonical_in_memory_state
1326 .get_finalized_num_hash()
1327 .is_some_and(|finalized| canonical_header.number() < finalized.number)
1328 {
1329 debug!(target: "engine::tree", head = canonical_header.number(), "rejecting canonical ancestor fcu below the finalized block");
1330 return Ok(Some(TreeOutcome::new(OnForkChoiceUpdated::too_deep_reorg())));
1331 }
1332
1333 if !self.is_consistent_forkchoice_state(state, None)? {
1334 return Ok(Some(TreeOutcome::new(OnForkChoiceUpdated::invalid_state())));
1335 }
1336
1337 if always_trigger_payload_job && self.config.unwind_canonical_header() {
1343 self.update_latest_block_to_canonical_ancestor(&canonical_header)?;
1344 }
1345
1346 if let Some(attr) = attrs {
1351 debug!(target: "engine::tree", head = canonical_header.number(), "handling payload attributes for canonical head");
1352 let updated =
1354 self.process_payload_attributes(attr.clone(), &canonical_header, state);
1355 return Ok(Some(TreeOutcome::new(updated)));
1356 }
1357
1358 return Ok(Some(Self::valid_outcome(state)));
1361 }
1362
1363 if let Some(chain_update) = self.on_new_head(state.head_block_hash)? {
1365 if !self.is_consistent_forkchoice_state(state, Some(&chain_update))? {
1366 return Ok(Some(TreeOutcome::new(OnForkChoiceUpdated::invalid_state())));
1367 }
1368
1369 let tip = chain_update.tip().clone_sealed_header();
1370 self.on_canonical_chain_update(chain_update);
1371
1372 if let Err(outcome) = self.ensure_consistent_forkchoice_state(state) {
1374 return Ok(Some(TreeOutcome::new(outcome)));
1376 }
1377
1378 if let Some(attr) = attrs {
1379 let updated = self.process_payload_attributes(attr.clone(), &tip, state);
1381 return Ok(Some(TreeOutcome::new(updated)));
1382 }
1383
1384 return Ok(Some(Self::valid_outcome(state)));
1385 }
1386
1387 Ok(None)
1388 }
1389
1390 fn handle_missing_block(
1395 &self,
1396 state: ForkchoiceState,
1397 ) -> ProviderResult<TreeOutcome<OnForkChoiceUpdated>> {
1398 let target = if self.state.forkchoice_state_tracker.is_empty() &&
1405 !state.safe_block_hash.is_zero() &&
1407 self.find_canonical_header(state.safe_block_hash).ok().flatten().is_none()
1408 {
1409 debug!(target: "engine::tree", "missing safe block on initial FCU, downloading safe block");
1410 state.safe_block_hash
1411 } else {
1412 state.head_block_hash
1413 };
1414
1415 let target = self.lowest_buffered_ancestor_or(target);
1416 trace!(target: "engine::tree", %target, "downloading missing block");
1417
1418 Ok(TreeOutcome::new(OnForkChoiceUpdated::valid(PayloadStatus::from_status(
1419 PayloadStatusEnum::Syncing,
1420 )))
1421 .with_event(TreeEvent::Download(
1422 DownloadRequest::single_block(target)
1423 .with_access_lists(self.should_download_access_lists()),
1424 )))
1425 }
1426
1427 fn remove_blocks(&mut self, new_tip_num: u64) {
1430 debug!(target: "engine::tree", ?new_tip_num, last_persisted_block_number=?self.persistence_state.last_persisted_block.number, "Removing blocks using persistence task");
1431 if new_tip_num < self.persistence_state.last_persisted_block.number {
1432 debug!(target: "engine::tree", ?new_tip_num, "Starting remove blocks job");
1433 self.state.set_pending_sparse_trie_prune(false);
1434 let (tx, rx) = crossbeam_channel::bounded(1);
1435 let _ = self.persistence.remove_blocks_above(new_tip_num, tx);
1436 self.persistence_state.start_remove(new_tip_num, rx);
1437 }
1438 }
1439
1440 fn persist_blocks(&mut self, input: SaveBlocksInput<N>) {
1443 let highest_num_hash = input.last_block();
1444 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");
1445
1446 let (tx, rx) = crossbeam_channel::bounded(1);
1447 let _ = self.persistence.save_blocks(input, tx);
1448
1449 self.persistence_state.start_save(highest_num_hash, rx);
1450 }
1451
1452 fn advance_persistence(&mut self) -> Result<(), AdvancePersistenceError> {
1457 if !self.persistence_state.in_progress() {
1458 let payload_build_active = self.payload_builds.is_active();
1459 if let Some(new_tip_num) = self.find_disk_reorg()? {
1460 self.remove_blocks(new_tip_num)
1461 } else if self.backfill_sync_state.is_pending_revalidation() &&
1462 !payload_build_active &&
1463 self.persistence_state.last_state_trie_persisted_block !=
1464 self.persistence_state.last_persisted_block
1465 {
1466 let Some(input) = self.get_save_blocks_input(PersistTarget::Persisted) else {
1467 return Err(AdvancePersistenceError::StateTrieCatchupUnavailable)
1468 };
1469 self.persist_blocks(input);
1470 } else if self.backfill_sync_state.is_pending_revalidation() && !payload_build_active {
1471 self.revalidate_pending_backfill()?;
1472 } else if let Some(input) = self.get_save_blocks_input(PersistTarget::Threshold) {
1473 self.persist_blocks(input);
1474 }
1475 }
1476
1477 Ok(())
1478 }
1479
1480 fn persist_until_complete(&mut self) -> Result<(), AdvancePersistenceError> {
1482 loop {
1483 if let Some((rx, start_time, action)) = self.persistence_state.rx.take() {
1485 debug!(target: "engine::tree", ?action, "waiting for in-flight persistence");
1486 let result = rx.recv().map_err(|_| AdvancePersistenceError::ChannelClosed)?;
1487 self.on_persistence_complete(result, start_time)?;
1488 continue
1489 }
1490
1491 if let Some(new_tip_num) = self.find_disk_reorg()? {
1495 self.remove_blocks(new_tip_num);
1496 continue
1497 }
1498
1499 let Some(input) = self.get_save_blocks_input(PersistTarget::Head) else {
1500 debug!(target: "engine::tree", "persistence complete, signaling termination");
1501 return Ok(())
1502 };
1503
1504 debug!(target: "engine::tree", count = input.persist_rest_blocks().len(), "persisting remaining blocks before shutdown");
1505 self.persist_blocks(input);
1506 }
1507 }
1508
1509 fn try_poll_persistence(&mut self) -> Result<bool, AdvancePersistenceError> {
1513 let Some((rx, start_time, action)) = self.persistence_state.rx.take() else {
1514 return Ok(false);
1515 };
1516
1517 match rx.try_recv() {
1518 Ok(result) => {
1519 self.on_persistence_complete(result, start_time)?;
1520 Ok(true)
1521 }
1522 Err(crossbeam_channel::TryRecvError::Empty) => {
1523 self.persistence_state.rx = Some((rx, start_time, action));
1525 Ok(false)
1526 }
1527 Err(crossbeam_channel::TryRecvError::Disconnected) => {
1528 Err(AdvancePersistenceError::ChannelClosed)
1529 }
1530 }
1531 }
1532
1533 fn on_persistence_complete(
1535 &mut self,
1536 result: PersistenceResult,
1537 start_time: Instant,
1538 ) -> Result<(), AdvancePersistenceError> {
1539 self.metrics.engine.persistence_duration.record(start_time.elapsed());
1540
1541 let PersistenceResult { last_block, last_state_trie_block, commit_duration } = result;
1542 debug_assert!(
1543 last_state_trie_block.number <= last_block.number,
1544 "state/trie frontier cannot exceed the last persisted block"
1545 );
1546
1547 debug!(target: "engine::tree", ?last_block, ?last_state_trie_block, elapsed=?start_time.elapsed(), "Finished persisting, calling finish");
1548 self.persistence_state.finish(last_block, last_state_trie_block);
1549
1550 let last_block_number = last_block.number;
1551
1552 let min_threshold = last_block_number.saturating_sub(CHANGESET_CACHE_RETENTION_BLOCKS);
1556 let eviction_threshold =
1557 if let Some(finalized) = self.canonical_in_memory_state.get_finalized_num_hash() {
1558 finalized.number.min(min_threshold)
1560 } else {
1561 min_threshold
1563 };
1564 debug!(
1565 target: "engine::tree",
1566 last_persisted = last_block_number,
1567 finalized_number = ?self.canonical_in_memory_state.get_finalized_num_hash().map(|f| f.number),
1568 eviction_threshold,
1569 "Evicting changesets below threshold"
1570 );
1571 self.state.tree_state.overlay_manager.evict_cached_changesets(eviction_threshold);
1572
1573 self.on_new_persisted_block()?;
1574
1575 self.purge_timing_stats(last_block_number, commit_duration);
1576
1577 Ok(())
1578 }
1579
1580 fn on_engine_message(
1585 &mut self,
1586 msg: FromEngine<EngineApiRequest<T, N>, N::Block>,
1587 ) -> Result<ops::ControlFlow<oneshot::Sender<()>>, InsertBlockFatalError> {
1588 match msg {
1589 FromEngine::Event(event) => match event {
1590 FromOrchestrator::BackfillSyncStarted => {
1591 debug!(target: "engine::tree", "received backfill sync started event");
1592 self.backfill_sync_state = BackfillSyncState::Active;
1593 }
1594 FromOrchestrator::BackfillSyncFinished(ctrl) => {
1595 self.on_backfill_sync_finished(ctrl)?;
1596 }
1597 FromOrchestrator::Terminate { tx } => {
1598 debug!(target: "engine::tree", "received terminate request");
1599 trace!(target: "engine::tree", "finishing termination, persisting remaining blocks");
1600 if let Err(err) = self.persist_until_complete() {
1601 error!(target: "engine::tree", %err, "Termination failed");
1602 }
1603 return Ok(ops::ControlFlow::Break(tx))
1604 }
1605 },
1606 FromEngine::Request(request) => {
1607 match request {
1608 EngineApiRequest::InsertExecutedBlock(payload) => {
1609 let block_num_hash = payload.recovered_block.num_hash();
1610 if block_num_hash.number <= self.state.tree_state.canonical_block_number() {
1611 return Ok(ops::ControlFlow::Continue(()))
1613 }
1614
1615 if self.state.tree_state.contains_hash(&block_num_hash.hash) {
1616 return Ok(ops::ControlFlow::Continue(()))
1618 }
1619
1620 debug!(target: "engine::tree", block=?block_num_hash, "inserting already executed block");
1621 let now = Instant::now();
1622
1623 let block = match self.payload_validator.on_inserted_executed_block(payload)
1624 {
1625 Ok(block) => block,
1626 Err(err) => {
1627 warn!(target: "engine::tree", %err, block=?block_num_hash, "Failed to insert already executed block");
1628 return Ok(ops::ControlFlow::Continue(()))
1629 }
1630 };
1631
1632 let is_pending = self.state.tree_state.canonical_block_hash() ==
1633 block.recovered_block().parent_hash();
1634 self.state.tree_state.insert_executed(block.clone());
1635 self.metrics
1636 .engine
1637 .executed_blocks
1638 .set(self.state.tree_state.block_count() as f64);
1639
1640 if is_pending {
1641 debug!(target: "engine::tree", pending=?block_num_hash, "updating pending block");
1642 self.canonical_in_memory_state.set_pending_block(block.clone());
1643 }
1644
1645 self.metrics.engine.inserted_already_executed_blocks.increment(1);
1646 self.emit_event(EngineApiEvent::BeaconConsensus(
1647 ConsensusEngineEvent::CanonicalBlockAdded(block, now.elapsed()),
1648 ));
1649 }
1650 EngineApiRequest::Beacon(request) => {
1651 match request {
1652 BeaconEngineMessage::ForkchoiceUpdated {
1653 cause,
1654 state,
1655 payload_attrs,
1656 tx,
1657 } => {
1658 let _cause = cause.enter();
1659 let has_attrs = payload_attrs.is_some();
1660
1661 let start = Instant::now();
1662 let mut output = self.on_forkchoice_updated(state, payload_attrs);
1663
1664 if let Ok(res) = &mut output {
1665 self.state
1667 .forkchoice_state_tracker
1668 .set_latest(state, res.outcome.forkchoice_status());
1669
1670 self.emit_event(ConsensusEngineEvent::ForkchoiceUpdated(
1672 state,
1673 res.outcome.forkchoice_status(),
1674 ));
1675
1676 self.on_maybe_tree_event(res.event.take())?;
1678 }
1679
1680 if let Err(ref err) = output {
1681 error!(target: "engine::tree", %err, ?state, "Error processing forkchoice update");
1682 }
1683
1684 self.metrics.engine.forkchoice_updated.update_response_metrics(
1685 start,
1686 &mut self.metrics.engine.new_payload.latest_finish_at,
1687 has_attrs,
1688 &output,
1689 );
1690
1691 if let Err(err) =
1692 tx.send(output.map(|o| o.outcome).map_err(Into::into))
1693 {
1694 self.metrics
1695 .engine
1696 .failed_forkchoice_updated_response_deliveries
1697 .increment(1);
1698 warn!(target: "engine::tree", ?state, elapsed=?start.elapsed(), "Failed to deliver forkchoiceUpdated response, receiver dropped (request cancelled): {err:?}");
1699 }
1700 }
1701 BeaconEngineMessage::NewPayload { cause, payload, tx } => {
1702 let _cause = cause.enter();
1703 let start = Instant::now();
1704 let gas_used = payload.gas_used();
1705 let num_hash = payload.num_hash();
1706 let mut output = self.on_new_payload(payload);
1707 self.metrics.engine.new_payload.update_response_metrics(
1708 start,
1709 &mut self.metrics.engine.forkchoice_updated.latest_finish_at,
1710 &output,
1711 gas_used,
1712 );
1713
1714 let maybe_event =
1715 output.as_mut().ok().and_then(|out| out.event.take());
1716
1717 if let Err(err) =
1719 tx.send(output.map(|o| o.outcome).map_err(Into::into))
1720 {
1721 warn!(target: "engine::tree", payload=?num_hash, elapsed=?start.elapsed(), "Failed to deliver newPayload response, receiver dropped (request cancelled): {err:?}");
1722 self.metrics
1723 .engine
1724 .failed_new_payload_response_deliveries
1725 .increment(1);
1726 }
1727
1728 self.on_maybe_tree_event(maybe_event)?;
1730 }
1731 BeaconEngineMessage::RethNewPayload {
1732 cause,
1733 payload,
1734 wait_for_persistence,
1735 wait_for_caches,
1736 tx,
1737 enqueued_at,
1738 } => {
1739 let _cause = cause.enter();
1740 debug!(
1741 target: "engine::tree",
1742 wait_for_persistence,
1743 wait_for_caches,
1744 "Processing reth_newPayload"
1745 );
1746
1747 let backpressure_wait = enqueued_at.elapsed();
1748
1749 let explicit_persistence_wait = if wait_for_persistence {
1750 let pending_persistence = self.persistence_state.rx.take();
1751 if let Some((rx, start_time, _action)) = pending_persistence {
1752 let (persistence_tx, persistence_rx) =
1753 std::sync::mpsc::channel();
1754 self.runtime.spawn_blocking_named(
1755 "wait-persist",
1756 move || {
1757 let start = Instant::now();
1758 let result = rx
1759 .recv()
1760 .expect("persistence state channel closed");
1761 let _ = persistence_tx.send((
1762 result,
1763 start_time,
1764 start.elapsed(),
1765 ));
1766 },
1767 );
1768 let (result, start_time, wait_duration) = persistence_rx
1769 .recv()
1770 .expect("persistence result channel closed");
1771 let _ = self.on_persistence_complete(result, start_time);
1772 wait_duration
1773 } else {
1774 Duration::ZERO
1775 }
1776 } else {
1777 Duration::ZERO
1778 };
1779
1780 let cache_wait = wait_for_caches
1781 .then(|| self.payload_validator.wait_for_caches());
1782
1783 let start = Instant::now();
1784 let gas_used = payload.gas_used();
1785 let num_hash = payload.num_hash();
1786 let mut output = self.on_new_payload(payload);
1787 let latency = start.elapsed();
1788 self.metrics.engine.new_payload.update_response_metrics(
1789 start,
1790 &mut self.metrics.engine.forkchoice_updated.latest_finish_at,
1791 &output,
1792 gas_used,
1793 );
1794
1795 let maybe_event =
1796 output.as_mut().ok().and_then(|out| out.event.take());
1797
1798 let timings = NewPayloadTimings {
1799 latency,
1800 persistence_wait: backpressure_wait + explicit_persistence_wait,
1801 execution_cache_wait: cache_wait
1802 .map(|wait| wait.execution_cache),
1803 sparse_trie_wait: cache_wait.map(|wait| wait.sparse_trie),
1804 };
1805 if let Err(err) = tx
1806 .send(output.map(|o| (o.outcome, timings)).map_err(Into::into))
1807 {
1808 error!(
1809 target: "engine::tree",
1810 payload=?num_hash,
1811 elapsed=?latency,
1812 "Failed to send event: {err:?}"
1813 );
1814 self.metrics
1815 .engine
1816 .failed_new_payload_response_deliveries
1817 .increment(1);
1818 }
1819
1820 self.on_maybe_tree_event(maybe_event)?;
1821 }
1822 }
1823 }
1824 }
1825 }
1826 FromEngine::DownloadedBlocks(blocks) => {
1827 if let Some(event) = self.on_downloaded(blocks)? {
1828 self.on_tree_event(event)?;
1829 }
1830 }
1831 }
1832 Ok(ops::ControlFlow::Continue(()))
1833 }
1834
1835 fn on_backfill_sync_finished(
1849 &mut self,
1850 ctrl: ControlFlow,
1851 ) -> Result<(), InsertBlockFatalError> {
1852 debug!(target: "engine::tree", "received backfill sync finished event");
1853 self.backfill_sync_state = BackfillSyncState::Idle;
1854
1855 let backfill_height = if let ControlFlow::Unwind { bad_block, target } = &ctrl {
1857 warn!(target: "engine::tree", invalid_block=?bad_block, "Bad block detected in unwind");
1858 self.state.invalid_headers.insert(**bad_block);
1860
1861 Some(*target)
1863 } else {
1864 ctrl.block_number()
1866 };
1867
1868 let Some(backfill_height) = backfill_height else { return Ok(()) };
1870
1871 let Some(backfill_num_hash) = self
1877 .provider
1878 .block_hash(backfill_height)?
1879 .map(|hash| BlockNumHash { hash, number: backfill_height })
1880 else {
1881 debug!(target: "engine::tree", ?ctrl, "Backfill block not found");
1882 return Ok(())
1883 };
1884
1885 if ctrl.is_unwind() {
1886 self.state.set_pending_sparse_trie_prune(false);
1889 self.state.tree_state.reset(backfill_num_hash)
1890 } else {
1891 self.state.tree_state.remove_until(
1892 backfill_num_hash,
1893 self.persistence_state.last_persisted_block.hash,
1894 Some(backfill_num_hash),
1895 );
1896 }
1897
1898 self.metrics.engine.executed_blocks.set(self.state.tree_state.block_count() as f64);
1899 self.metrics.tree.canonical_chain_height.set(backfill_height as f64);
1900
1901 self.state.buffer.remove_old_blocks(backfill_height);
1903 self.purge_timing_stats(backfill_height, None);
1904 self.canonical_in_memory_state.clear_state();
1907
1908 if let Ok(Some(new_head)) = self.provider.sealed_header(backfill_height) {
1909 self.state.tree_state.set_canonical_head(new_head.num_hash());
1912 self.persistence_state.finish(new_head.num_hash(), new_head.num_hash());
1913
1914 self.canonical_in_memory_state.set_canonical_head(new_head);
1916
1917 if !ctrl.is_unwind() {
1921 self.on_canonicalized_sync_target(backfill_num_hash.hash);
1922 }
1923 }
1924
1925 let Some(sync_target_state) = self.state.forkchoice_state_tracker.sync_target_state()
1928 else {
1929 return Ok(())
1930 };
1931 if !self.engine_kind.is_opstack() && sync_target_state.finalized_block_hash.is_zero() {
1932 return Ok(())
1934 }
1935 let target_hash = self.backfill_target_hash(sync_target_state);
1936 if target_hash.is_zero() {
1937 return Ok(())
1938 }
1939 let newest_target = self.state.buffer.block(&target_hash).map(|block| block.number());
1941
1942 if let Some(backfill_target) =
1948 ctrl.block_number().zip(newest_target).and_then(|(progress, target_number)| {
1949 self.backfill_sync_target(progress, target_number, None)
1952 })
1953 {
1954 self.emit_event(EngineApiEvent::BackfillAction(BackfillAction::Start(
1956 backfill_target.into(),
1957 )));
1958 return Ok(())
1959 };
1960
1961 if let Some(lowest_buffered) =
1963 self.state.buffer.lowest_ancestor(&sync_target_state.head_block_hash)
1964 {
1965 let current_head_num = self.state.tree_state.current_canonical_head.number;
1966 let target_head_num = lowest_buffered.number();
1967
1968 if let Some(distance) = self.distance_from_local_tip(current_head_num, target_head_num)
1969 {
1970 debug!(
1972 target: "engine::tree",
1973 %current_head_num,
1974 %target_head_num,
1975 %distance,
1976 "Backfill complete, downloading remaining blocks to reach FCU target"
1977 );
1978
1979 self.emit_event(EngineApiEvent::Download(
1980 DownloadRequest::block_range(lowest_buffered.parent_hash(), distance)
1981 .with_access_lists(self.should_download_access_lists()),
1982 ));
1983 return Ok(());
1984 }
1985 } else {
1986 debug!(
1989 target: "engine::tree",
1990 head_hash = %sync_target_state.head_block_hash,
1991 "Backfill complete but head block not buffered, requesting download"
1992 );
1993 self.emit_event(EngineApiEvent::Download(
1994 DownloadRequest::single_block(sync_target_state.head_block_hash)
1995 .with_access_lists(self.should_download_access_lists()),
1996 ));
1997 return Ok(());
1998 }
1999
2000 self.try_connect_buffered_blocks(self.state.tree_state.current_canonical_head)
2002 }
2003
2004 fn make_canonical(&mut self, target: B256) -> ProviderResult<()> {
2008 if let Some(chain_update) = self.on_new_head(target)? {
2009 self.on_canonical_chain_update(chain_update);
2010 }
2011
2012 self.on_canonicalized_sync_target(target);
2013
2014 Ok(())
2015 }
2016
2017 fn on_canonicalized_sync_target(&mut self, target: B256) {
2019 let Some(sync_target_state) = self
2020 .state
2021 .forkchoice_state_tracker
2022 .sync_target_state()
2023 .filter(|state| state.head_block_hash == target)
2024 else {
2025 return;
2026 };
2027
2028 if let Err(outcome) = self.ensure_consistent_forkchoice_state(sync_target_state) {
2029 debug!(
2030 target: "engine::tree",
2031 head = %sync_target_state.head_block_hash,
2032 safe = %sync_target_state.safe_block_hash,
2033 finalized = %sync_target_state.finalized_block_hash,
2034 ?outcome,
2035 "Canonicalized sync target head before safe/finalized could be applied"
2036 );
2037 return;
2038 }
2039
2040 self.state.forkchoice_state_tracker.promote_sync_target_to_valid(sync_target_state);
2041 }
2042
2043 fn on_maybe_tree_event(&mut self, event: Option<TreeEvent>) -> ProviderResult<()> {
2045 if let Some(event) = event {
2046 self.on_tree_event(event)?;
2047 }
2048
2049 Ok(())
2050 }
2051
2052 fn on_tree_event(&mut self, event: TreeEvent) -> ProviderResult<()> {
2056 match event {
2057 TreeEvent::TreeAction(action) => match action {
2058 TreeAction::MakeCanonical { sync_target_head } => {
2059 self.make_canonical(sync_target_head)?;
2060 }
2061 },
2062 TreeEvent::BackfillAction(action) => {
2063 self.emit_event(EngineApiEvent::BackfillAction(action));
2064 }
2065 TreeEvent::Download(action) => {
2066 self.emit_event(EngineApiEvent::Download(action));
2067 }
2068 }
2069
2070 Ok(())
2071 }
2072
2073 fn purge_timing_stats(&mut self, below_number: u64, commit_duration: Option<Duration>) {
2080 let threshold = self.config.slow_block_threshold();
2081 let check_slow = commit_duration.is_some() && threshold.is_some();
2082
2083 let keys_to_remove: Vec<B256> = self
2085 .execution_timing_stats
2086 .iter()
2087 .filter(|(_, stats)| stats.block_number <= below_number)
2088 .map(|(k, _)| *k)
2089 .collect();
2090
2091 for key in keys_to_remove {
2092 let stats = self.execution_timing_stats.remove(&key).expect("key just found");
2093 if check_slow {
2094 let commit_dur = commit_duration.expect("checked above");
2095 let total_duration =
2097 stats.execution_duration + stats.state_hash_duration + commit_dur;
2098
2099 if total_duration > threshold.expect("checked above") {
2100 self.emit_event(ConsensusEngineEvent::SlowBlock(SlowBlockInfo {
2101 stats,
2102 commit_duration: Some(commit_dur),
2103 total_duration,
2104 }));
2105 }
2106 }
2107 }
2108 }
2109
2110 fn revalidate_pending_backfill(&mut self) -> ProviderResult<()> {
2112 debug_assert!(self.backfill_sync_state.is_pending_revalidation());
2113
2114 let sync_target_state = self.state.forkchoice_state_tracker.sync_target_state();
2115 let backfill_target = if let Some(state) = sync_target_state {
2116 let configured_target = self.backfill_target_hash(state);
2117 let target_hash =
2118 if configured_target.is_zero() { state.head_block_hash } else { configured_target };
2119 let target_number = if let Some(block) = self.state.buffer.block(&target_hash) {
2120 Some(block.number())
2121 } else {
2122 self.sealed_header_by_hash(target_hash)?.map(|header| header.number())
2123 };
2124
2125 target_number.and_then(|target_number| {
2126 self.backfill_sync_target(
2127 self.state.tree_state.canonical_block_number(),
2128 target_number,
2129 None,
2130 )
2131 })
2132 } else {
2133 None
2134 };
2135
2136 if let Some(target) = backfill_target {
2137 self.dispatch_backfill_action(BackfillAction::Start(target.into()));
2138 return Ok(())
2139 }
2140
2141 self.backfill_sync_state = BackfillSyncState::Idle;
2142 debug!(target: "engine::tree", "dropping deferred backfill after re-evaluation");
2143
2144 if let Some(state) = sync_target_state &&
2147 state.head_block_hash != self.state.tree_state.canonical_block_hash()
2148 {
2149 let target = self.lowest_buffered_ancestor_or(state.head_block_hash);
2150 self.send_event(EngineApiEvent::Download(DownloadRequest::single_block(target)));
2151 }
2152
2153 Ok(())
2154 }
2155
2156 fn emit_event(&mut self, event: impl Into<EngineApiEvent<N>>) {
2158 let event = event.into();
2159
2160 if let EngineApiEvent::BackfillAction(action) = event {
2161 debug_assert_eq!(
2162 self.backfill_sync_state,
2163 BackfillSyncState::Idle,
2164 "backfill action should only be emitted when backfill is idle"
2165 );
2166
2167 let persistence_in_progress = self.persistence_state.in_progress();
2168 let state_trie_needs_catchup = self.persistence_state.last_state_trie_persisted_block !=
2169 self.persistence_state.last_persisted_block;
2170 if self.payload_builds.is_active() ||
2171 persistence_in_progress ||
2172 state_trie_needs_catchup
2173 {
2174 debug!(
2178 target: "engine::tree",
2179 last_persisted_block = self.persistence_state.last_persisted_block.number,
2180 last_state_trie_persisted_block = self
2181 .persistence_state
2182 .last_state_trie_persisted_block
2183 .number,
2184 "deferring backfill until persistence and payload jobs drain"
2185 );
2186 self.backfill_sync_state = BackfillSyncState::PendingRevalidation;
2187 return
2188 }
2189
2190 self.dispatch_backfill_action(action);
2191 return
2192 }
2193
2194 self.send_event(event);
2195 }
2196
2197 fn dispatch_backfill_action(&mut self, action: BackfillAction) {
2199 debug_assert!(
2200 self.backfill_sync_state.is_idle() ||
2201 self.backfill_sync_state.is_pending_revalidation(),
2202 "backfill action can only be dispatched while idle or pending revalidation"
2203 );
2204 self.backfill_sync_state = BackfillSyncState::Pending;
2205 self.metrics.engine.pipeline_runs.increment(1);
2206 if let Some(state) = self.state.forkchoice_state_tracker.latest_state() {
2209 self.forward_finalized(state.finalized_block_hash);
2210 }
2211 debug!(target: "engine::tree", "emitting backfill action event");
2212 self.send_event(EngineApiEvent::BackfillAction(action));
2213 }
2214
2215 fn forward_finalized(&self, finalized: B256) {
2217 if !finalized.is_zero() {
2218 self.send_event(EngineApiEvent::BackfillAction(BackfillAction::UpdateFinalized(
2219 finalized,
2220 )));
2221 }
2222 }
2223
2224 fn send_event(&self, event: EngineApiEvent<N>) {
2226 let _ = self.outgoing.send(event).inspect_err(
2227 |err| error!(target: "engine::tree", "Failed to send internal event: {err:?}"),
2228 );
2229 }
2230
2231 fn get_save_blocks_input(&self, target: PersistTarget) -> Option<SaveBlocksInput<N>> {
2238 debug_assert!(!self.persistence_state.in_progress());
2241
2242 let prev_partial_state_trie = self.persistence_state.last_state_trie_persisted_block.number;
2243 let prev_db_tip = self.persistence_state.last_persisted_block.number;
2244 let canonical_head_number = self.state.tree_state.canonical_block_number();
2245
2246 let (new_db_tip, new_partial_state_trie) = match target {
2247 PersistTarget::Head => (canonical_head_number, canonical_head_number),
2248 PersistTarget::Persisted => {
2249 debug_assert!(self.backfill_sync_state.is_pending_revalidation());
2252 debug_assert!(!self.payload_builds.is_active());
2253 (prev_db_tip, prev_db_tip)
2254 }
2255 PersistTarget::Threshold => {
2256 if (self.config.suppress_persistence_during_build() &&
2257 self.payload_builds.is_active()) ||
2258 !self.backfill_sync_state.is_idle()
2259 {
2260 return None
2261 }
2262
2263 let persistence_threshold =
2264 usize::try_from(self.config.persistence_threshold()).unwrap_or(usize::MAX);
2265 if self.canonical_in_memory_state.canonical_chain().count() <= persistence_threshold
2266 {
2267 return None
2268 }
2269
2270 let new_db_tip =
2271 canonical_head_number.saturating_sub(self.config.memory_block_buffer_target());
2272 if new_db_tip <= prev_db_tip {
2273 return None
2274 }
2275
2276 let new_partial_state_trie = new_db_tip
2277 .saturating_sub(self.config.num_state_masking_blocks())
2278 .max(prev_partial_state_trie);
2279 (new_db_tip, new_partial_state_trie)
2280 }
2281 };
2282
2283 debug_assert!(
2284 new_db_tip >= prev_db_tip,
2285 "disk reorg must be resolved before saving blocks"
2286 );
2287 debug_assert!(
2288 new_partial_state_trie >= prev_partial_state_trie,
2289 "disk reorg must be resolved before saving state/trie"
2290 );
2291
2292 if new_db_tip == prev_db_tip && new_partial_state_trie == prev_partial_state_trie {
2293 return None
2294 }
2295
2296 let mut blocks = Vec::new();
2297 let mut current_hash = self.state.tree_state.canonical_block_hash();
2298
2299 debug!(
2300 target: "engine::tree",
2301 ?current_hash,
2302 ?prev_partial_state_trie,
2303 ?prev_db_tip,
2304 ?canonical_head_number,
2305 ?new_partial_state_trie,
2306 ?new_db_tip,
2307 target = ?target,
2308 "Returning save input"
2309 );
2310 while let Some(block) = self.state.tree_state.blocks_by_hash.get(¤t_hash) {
2311 if block.recovered_block().number() <= prev_partial_state_trie {
2312 break;
2313 }
2314
2315 if block.recovered_block().number() <= new_db_tip {
2316 blocks.push(block.clone());
2317 }
2318
2319 current_hash = block.recovered_block().parent_hash();
2320 }
2321
2322 blocks.reverse();
2324
2325 Some(SaveBlocksInput::new(
2326 blocks,
2327 prev_db_tip,
2328 prev_partial_state_trie,
2329 new_db_tip,
2330 new_partial_state_trie,
2331 ))
2332 }
2333
2334 fn on_new_persisted_block(&mut self) -> ProviderResult<()> {
2342 let in_memory_persisted_block = self.persistence_state.last_state_trie_persisted_block;
2343
2344 if let Some(remove_above) = self.find_disk_reorg()? {
2347 self.remove_blocks(remove_above);
2348 return Ok(())
2349 }
2350
2351 let finalized = self.state.forkchoice_state_tracker.last_valid_finalized();
2352 self.canonical_in_memory_state.remove_persisted_blocks_until(
2357 self.persistence_state.last_persisted_block,
2358 in_memory_persisted_block.number,
2359 );
2360 self.remove_before(in_memory_persisted_block, finalized)?;
2361 self.state.tree_state.overlay_manager.precompute_execution_overlay(
2364 self.state.tree_state.canonical_block_hash(),
2365 in_memory_persisted_block.hash,
2366 );
2367 self.state.set_pending_sparse_trie_prune(self.should_prune_sparse_trie());
2368 Ok(())
2369 }
2370
2371 const fn should_prune_sparse_trie(&self) -> bool {
2373 self.config.use_state_root_task()
2374 }
2375
2376 #[instrument(level = "debug", target = "engine::tree", skip(self))]
2383 fn canonical_block_by_hash(&self, hash: B256) -> ProviderResult<ExecutedBlock<N>> {
2384 trace!(target: "engine::tree", ?hash, "Fetching executed block by hash");
2385 if let Some(block) = self.state.tree_state.executed_block_by_hash(hash) {
2387 return Ok(block.clone())
2388 }
2389
2390 let (block, senders) = self
2391 .provider
2392 .sealed_block_with_senders(hash.into(), TransactionVariant::WithHash)?
2393 .ok_or_else(|| ProviderError::HeaderNotFound(hash.into()))?
2394 .split_sealed();
2395 let mut execution_output = self
2396 .provider
2397 .get_state(block.header().number())?
2398 .ok_or_else(|| ProviderError::StateForNumberNotFound(block.header().number()))?;
2399 let bundle_state = execution_output.state();
2400 let hashed_state = if block.parent_hash().is_zero() {
2405 HashedPostState::from_bundle_state::<KeccakKeyHasher>(bundle_state.state())
2406 } else {
2407 self.provider
2408 .state_by_block_hash(block.parent_hash())?
2409 .hashed_post_state(bundle_state)?
2410 };
2411
2412 debug!(
2413 target: "engine::tree",
2414 number = ?block.number(),
2415 "computing block trie updates",
2416 );
2417 let db_provider = self.provider.database_provider_ro()?;
2418 let trie_updates = self
2419 .state
2420 .tree_state
2421 .overlay_manager
2422 .compute_block_trie_updates(&db_provider, block.number())?;
2423
2424 let sorted_hashed_state = Arc::new(hashed_state.into_sorted());
2425 let sorted_trie_updates = Arc::new(trie_updates);
2426 let trie_data = SortedTrieData::new(sorted_hashed_state, sorted_trie_updates);
2427
2428 let execution_output = Arc::new(BlockExecutionOutput {
2429 state: execution_output.bundle,
2430 result: BlockExecutionResult {
2431 receipts: execution_output.receipts.pop().unwrap_or_default(),
2432 requests: execution_output.requests.pop().unwrap_or_default(),
2433 gas_used: block.gas_used(),
2434 blob_gas_used: block.blob_gas_used().unwrap_or_default(),
2435 },
2436 });
2437
2438 Ok(ExecutedBlock::new(
2439 Arc::new(RecoveredBlock::new_sealed(block, senders)),
2440 execution_output,
2441 trie_data,
2442 ))
2443 }
2444
2445 fn has_block_by_hash(&self, hash: B256) -> ProviderResult<bool> {
2449 if self.state.tree_state.contains_hash(&hash) {
2450 Ok(true)
2451 } else {
2452 self.provider.is_known(hash)
2453 }
2454 }
2455
2456 fn sealed_header_by_hash(
2458 &self,
2459 hash: B256,
2460 ) -> ProviderResult<Option<SealedHeader<N::BlockHeader>>> {
2461 let header = self.state.tree_state.sealed_header_by_hash(&hash);
2463
2464 if header.is_some() {
2465 Ok(header)
2466 } else {
2467 self.provider.sealed_header_by_hash(hash)
2468 }
2469 }
2470
2471 fn lowest_buffered_ancestor_or(&self, hash: B256) -> B256 {
2478 self.state
2479 .buffer
2480 .lowest_ancestor(&hash)
2481 .map(|block| block.parent_hash())
2482 .unwrap_or_else(|| hash)
2483 }
2484
2485 fn should_download_access_lists(&self) -> bool {
2494 self.canonical_in_memory_state.get_canonical_head().block_access_list_hash().is_some()
2495 }
2496
2497 fn latest_valid_hash_for_invalid_payload(
2508 &mut self,
2509 parent_hash: B256,
2510 ) -> ProviderResult<Option<B256>> {
2511 if self.has_block_by_hash(parent_hash)? {
2513 return Ok(Some(parent_hash))
2514 }
2515
2516 let mut current_hash = parent_hash;
2519 let mut current_block = self.state.invalid_headers.get(¤t_hash);
2520 while let Some(block_with_parent) = current_block {
2521 current_hash = block_with_parent.parent;
2522 current_block = self.state.invalid_headers.get(¤t_hash);
2523
2524 if current_block.is_none() && self.has_block_by_hash(current_hash)? {
2527 return Ok(Some(current_hash))
2528 }
2529 }
2530 Ok(None)
2531 }
2532
2533 fn prepare_invalid_response(&mut self, parent_hash: B256) -> ProviderResult<PayloadStatus> {
2537 let valid_parent_hash = match self.sealed_header_by_hash(parent_hash)? {
2538 Some(parent) if !parent.difficulty().is_zero() => Some(B256::ZERO),
2542 Some(_) => Some(parent_hash),
2543 None => self.latest_valid_hash_for_invalid_payload(parent_hash)?,
2544 };
2545
2546 Ok(PayloadStatus::from_status(PayloadStatusEnum::Invalid {
2547 validation_error: PayloadValidationError::LinksToRejectedPayload.to_string(),
2548 })
2549 .with_latest_valid_hash(valid_parent_hash.unwrap_or_default()))
2550 }
2551
2552 fn is_sync_target_head(&self, block_hash: B256) -> bool {
2556 if let Some(target) = self.state.forkchoice_state_tracker.sync_target_state() {
2557 return target.head_block_hash == block_hash
2558 }
2559 false
2560 }
2561
2562 fn is_any_sync_target(&self, block_hash: B256) -> bool {
2566 if let Some(target) = self.state.forkchoice_state_tracker.sync_target_state() {
2567 return target.contains(block_hash)
2568 }
2569 false
2570 }
2571
2572 fn check_invalid_ancestor_with_head(
2578 &mut self,
2579 check: B256,
2580 head: &SealedBlock<N::Block>,
2581 ) -> ProviderResult<Option<PayloadStatus>> {
2582 let Some(header) = self.state.invalid_headers.get(&check) else { return Ok(None) };
2584
2585 Ok(Some(self.on_invalid_new_payload(head.clone(), header)?))
2586 }
2587
2588 fn on_invalid_new_payload(
2590 &mut self,
2591 head: SealedBlock<N::Block>,
2592 invalid: BlockWithParent,
2593 ) -> ProviderResult<PayloadStatus> {
2594 let status = self.prepare_invalid_response(invalid.parent)?;
2596
2597 self.state.invalid_headers.insert_with_invalid_ancestor(head.hash(), invalid);
2599 self.emit_event(ConsensusEngineEvent::InvalidBlock {
2600 block: Box::new(head),
2601 error: PayloadValidationError::LinksToRejectedPayload.to_string(),
2602 });
2603
2604 Ok(status)
2605 }
2606
2607 fn find_invalid_ancestor(&mut self, payload: &T::ExecutionData) -> Option<BlockWithParent> {
2621 let parent_hash = payload.parent_hash();
2622 let block_hash = payload.block_hash();
2623
2624 if let Some(entry) = self.state.invalid_headers.get(&block_hash) {
2626 return Some(entry);
2627 }
2628
2629 let mut lowest_buffered_ancestor = self.lowest_buffered_ancestor_or(block_hash);
2630 if lowest_buffered_ancestor == block_hash {
2631 lowest_buffered_ancestor = parent_hash;
2632 }
2633
2634 self.state.invalid_headers.get(&lowest_buffered_ancestor)
2636 }
2637
2638 fn handle_invalid_ancestor_payload(
2647 &mut self,
2648 payload: T::ExecutionData,
2649 invalid: BlockWithParent,
2650 ) -> Result<PayloadStatus, InsertBlockFatalError> {
2651 let parent_hash = payload.parent_hash();
2652 let num_hash = payload.num_hash();
2653
2654 let block = match self.payload_validator.convert_payload_to_block(payload) {
2660 Ok(block) => block,
2661 Err(error) => return Ok(self.on_new_payload_error(error, num_hash, parent_hash)?),
2662 };
2663
2664 Ok(self.on_invalid_new_payload(block, invalid)?)
2665 }
2666
2667 fn check_invalid_ancestor(&mut self, head: B256) -> ProviderResult<Option<PayloadStatus>> {
2670 let Some(header) = self.state.invalid_headers.get(&head) else { return Ok(None) };
2672
2673 match self.prepare_invalid_response(header.parent) {
2675 Ok(status) => Ok(Some(status)),
2676 Err(err) => {
2677 debug!(target: "engine::tree", %err, "Failed to prepare invalid response for ancestor check");
2678 Ok(Some(PayloadStatus::from_status(PayloadStatusEnum::Invalid {
2680 validation_error: PayloadValidationError::LinksToRejectedPayload.to_string(),
2681 })))
2682 }
2683 }
2684 }
2685
2686 fn validate_block(&self, block: &SealedBlock<N::Block>) -> Result<(), ConsensusError> {
2689 if let Err(e) = self.consensus.validate_header(block.sealed_header()) {
2690 error!(target: "engine::tree", block = ?block.block_with_parent(), %e, "Failed to validate header");
2691 return Err(e)
2692 }
2693
2694 if let Err(e) = self.consensus.validate_block_pre_execution(block) {
2695 error!(target: "engine::tree", block = ?block.block_with_parent(), %e, "Failed to validate block");
2696 return Err(e)
2697 }
2698
2699 Ok(())
2700 }
2701
2702 #[instrument(level = "debug", target = "engine::tree", skip(self))]
2704 fn try_connect_buffered_blocks(
2705 &mut self,
2706 parent: BlockNumHash,
2707 ) -> Result<(), InsertBlockFatalError> {
2708 let blocks = self.state.buffer.remove_block_with_children(&parent.hash);
2709
2710 if blocks.is_empty() {
2711 return Ok(())
2713 }
2714
2715 let now = Instant::now();
2716 let block_count = blocks.len();
2717 for child in blocks {
2718 let child_num_hash = child.num_hash();
2719 match self.insert_block(child) {
2720 Ok(res) => {
2721 debug!(target: "engine::tree", child =?child_num_hash, ?res, "connected buffered block");
2722 if self.is_any_sync_target(child_num_hash.hash) &&
2723 matches!(res, InsertPayloadOk::Inserted(BlockStatus::Valid))
2724 {
2725 debug!(target: "engine::tree", child =?child_num_hash, "connected sync target block");
2726 self.make_canonical(child_num_hash.hash)?;
2729 }
2730 }
2731 Err(err) => {
2732 if let InsertPayloadError::Block(err) = err {
2733 debug!(target: "engine::tree", ?err, "failed to connect buffered block to tree");
2734 if let Err(InsertBlockProcessingError::Fatal(fatal)) =
2735 self.on_insert_block_error(err)
2736 {
2737 warn!(target: "engine::tree", %fatal, "fatal error occurred while connecting buffered blocks");
2738 }
2739 }
2740 }
2741 }
2742 }
2743
2744 debug!(target: "engine::tree", elapsed = ?now.elapsed(), %block_count, "connected buffered blocks");
2745 Ok(())
2746 }
2747
2748 fn buffer_block(
2750 &mut self,
2751 block: SealedBlock<N::Block>,
2752 ) -> Result<(), InsertBlockError<N::Block>> {
2753 if let Err(err) = self.validate_block(&block) {
2754 return Err(InsertBlockError::consensus_error(err, block))
2755 }
2756 self.state.buffer.insert_block(block.into());
2757 Ok(())
2758 }
2759
2760 #[inline]
2765 const fn exceeds_backfill_run_threshold(&self, local_tip: u64, block: u64) -> bool {
2766 block > local_tip && block - local_tip > self.config.backfill_run_threshold()
2767 }
2768
2769 #[inline]
2772 const fn distance_from_local_tip(&self, local_tip: u64, block: u64) -> Option<u64> {
2773 if block > local_tip {
2774 Some(block - local_tip)
2775 } else {
2776 None
2777 }
2778 }
2779
2780 const fn backfill_target_hash(&self, state: ForkchoiceState) -> B256 {
2788 if self.engine_kind.is_opstack() {
2789 state.head_block_hash
2790 } else {
2791 state.finalized_block_hash
2792 }
2793 }
2794
2795 fn backfill_sync_target(
2802 &self,
2803 canonical_tip_num: u64,
2804 target_block_number: u64,
2805 downloaded_block: Option<BlockNumHash>,
2806 ) -> Option<B256> {
2807 let state = self.state.forkchoice_state_tracker.sync_target_state()?;
2808 let target_hash = self.backfill_target_hash(state);
2809
2810 let exceeds_backfill_threshold = match downloaded_block.as_ref() {
2812 Some(downloaded_block) if downloaded_block.hash == target_hash => {
2814 self.exceeds_backfill_run_threshold(canonical_tip_num, downloaded_block.number)
2815 }
2816 _ => match self.state.buffer.block(&target_hash) {
2817 Some(buffered_target) => {
2819 self.exceeds_backfill_run_threshold(canonical_tip_num, buffered_target.number())
2820 }
2821 None => self.exceeds_backfill_run_threshold(canonical_tip_num, target_block_number),
2823 },
2824 };
2825
2826 if !exceeds_backfill_threshold {
2827 return None
2828 }
2829
2830 match self.provider.header_by_hash_or_number(target_hash.into()) {
2832 Err(err) => {
2833 warn!(target: "engine::tree", %err, "Failed to get backfill target block header");
2834 None
2835 }
2836 Ok(None) if !target_hash.is_zero() => Some(target_hash),
2838 Ok(None) => {
2839 debug!(target: "engine::tree", hash=?state.head_block_hash, "Setting head hash as an optimistic backfill target.");
2852 Some(state.head_block_hash)
2853 }
2854 Ok(Some(_)) => None,
2856 }
2857 }
2858
2859 fn find_disk_reorg(&self) -> ProviderResult<Option<u64>> {
2862 let mut canonical = self.state.tree_state.current_canonical_head;
2863 let mut persisted = self.persistence_state.last_persisted_block;
2864
2865 let parent_num_hash = |num_hash: NumHash| -> ProviderResult<NumHash> {
2866 Ok(self
2867 .sealed_header_by_hash(num_hash.hash)?
2868 .ok_or(ProviderError::BlockHashNotFound(num_hash.hash))?
2869 .parent_num_hash())
2870 };
2871
2872 while canonical.number > persisted.number {
2875 canonical = parent_num_hash(canonical)?;
2876 }
2877
2878 if canonical == persisted {
2880 return Ok(None);
2881 }
2882
2883 while persisted.number > canonical.number {
2889 persisted = parent_num_hash(persisted)?;
2890 }
2891
2892 debug_assert_eq!(persisted.number, canonical.number);
2893
2894 while persisted.hash != canonical.hash {
2896 canonical = parent_num_hash(canonical)?;
2897 persisted = parent_num_hash(persisted)?;
2898 }
2899
2900 debug!(target: "engine::tree", remove_above=persisted.number, "on-disk reorg detected");
2901
2902 Ok(Some(persisted.number))
2903 }
2904
2905 fn on_canonical_chain_update(&mut self, chain_update: NewCanonicalChain<N>) {
2909 trace!(target: "engine::tree", new_blocks = %chain_update.new_block_count(), reorged_blocks = %chain_update.reorged_block_count(), "applying new chain update");
2910 let start = Instant::now();
2911
2912 self.state.tree_state.set_canonical_head(chain_update.tip().num_hash());
2914
2915 let tip = chain_update.tip().clone_sealed_header();
2916 let notification = chain_update.to_chain_notification();
2917
2918 if let NewCanonicalChain::Reorg { new, old } = &chain_update {
2920 let new_first = new.first().map(|first| first.recovered_block().num_hash());
2921 let old_first = old.first().map(|first| first.recovered_block().num_hash());
2922 trace!(target: "engine::tree", ?new_first, ?old_first, "Reorg detected, new and old first blocks");
2923
2924 self.state.set_pending_sparse_trie_prune(false);
2925 self.update_reorg_metrics(old.len(), old_first);
2926 self.reinsert_reorged_blocks(new.clone());
2927 self.reinsert_reorged_blocks(old.clone());
2928 }
2929
2930 self.canonical_in_memory_state.update_chain(chain_update);
2932 self.canonical_in_memory_state.set_canonical_head(tip.clone());
2933 self.payload_validator.on_canonical_head_changed(tip.hash(), &self.state);
2934
2935 self.metrics.tree.canonical_chain_height.set(tip.number() as f64);
2937
2938 self.canonical_in_memory_state.notify_canon_state(notification);
2940
2941 self.emit_event(ConsensusEngineEvent::CanonicalChainCommitted(
2943 Box::new(tip),
2944 start.elapsed(),
2945 ));
2946 }
2947
2948 fn update_reorg_metrics(&self, old_chain_length: usize, first_reorged_block: Option<NumHash>) {
2950 if let Some(first_reorged_block) = first_reorged_block.map(|block| block.number) {
2951 if let Some(finalized) = self.canonical_in_memory_state.get_finalized_num_hash() &&
2952 first_reorged_block <= finalized.number
2953 {
2954 self.metrics.tree.reorgs.finalized.increment(1);
2955 } else if let Some(safe) = self.canonical_in_memory_state.get_safe_num_hash() &&
2956 first_reorged_block <= safe.number
2957 {
2958 self.metrics.tree.reorgs.safe.increment(1);
2959 } else {
2960 self.metrics.tree.reorgs.head.increment(1);
2961 }
2962 } else {
2963 debug_unreachable!("Reorged chain doesn't have any blocks");
2964 }
2965 self.metrics.tree.latest_reorg_depth.set(old_chain_length as f64);
2966 }
2967
2968 fn reinsert_reorged_blocks(&mut self, new_chain: Vec<ExecutedBlock<N>>) {
2970 for block in new_chain {
2971 if self
2972 .state
2973 .tree_state
2974 .executed_block_by_hash(block.recovered_block().hash())
2975 .is_none()
2976 {
2977 trace!(target: "engine::tree", num=?block.recovered_block().number(), hash=?block.recovered_block().hash(), "Reinserting block into tree state");
2978 self.state.tree_state.insert_executed(block);
2979 }
2980 }
2981 self.metrics.engine.executed_blocks.set(self.state.tree_state.block_count() as f64);
2982 }
2983
2984 fn on_disconnected_downloaded_block(
2989 &self,
2990 downloaded_block: BlockNumHash,
2991 missing_parent: BlockNumHash,
2992 head: BlockNumHash,
2993 ) -> Option<TreeEvent> {
2994 if let Some(target) =
2996 self.backfill_sync_target(head.number, missing_parent.number, Some(downloaded_block))
2997 {
2998 trace!(target: "engine::tree", %target, "triggering backfill on downloaded block");
2999 return Some(TreeEvent::BackfillAction(BackfillAction::Start(target.into())));
3000 }
3001
3002 let request = if let Some(distance) =
3012 self.distance_from_local_tip(head.number, missing_parent.number)
3013 {
3014 trace!(target: "engine::tree", %distance, missing=?missing_parent, "downloading missing parent block range");
3015 DownloadRequest::block_range(missing_parent.hash, distance)
3016 } else {
3017 trace!(target: "engine::tree", missing=?missing_parent, "downloading missing parent block");
3018 DownloadRequest::single_block(missing_parent.hash)
3021 };
3022
3023 Some(TreeEvent::Download(request.with_access_lists(self.should_download_access_lists())))
3024 }
3025
3026 fn on_valid_downloaded_block(
3033 &mut self,
3034 block_num_hash: BlockNumHash,
3035 ) -> Result<Option<TreeEvent>, InsertBlockFatalError> {
3036 if let Some(sync_target) = self.state.forkchoice_state_tracker.sync_target_state() &&
3039 sync_target.contains(block_num_hash.hash)
3040 {
3041 debug!(target: "engine::tree", ?sync_target, "appended downloaded sync target block");
3042
3043 if sync_target.head_block_hash == block_num_hash.hash {
3044 return Ok(Some(TreeEvent::TreeAction(TreeAction::MakeCanonical {
3046 sync_target_head: block_num_hash.hash,
3047 })))
3048 }
3049
3050 self.make_canonical(block_num_hash.hash)?;
3054 self.try_connect_buffered_blocks(block_num_hash)?;
3055
3056 if self.state.tree_state.canonical_block_hash() != sync_target.head_block_hash {
3059 let target = self.lowest_buffered_ancestor_or(sync_target.head_block_hash);
3060 trace!(target: "engine::tree", %target, "sync target head not yet reached, downloading head block");
3061 return Ok(Some(TreeEvent::Download(
3062 DownloadRequest::single_block(target)
3063 .with_access_lists(self.should_download_access_lists()),
3064 )))
3065 }
3066
3067 return Ok(None)
3068 }
3069 trace!(target: "engine::tree", "appended downloaded block");
3070 self.try_connect_buffered_blocks(block_num_hash)?;
3071 Ok(None)
3072 }
3073
3074 #[instrument(level = "debug", target = "engine::tree", skip_all, fields(block_hash = %block.hash(), block_num = %block.number()))]
3080 fn on_downloaded_block(
3081 &mut self,
3082 block: SealedBlockWithAccessList<N::Block>,
3083 ) -> Result<Option<TreeEvent>, InsertBlockFatalError> {
3084 let block_num_hash = block.num_hash();
3085 let lowest_buffered_ancestor = self.lowest_buffered_ancestor_or(block_num_hash.hash);
3086 if self.check_invalid_ancestor_with_head(lowest_buffered_ancestor, &block)?.is_some() {
3087 return Ok(None)
3088 }
3089
3090 if !self.backfill_sync_state.is_idle() {
3091 return Ok(None)
3092 }
3093
3094 match self.insert_block(block) {
3096 Ok(InsertPayloadOk::Inserted(BlockStatus::Valid)) => {
3097 return self.on_valid_downloaded_block(block_num_hash);
3098 }
3099 Ok(InsertPayloadOk::Inserted(BlockStatus::Disconnected { head, missing_ancestor })) => {
3100 return Ok(self.on_disconnected_downloaded_block(
3103 block_num_hash,
3104 missing_ancestor,
3105 head,
3106 ))
3107 }
3108 Ok(InsertPayloadOk::AlreadySeen(_)) => {
3109 trace!(target: "engine::tree", "downloaded block already executed");
3110 }
3111 Err(err) => {
3112 if let InsertPayloadError::Block(err) = err {
3113 debug!(target: "engine::tree", err=%err.kind(), "failed to insert downloaded block");
3114 if let Err(InsertBlockProcessingError::Fatal(fatal)) =
3115 self.on_insert_block_error(err)
3116 {
3117 warn!(target: "engine::tree", %fatal, "fatal error occurred while inserting downloaded block");
3118 }
3119 }
3120 }
3121 }
3122 Ok(None)
3123 }
3124
3125 fn insert_payload(
3134 &mut self,
3135 payload: T::ExecutionData,
3136 ) -> Result<InsertPayloadOk, InsertPayloadError<N::Block>> {
3137 self.insert_block_or_payload(
3138 payload.block_with_parent(),
3139 payload,
3140 |validator, payload, ctx| validator.validate_payload(payload, ctx),
3141 |this, payload| Ok(this.payload_validator.convert_payload_to_block(payload)?.into()),
3142 )
3143 }
3144
3145 fn insert_block(
3146 &mut self,
3147 block: SealedBlockWithAccessList<N::Block>,
3148 ) -> Result<InsertPayloadOk, InsertPayloadError<N::Block>> {
3149 self.insert_block_or_payload(
3150 block.block_with_parent(),
3151 block,
3152 |validator, block, ctx| validator.validate_block(block, ctx),
3153 |_, block| Ok(block),
3154 )
3155 }
3156
3157 #[instrument(level = "debug", target = "engine::tree", skip_all, fields(?block_id))]
3174 fn insert_block_or_payload<Input, Err>(
3175 &mut self,
3176 block_id: BlockWithParent,
3177 input: Input,
3178 execute: impl FnOnce(&mut V, Input, TreeCtx<'_, N>) -> Result<ValidationOutput<N>, Err>,
3179 convert_to_block: impl FnOnce(
3180 &mut Self,
3181 Input,
3182 ) -> Result<SealedBlockWithAccessList<N::Block>, Err>,
3183 ) -> Result<InsertPayloadOk, Err>
3184 where
3185 Err: From<InsertBlockError<N::Block>>,
3186 {
3187 let block_insert_start = Instant::now();
3188 let block_num_hash = block_id.block;
3189 debug!(target: "engine::tree", block=?block_num_hash, parent = ?block_id.parent, "Inserting new block into tree");
3190
3191 if self.state.tree_state.contains_hash(&block_num_hash.hash) {
3193 convert_to_block(self, input)?;
3194 return Ok(InsertPayloadOk::AlreadySeen(BlockStatus::Valid));
3195 }
3196
3197 if block_num_hash.number <= self.persistence_state.last_persisted_block.number {
3200 match self.provider.sealed_header_by_hash(block_num_hash.hash) {
3201 Err(err) => {
3202 let block = convert_to_block(self, input)?;
3203 return Err(InsertBlockError::new(block.split().0, err.into()).into());
3204 }
3205 Ok(Some(_)) => {
3206 convert_to_block(self, input)?;
3207 return Ok(InsertPayloadOk::AlreadySeen(BlockStatus::Valid));
3208 }
3209 Ok(None) => {}
3210 }
3211 }
3212
3213 if !self.state.tree_state.contains_hash(&block_id.parent) {
3215 let parent_exists = match self.provider.header(block_id.parent) {
3216 Ok(header) => header.is_some(),
3217 Err(err) => {
3218 let block = convert_to_block(self, input)?;
3219 return Err(InsertBlockError::new(block.split().0, err.into()).into());
3220 }
3221 };
3222
3223 if !parent_exists {
3224 let block = convert_to_block(self, input)?;
3225 let missing_ancestor = self
3228 .state
3229 .buffer
3230 .lowest_ancestor(&block.parent_hash())
3231 .map(|block| block.parent_num_hash())
3232 .unwrap_or_else(|| block.parent_num_hash());
3233
3234 self.state.buffer.insert_block(block);
3235
3236 return Ok(InsertPayloadOk::Inserted(BlockStatus::Disconnected {
3237 head: self.state.tree_state.current_canonical_head,
3238 missing_ancestor,
3239 }))
3240 }
3241 }
3242
3243 let is_fork = block_id.block.number <= self.state.tree_state.current_canonical_head.number;
3248
3249 let ctx = TreeCtx::new(&mut self.state, &self.canonical_in_memory_state);
3250
3251 let start = Instant::now();
3252
3253 let ValidationOutput { executed_block: executed, execution_timing_stats: timing_stats } =
3254 execute(&mut self.payload_validator, input, ctx)?;
3255
3256 if let Some(raw_bal) = executed.bal().map(|bal| bal.as_raw_bal().clone()) {
3257 let num_hash = executed.recovered_block().num_hash();
3258 if let Err(err) = self.provider.bal_store().insert(num_hash, raw_bal) {
3259 warn!(
3260 target: "engine::tree",
3261 ?num_hash,
3262 %err,
3263 "Failed to store validated block access list"
3264 );
3265 }
3266 }
3267
3268 if let Some(stats) = timing_stats {
3271 if let Some(threshold) = self.config.slow_block_threshold() {
3272 let total_duration = stats.execution_duration + stats.state_hash_duration;
3273 if total_duration > threshold {
3274 self.emit_event(ConsensusEngineEvent::SlowBlock(SlowBlockInfo {
3275 stats: stats.clone(),
3276 commit_duration: None,
3277 total_duration,
3278 }));
3279 }
3280 }
3281 self.execution_timing_stats.insert(executed.recovered_block().hash(), stats);
3282 }
3283
3284 let is_pending = self.state.tree_state.canonical_block_hash() ==
3285 executed.recovered_block().parent_hash();
3286 self.state.tree_state.insert_executed(executed.clone());
3287
3288 if is_pending {
3289 debug!(target: "engine::tree", pending=?block_num_hash, "updating pending block");
3290 self.canonical_in_memory_state.set_pending_block(executed.clone());
3291 }
3292
3293 self.metrics.engine.executed_blocks.set(self.state.tree_state.block_count() as f64);
3294
3295 let elapsed = start.elapsed();
3297 let engine_event = if is_fork {
3298 ConsensusEngineEvent::ForkBlockAdded(executed, elapsed)
3299 } else {
3300 ConsensusEngineEvent::CanonicalBlockAdded(executed, elapsed)
3301 };
3302 self.emit_event(EngineApiEvent::BeaconConsensus(engine_event));
3303
3304 self.metrics
3305 .engine
3306 .block_insert_total_duration
3307 .record(block_insert_start.elapsed().as_secs_f64());
3308 debug!(target: "engine::tree", block=?block_num_hash, "Finished inserting block");
3309 Ok(InsertPayloadOk::Inserted(BlockStatus::Valid))
3310 }
3311
3312 fn on_insert_block_error(
3318 &mut self,
3319 error: InsertBlockError<N::Block>,
3320 ) -> Result<PayloadStatus, InsertBlockProcessingError> {
3321 let (block, error) = error.split();
3322
3323 let validation_err = error.ensure_validation_error()?;
3324
3325 warn!(
3329 target: "engine::tree",
3330 invalid_hash=%block.hash(),
3331 invalid_number=block.number(),
3332 %validation_err,
3333 "Invalid block error on new payload",
3334 );
3335 let latest_valid_hash =
3338 if matches!(&validation_err, InsertBlockValidationError::BlockAccessListDecode(_)) {
3339 None
3340 } else {
3341 self.latest_valid_hash_for_invalid_payload(block.parent_hash())
3342 .map_err(InsertBlockFatalError::from)?
3343 };
3344
3345 let is_transient = match &validation_err {
3347 InsertBlockValidationError::Consensus(err) => self.consensus.is_transient_error(err),
3348 _ => false,
3349 };
3350 if is_transient {
3351 warn!(
3352 target: "engine::tree",
3353 invalid_hash=%block.hash(),
3354 invalid_number=block.number(),
3355 %validation_err,
3356 "Skipping invalid header cache insert for transient validation error",
3357 );
3358 } else {
3359 self.state.invalid_headers.insert(block.block_with_parent());
3360 }
3361 self.emit_event(EngineApiEvent::BeaconConsensus(ConsensusEngineEvent::InvalidBlock {
3362 block: Box::new(block),
3363 error: validation_err.to_string(),
3364 }));
3365
3366 Ok(PayloadStatus::new(
3367 PayloadStatusEnum::Invalid { validation_error: validation_err.to_string() },
3368 latest_valid_hash,
3369 ))
3370 }
3371
3372 fn on_new_payload_error(
3374 &mut self,
3375 error: NewPayloadError,
3376 payload_num_hash: NumHash,
3377 parent_hash: B256,
3378 ) -> ProviderResult<PayloadStatus> {
3379 error!(target: "engine::tree", payload=?payload_num_hash, %error, "Invalid payload");
3380 let latest_valid_hash =
3383 if error.is_block_hash_mismatch() || error.is_invalid_versioned_hashes() {
3384 None
3388 } else {
3389 self.latest_valid_hash_for_invalid_payload(parent_hash)?
3390 };
3391
3392 let status = PayloadStatusEnum::from(error);
3393 Ok(PayloadStatus::new(status, latest_valid_hash))
3394 }
3395
3396 pub fn find_canonical_header(
3402 &self,
3403 hash: B256,
3404 ) -> Result<Option<SealedHeader<N::BlockHeader>>, ProviderError> {
3405 if let Some(header) = self.canonical_in_memory_state.header_by_hash(hash) {
3406 return Ok(Some(header))
3407 }
3408
3409 let Some(header) = self.provider.sealed_header_by_hash(hash)? else { return Ok(None) };
3410 let number = header.number();
3413 if number > self.canonical_in_memory_state.get_canonical_block_number() {
3414 return Ok(None)
3415 }
3416 let canonical_hash =
3417 if let Some(hash) = self.canonical_in_memory_state.hash_by_number(number) {
3418 Some(hash)
3419 } else {
3420 self.provider.block_hash(number)?
3421 };
3422
3423 Ok((canonical_hash == Some(hash)).then_some(header))
3424 }
3425
3426 fn is_consistent_forkchoice_state(
3444 &self,
3445 state: ForkchoiceState,
3446 chain_update: Option<&NewCanonicalChain<N>>,
3447 ) -> ProviderResult<bool> {
3448 let canonical_head_number = match chain_update {
3449 Some(chain_update) => {
3450 let new = chain_update.new_blocks();
3451 new.first().expect("non empty chain").block_number() - 1
3453 }
3454 None => {
3455 let Some(head) = self.find_canonical_header(state.head_block_hash)? else {
3456 return Ok(false)
3457 };
3458 head.number()
3459 }
3460 };
3461
3462 for hash in [state.finalized_block_hash, state.safe_block_hash] {
3463 if hash.is_zero() || chain_update.is_some_and(|update| update.contains(hash)) {
3464 continue
3465 }
3466 let Some(header) = self.find_canonical_header(hash)? else { return Ok(false) };
3467 if header.number() > canonical_head_number {
3468 return Ok(false)
3469 }
3470 }
3471 Ok(true)
3472 }
3473
3474 fn update_finalized_block(
3476 &self,
3477 finalized_block_hash: B256,
3478 ) -> Result<(), OnForkChoiceUpdated> {
3479 if finalized_block_hash.is_zero() {
3480 return Ok(())
3481 }
3482
3483 match self.find_canonical_header(finalized_block_hash) {
3484 Ok(None) => {
3485 debug!(target: "engine::tree", "Finalized block not found in canonical chain");
3486 return Err(OnForkChoiceUpdated::invalid_state())
3488 }
3489 Ok(Some(finalized)) => {
3490 if Some(finalized.num_hash()) !=
3491 self.canonical_in_memory_state.get_finalized_num_hash()
3492 {
3493 let _ = self.persistence.save_finalized_block_number(finalized.number());
3496 self.canonical_in_memory_state.set_finalized(finalized.clone());
3497 self.metrics.tree.finalized_block_height.set(finalized.number() as f64);
3499 }
3500 }
3501 Err(err) => {
3502 error!(target: "engine::tree", %err, "Failed to fetch finalized block header");
3503 }
3504 }
3505
3506 Ok(())
3507 }
3508
3509 fn update_safe_block(&self, safe_block_hash: B256) -> Result<(), OnForkChoiceUpdated> {
3511 if safe_block_hash.is_zero() {
3512 return Ok(())
3513 }
3514
3515 match self.find_canonical_header(safe_block_hash) {
3516 Ok(None) => {
3517 debug!(target: "engine::tree", "Safe block not found in canonical chain");
3518 return Err(OnForkChoiceUpdated::invalid_state())
3520 }
3521 Ok(Some(safe)) => {
3522 if Some(safe.num_hash()) != self.canonical_in_memory_state.get_safe_num_hash() {
3523 let _ = self.persistence.save_safe_block_number(safe.number());
3526 self.canonical_in_memory_state.set_safe(safe.clone());
3527 self.metrics.tree.safe_block_height.set(safe.number() as f64);
3529 }
3530 }
3531 Err(err) => {
3532 error!(target: "engine::tree", %err, "Failed to fetch safe block header");
3533 }
3534 }
3535
3536 Ok(())
3537 }
3538
3539 fn ensure_consistent_forkchoice_state(
3548 &self,
3549 state: ForkchoiceState,
3550 ) -> Result<(), OnForkChoiceUpdated> {
3551 self.update_finalized_block(state.finalized_block_hash)?;
3557
3558 self.update_safe_block(state.safe_block_hash)
3564 }
3565
3566 fn process_payload_attributes(
3581 &mut self,
3582 attributes: T::PayloadAttributes,
3583 head: &N::BlockHeader,
3584 state: ForkchoiceState,
3585 ) -> OnForkChoiceUpdated {
3586 if let Err(err) =
3587 self.payload_validator.validate_payload_attributes_against_header(&attributes, head)
3588 {
3589 warn!(target: "engine::tree", %err, ?head, "Invalid payload attributes");
3590 return OnForkChoiceUpdated::invalid_payload_attributes()
3591 }
3592
3593 let payload_build = self.payload_builds.acquire();
3601
3602 let resources = self
3603 .payload_validator
3604 .payload_builder_resources(
3605 state.head_block_hash,
3606 head,
3607 attributes.timestamp(),
3608 &mut self.state,
3609 )
3610 .with_lease(PayloadBuilderLease::new(payload_build));
3611
3612 let pending_payload_id = self.payload_builder.send_new_payload(BuildNewPayload {
3615 parent_hash: state.head_block_hash,
3616 attributes,
3617 resources,
3618 });
3619
3620 OnForkChoiceUpdated::updated_with_pending_payload_id(
3632 PayloadStatus::new(PayloadStatusEnum::Valid, Some(state.head_block_hash)),
3633 pending_payload_id,
3634 )
3635 }
3636
3637 pub(crate) fn remove_before(
3644 &mut self,
3645 upper_bound: BlockNumHash,
3646 finalized_hash: Option<B256>,
3647 ) -> ProviderResult<()> {
3648 let num = if let Some(hash) = finalized_hash {
3651 self.provider.block_number(hash)?.map(|number| BlockNumHash { number, hash })
3652 } else {
3653 None
3654 };
3655
3656 self.state.tree_state.remove_until(
3657 upper_bound,
3658 self.persistence_state.last_persisted_block.hash,
3659 num,
3660 );
3661 self.metrics.engine.executed_blocks.set(self.state.tree_state.block_count() as f64);
3662 Ok(())
3663 }
3664}
3665
3666#[derive(Debug)]
3668enum LoopEvent<T, N>
3669where
3670 N: NodePrimitives,
3671 T: PayloadTypes,
3672{
3673 EngineMessage(FromEngine<EngineApiRequest<T, N>, N::Block>),
3675 PersistenceComplete {
3677 result: PersistenceResult,
3679 start_time: Instant,
3681 },
3682 PayloadBuildFinished,
3684 Disconnected,
3686}
3687
3688#[derive(Clone, Debug)]
3690struct PayloadBuildTracker {
3691 active: Arc<AtomicUsize>,
3692 finished_tx: Sender<()>,
3693}
3694
3695impl PayloadBuildTracker {
3696 fn new() -> (Self, Receiver<()>) {
3698 let (finished_tx, finished_rx) = crossbeam_channel::bounded(1);
3699 (Self { active: Arc::new(AtomicUsize::new(0)), finished_tx }, finished_rx)
3700 }
3701
3702 fn acquire(&self) -> PayloadBuildLease {
3704 self.active.fetch_add(1, Ordering::AcqRel);
3705 PayloadBuildLease {
3706 active: Arc::clone(&self.active),
3707 finished_tx: self.finished_tx.clone(),
3708 }
3709 }
3710
3711 fn is_active(&self) -> bool {
3713 self.active.load(Ordering::Acquire) != 0
3714 }
3715}
3716
3717#[derive(Debug)]
3719struct PayloadBuildLease {
3720 active: Arc<AtomicUsize>,
3721 finished_tx: Sender<()>,
3722}
3723
3724impl Drop for PayloadBuildLease {
3725 fn drop(&mut self) {
3726 let previous = self.active.fetch_sub(1, Ordering::AcqRel);
3727 debug_assert!(previous > 0, "payload build lease count underflow");
3728
3729 if previous == 1 {
3730 let _ = self.finished_tx.try_send(());
3733 }
3734 }
3735}
3736
3737#[derive(Clone, Copy, Debug, Eq, PartialEq)]
3743pub enum BlockStatus {
3744 Valid,
3750 Disconnected {
3752 head: BlockNumHash,
3754 missing_ancestor: BlockNumHash,
3756 },
3757}
3758
3759#[derive(Clone, Copy, Debug, Eq, PartialEq)]
3764pub enum InsertPayloadOk {
3765 AlreadySeen(BlockStatus),
3767 Inserted(BlockStatus),
3769}
3770
3771#[derive(Debug, Clone, Copy)]
3773enum PersistTarget {
3774 Threshold,
3776 Head,
3778 Persisted,
3780}
3781
3782#[derive(Debug, Clone, Copy, Default)]
3784pub struct CacheWaitDurations {
3785 pub execution_cache: Duration,
3787 pub sparse_trie: Duration,
3789}
3790
3791pub trait WaitForCaches {
3796 fn wait_for_caches(&self) -> CacheWaitDurations;
3800}