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::{debug_unreachable, 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_stages_api::ControlFlow;
40use reth_storage_overlay::OverlayManager;
41use reth_tasks::{spawn_os_thread, utils::increase_thread_priority};
42use reth_trie::{HashedPostState, KeccakKeyHasher, SortedTrieData};
43use state::TreeState;
44use std::{
45 fmt::Debug,
46 ops,
47 sync::{
48 atomic::{AtomicUsize, Ordering},
49 Arc,
50 },
51 time::Duration,
52};
53
54use crossbeam_channel::{Receiver, Sender};
55use tokio::sync::{
56 mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
57 oneshot,
58};
59use tracing::*;
60
61mod block_buffer;
62pub mod error;
63pub mod instrumented_state;
64mod invalid_headers;
65mod metrics;
66pub mod payload_processor;
67pub mod payload_validator;
68mod persistence_state;
69pub mod state_root_strategy;
70#[cfg(test)]
71mod tests;
72mod txpool_prewarm;
73pub mod types;
74
75use crate::{persistence::PersistenceResult, tree::error::AdvancePersistenceError};
76pub use block_buffer::BlockBuffer;
77pub use invalid_headers::InvalidHeaderCache;
78pub use metrics::EngineApiMetrics;
79pub use payload_processor::*;
80pub use payload_validator::{BasicEngineValidator, EngineValidator};
81pub use persistence_state::PersistenceState;
82pub use reth_engine_primitives::TreeConfig;
83pub use reth_execution_cache::{
84 CacheCheckout, CachedStateCacheMetrics, CachedStateMetrics, CachedStateMetricsSource,
85 CachedStateProvider, ExecutionCache, PayloadExecutionCache, SavedCache,
86 TxPoolPrewarmCacheSnapshot,
87};
88pub use txpool_prewarm::{
89 Source as TxPoolPrewarmSource, Transaction as TxPoolPrewarmTransaction,
90 Transactions as TxPoolPrewarmTransactions,
91};
92pub use types::{ExecutionEnv, ValidationOutcome, ValidationOutput};
93
94pub mod state;
95
96pub const CHANGESET_CACHE_RETENTION_BLOCKS: u64 = 64;
101
102#[derive(Debug)]
106pub struct EngineApiTreeState<N: NodePrimitives> {
107 tree_state: TreeState<N>,
109 pending_sparse_trie_prune: bool,
111 forkchoice_state_tracker: ForkchoiceStateTracker,
113 buffer: BlockBuffer<N::Block>,
115 invalid_headers: InvalidHeaderCache,
118}
119
120impl<N: NodePrimitives> EngineApiTreeState<N> {
121 fn new(
122 block_buffer_limit: u32,
123 max_invalid_header_cache_length: u32,
124 invalid_header_hit_eviction_threshold: u8,
125 canonical_block: BlockNumHash,
126 engine_kind: EngineApiKind,
127 overlay_manager: OverlayManager<N>,
128 ) -> Self {
129 Self {
130 invalid_headers: InvalidHeaderCache::new(
131 max_invalid_header_cache_length,
132 invalid_header_hit_eviction_threshold,
133 ),
134 buffer: BlockBuffer::new(block_buffer_limit),
135 tree_state: TreeState::new(canonical_block, engine_kind, overlay_manager),
136 pending_sparse_trie_prune: false,
137 forkchoice_state_tracker: ForkchoiceStateTracker::default(),
138 }
139 }
140
141 pub const fn tree_state(&self) -> &TreeState<N> {
143 &self.tree_state
144 }
145
146 pub const fn pending_sparse_trie_prune(&self) -> bool {
148 self.pending_sparse_trie_prune
149 }
150
151 pub const fn set_pending_sparse_trie_prune(&mut self, pending: bool) {
153 self.pending_sparse_trie_prune = pending;
154 }
155
156 pub fn take_sparse_trie_prune_blocks(
163 &mut self,
164 parent_hash: B256,
165 ) -> Option<Vec<ExecutedBlock<N>>> {
166 if !self.pending_sparse_trie_prune {
167 return None
168 }
169
170 self.pending_sparse_trie_prune = false;
171 Some(
172 self.tree_state
173 .blocks_by_hash(parent_hash)
174 .map(|(_, blocks)| blocks)
175 .unwrap_or_default(),
176 )
177 }
178
179 pub fn has_invalid_header(&mut self, hash: &B256) -> bool {
181 self.invalid_headers.get(hash).is_some()
182 }
183}
184
185#[derive(Debug)]
187pub struct TreeOutcome<T> {
188 pub outcome: T,
190 pub event: Option<TreeEvent>,
192 pub already_seen: bool,
195}
196
197impl<T> TreeOutcome<T> {
198 pub const fn new(outcome: T) -> Self {
200 Self { outcome, event: None, already_seen: false }
201 }
202
203 pub fn with_event(mut self, event: TreeEvent) -> Self {
205 self.event = Some(event);
206 self
207 }
208
209 pub const fn with_already_seen(mut self, value: bool) -> Self {
211 self.already_seen = value;
212 self
213 }
214}
215
216#[derive(Debug)]
218pub struct TryInsertPayloadResult {
219 pub status: PayloadStatus,
223 pub already_seen: bool,
225}
226
227impl TryInsertPayloadResult {
228 #[inline]
230 pub fn into_outcome(self) -> TreeOutcome<PayloadStatus> {
231 TreeOutcome::new(self.status).with_already_seen(self.already_seen)
232 }
233}
234
235#[derive(Debug)]
237pub enum TreeEvent {
238 TreeAction(TreeAction),
240 BackfillAction(BackfillAction),
242 Download(DownloadRequest),
244}
245
246impl TreeEvent {
247 const fn is_backfill_action(&self) -> bool {
249 matches!(self, Self::BackfillAction(_))
250 }
251}
252
253#[derive(Debug)]
255pub enum TreeAction {
256 MakeCanonical {
258 sync_target_head: B256,
260 },
261}
262
263pub struct EngineApiTreeHandler<N, P, T, V, C>
268where
269 N: NodePrimitives,
270 T: PayloadTypes,
271 C: ConfigureEvm<Primitives = N> + 'static,
272{
273 provider: P,
274 consensus: Arc<dyn FullConsensus<N>>,
275 payload_validator: V,
276 state: EngineApiTreeState<N>,
278 incoming_tx: Sender<FromEngine<EngineApiRequest<T, N>, N::Block>>,
287 incoming: Receiver<FromEngine<EngineApiRequest<T, N>, N::Block>>,
289 outgoing: UnboundedSender<EngineApiEvent<N>>,
291 persistence: PersistenceHandle<N>,
293 persistence_state: PersistenceState,
295 backfill_sync_state: BackfillSyncState,
297 canonical_in_memory_state: CanonicalInMemoryState<N>,
300 payload_builder: PayloadBuilderHandle<T>,
303 config: TreeConfig,
305 metrics: EngineApiMetrics,
307 engine_kind: EngineApiKind,
309 evm_config: C,
311 execution_timing_stats: B256Map<Box<ExecutionTimingStats>>,
315 payload_builds: PayloadBuildTracker,
317 payload_build_finished: Receiver<()>,
319 runtime: reth_tasks::Runtime,
321}
322
323impl<N, P: Debug, T: PayloadTypes, V: Debug, C> std::fmt::Debug
324 for EngineApiTreeHandler<N, P, T, V, C>
325where
326 N: NodePrimitives,
327 C: ConfigureEvm<Primitives = N>,
328{
329 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
330 f.debug_struct("EngineApiTreeHandler")
331 .field("provider", &self.provider)
332 .field("consensus", &self.consensus)
333 .field("payload_validator", &self.payload_validator)
334 .field("state", &self.state)
335 .field("incoming_tx", &self.incoming_tx)
336 .field("persistence", &self.persistence)
337 .field("persistence_state", &self.persistence_state)
338 .field("backfill_sync_state", &self.backfill_sync_state)
339 .field("canonical_in_memory_state", &self.canonical_in_memory_state)
340 .field("payload_builder", &self.payload_builder)
341 .field("config", &self.config)
342 .field("metrics", &self.metrics)
343 .field("engine_kind", &self.engine_kind)
344 .field("evm_config", &self.evm_config)
345 .field("execution_timing_stats", &self.execution_timing_stats.len())
346 .field("payload_builds_active", &self.payload_builds.is_active())
347 .field("runtime", &self.runtime)
348 .finish()
349 }
350}
351
352impl<N, P, T, V, C> EngineApiTreeHandler<N, P, T, V, C>
353where
354 N: NodePrimitives,
355 P: DatabaseProviderFactory
356 + BlockReader<Block = N::Block, Header = N::BlockHeader>
357 + StateProviderFactory
358 + StateReader<Receipt = N::Receipt>
359 + BalProvider
360 + Clone
361 + 'static,
362 P::Provider: BlockReader<Block = N::Block, Header = N::BlockHeader>
363 + PruneCheckpointReader
364 + StageCheckpointReader
365 + ChangeSetReader
366 + StorageChangeSetReader
367 + StorageSettingsCache
368 + 'static,
369 C: ConfigureEvm<Primitives = N> + 'static,
370 T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>,
371 V: EngineValidator<T> + WaitForCaches,
372{
373 #[expect(clippy::too_many_arguments)]
375 pub fn new(
376 provider: P,
377 consensus: Arc<dyn FullConsensus<N>>,
378 payload_validator: V,
379 outgoing: UnboundedSender<EngineApiEvent<N>>,
380 state: EngineApiTreeState<N>,
381 canonical_in_memory_state: CanonicalInMemoryState<N>,
382 persistence: PersistenceHandle<N>,
383 persistence_state: PersistenceState,
384 payload_builder: PayloadBuilderHandle<T>,
385 config: TreeConfig,
386 engine_kind: EngineApiKind,
387 evm_config: C,
388 runtime: reth_tasks::Runtime,
389 ) -> Self {
390 let (incoming_tx, incoming) = crossbeam_channel::unbounded();
391
392 let (payload_builds, payload_build_finished) = PayloadBuildTracker::new();
393
394 Self {
395 provider,
396 consensus,
397 payload_validator,
398 incoming,
399 outgoing,
400 persistence,
401 persistence_state,
402 backfill_sync_state: BackfillSyncState::Idle,
403 state,
404 canonical_in_memory_state,
405 payload_builder,
406 config,
407 metrics: Default::default(),
408 incoming_tx,
409 engine_kind,
410 evm_config,
411 execution_timing_stats: B256Map::default(),
412 payload_builds,
413 payload_build_finished,
414 runtime,
415 }
416 }
417
418 #[expect(clippy::complexity)]
424 pub fn spawn_new(
425 provider: P,
426 consensus: Arc<dyn FullConsensus<N>>,
427 payload_validator: V,
428 persistence: PersistenceHandle<N>,
429 payload_builder: PayloadBuilderHandle<T>,
430 canonical_in_memory_state: CanonicalInMemoryState<N>,
431 overlay_manager: OverlayManager<N>,
432 config: TreeConfig,
433 kind: EngineApiKind,
434 evm_config: C,
435 runtime: reth_tasks::Runtime,
436 ) -> (Sender<FromEngine<EngineApiRequest<T, N>, N::Block>>, UnboundedReceiver<EngineApiEvent<N>>)
437 {
438 let best_block_number = provider.best_block_number().unwrap_or(0);
439 let header = provider.sealed_header(best_block_number).ok().flatten().unwrap_or_default();
440
441 let persistence_state = PersistenceState {
442 last_persisted_block: BlockNumHash::new(best_block_number, header.hash()),
443 last_state_trie_persisted_block: BlockNumHash::new(best_block_number, header.hash()),
444 rx: None,
445 };
446
447 let (tx, outgoing) = unbounded_channel();
448 let state = EngineApiTreeState::new(
449 config.block_buffer_limit(),
450 config.max_invalid_header_cache_length(),
451 config.invalid_header_hit_eviction_threshold(),
452 header.num_hash(),
453 kind,
454 overlay_manager,
455 );
456
457 let task = Self::new(
458 provider,
459 consensus,
460 payload_validator,
461 tx,
462 state,
463 canonical_in_memory_state,
464 persistence,
465 persistence_state,
466 payload_builder,
467 config,
468 kind,
469 evm_config,
470 runtime,
471 );
472 let incoming = task.incoming_tx.clone();
473 spawn_os_thread("engine", || {
474 increase_thread_priority();
475 task.run()
476 });
477 (incoming, outgoing)
478 }
479
480 fn valid_outcome(state: ForkchoiceState) -> TreeOutcome<OnForkChoiceUpdated> {
482 TreeOutcome::new(OnForkChoiceUpdated::valid(PayloadStatus::new(
483 PayloadStatusEnum::Valid,
484 Some(state.head_block_hash),
485 )))
486 }
487
488 pub fn sender(&self) -> Sender<FromEngine<EngineApiRequest<T, N>, N::Block>> {
490 self.incoming_tx.clone()
491 }
492
493 fn persistence_gap(&self) -> u64 {
496 self.canonical_in_memory_state.canonical_chain().count() as u64
497 }
498
499 fn persistence_backpressure_gap(&self) -> u64 {
501 self.persistence_gap().saturating_sub(self.config.memory_block_buffer_target())
502 }
503
504 fn should_backpressure(&self) -> bool {
509 self.persistence_state.in_progress() &&
510 self.persistence_backpressure_gap() >=
511 self.config.persistence_backpressure_threshold()
512 }
513
514 pub fn run(mut self) {
518 let terminated = self.run_until_exit();
519 drop(self);
520 if let Some(tx) = terminated {
521 let _ = tx.send(());
522 }
523 }
524
525 fn run_until_exit(&mut self) -> Option<oneshot::Sender<()>> {
526 loop {
527 match self.try_poll_persistence() {
552 Ok(true) => {
553 if let Err(err) = self.advance_persistence() {
554 error!(target: "engine::tree", %err, "Advancing persistence failed");
555 return None
556 }
557 continue;
558 }
559 Ok(false) => {}
560 Err(err) => {
561 error!(target: "engine::tree", %err, "Polling persistence failed");
562 return None
563 }
564 }
565
566 let event = if self.should_backpressure() {
567 self.metrics.engine.backpressure_active.set(1.0);
568 let stall_start = Instant::now();
569 let event = self.wait_for_persistence_event();
570 self.metrics.engine.backpressure_stall_duration.record(stall_start.elapsed());
571 event
572 } else {
573 self.metrics.engine.backpressure_active.set(0.0);
574 self.wait_for_event()
575 };
576
577 match event {
578 LoopEvent::EngineMessage(msg) => {
579 debug!(target: "engine::tree", %msg, "received new engine message");
580 match self.on_engine_message(msg) {
581 Ok(ops::ControlFlow::Break(tx)) => return Some(tx),
582 Ok(ops::ControlFlow::Continue(())) => {}
583 Err(fatal) => {
584 error!(target: "engine::tree", %fatal, "insert block fatal error");
585 return None
586 }
587 }
588 }
589 LoopEvent::PersistenceComplete { result, start_time } => {
590 if let Err(err) = self.on_persistence_complete(result, start_time) {
591 error!(target: "engine::tree", %err, "Persistence complete handling failed");
592 return None
593 }
594 }
595 LoopEvent::PayloadBuildFinished => {}
596 LoopEvent::Disconnected => {
597 error!(target: "engine::tree", "Channel disconnected");
598 return None
599 }
600 }
601
602 if let Err(err) = self.advance_persistence() {
607 error!(target: "engine::tree", %err, "Advancing persistence failed");
608 return None
609 }
610 }
611 }
612
613 fn wait_for_persistence_event(&mut self) -> LoopEvent<T, N> {
619 let maybe_persistence = self.persistence_state.rx.take();
620
621 if let Some((persistence_rx, start_time, _action)) = maybe_persistence {
622 match persistence_rx.recv() {
623 Ok(result) => LoopEvent::PersistenceComplete { result, start_time },
624 Err(_) => LoopEvent::Disconnected,
625 }
626 } else {
627 self.wait_for_event()
628 }
629 }
630
631 fn wait_for_event(&mut self) -> LoopEvent<T, N> {
636 let maybe_persistence = self.persistence_state.rx.take();
638
639 if let Some((persistence_rx, start_time, action)) = maybe_persistence {
640 crossbeam_channel::select_biased! {
643 recv(persistence_rx) -> result => {
644 match result {
646 Ok(result) => LoopEvent::PersistenceComplete {
647 result,
648 start_time,
649 },
650 Err(_) => LoopEvent::Disconnected,
651 }
652 },
653 recv(self.payload_build_finished) -> result => {
654 self.persistence_state.rx = Some((persistence_rx, start_time, action));
656 match result {
657 Ok(()) => LoopEvent::PayloadBuildFinished,
658 Err(_) => LoopEvent::Disconnected,
659 }
660 },
661 recv(self.incoming) -> msg => {
662 self.persistence_state.rx = Some((persistence_rx, start_time, action));
664 match msg {
665 Ok(m) => LoopEvent::EngineMessage(m),
666 Err(_) => LoopEvent::Disconnected,
667 }
668 },
669 }
670 } else {
671 crossbeam_channel::select_biased! {
673 recv(self.payload_build_finished) -> result => match result {
674 Ok(()) => LoopEvent::PayloadBuildFinished,
675 Err(_) => LoopEvent::Disconnected,
676 },
677 recv(self.incoming) -> msg => match msg {
678 Ok(m) => LoopEvent::EngineMessage(m),
679 Err(_) => LoopEvent::Disconnected,
680 },
681 }
682 }
683 }
684
685 fn on_downloaded(
691 &mut self,
692 mut blocks: Vec<SealedBlockWithAccessList<N::Block>>,
693 ) -> Result<Option<TreeEvent>, InsertBlockFatalError> {
694 if blocks.is_empty() {
695 return Ok(None)
697 }
698
699 trace!(target: "engine::tree", block_count = %blocks.len(), "received downloaded blocks");
700 let batch = self.config.max_execute_block_batch_size().min(blocks.len());
701 for block in blocks.drain(..batch) {
702 if let Some(event) = self.on_downloaded_block(block)? {
703 let needs_backfill = event.is_backfill_action();
704 self.on_tree_event(event)?;
705 if needs_backfill {
706 return Ok(None)
708 }
709 }
710 }
711
712 if !blocks.is_empty() {
714 let _ = self.incoming_tx.send(FromEngine::DownloadedBlocks(blocks));
715 }
716
717 Ok(None)
718 }
719
720 #[instrument(
735 level = "debug",
736 target = "engine::tree",
737 skip_all,
738 fields(block_hash = %payload.block_hash(), block_num = %payload.block_number()),
739 )]
740 fn on_new_payload(
741 &mut self,
742 payload: T::ExecutionData,
743 ) -> Result<TreeOutcome<PayloadStatus>, InsertBlockProcessingError> {
744 let _thread_resource_usage =
745 self.metrics.engine.new_payload.measure_thread_resource_usage();
746 trace!(target: "engine::tree", "invoked new payload");
747
748 let start = Instant::now();
750
751 let num_hash = payload.num_hash();
778 let engine_event = ConsensusEngineEvent::BlockReceived(num_hash);
779 self.emit_event(EngineApiEvent::BeaconConsensus(engine_event));
780
781 let block_hash = num_hash.hash;
782
783 if let Some(invalid) = self.find_invalid_ancestor(&payload) {
785 let status = self.handle_invalid_ancestor_payload(payload, invalid)?;
786 return Ok(TreeOutcome::new(status));
787 }
788
789 self.metrics.block_validation.record_payload_validation(start.elapsed().as_secs_f64());
791
792 let mut outcome = if self.backfill_sync_state.is_idle() {
793 self.try_insert_payload(payload)?.into_outcome()
794 } else {
795 TreeOutcome::new(self.try_buffer_payload(payload)?)
796 };
797
798 if outcome.outcome.is_valid() && self.is_sync_target_head(block_hash) {
800 if self.state.tree_state.canonical_block_hash() != block_hash {
802 outcome = outcome.with_event(TreeEvent::TreeAction(TreeAction::MakeCanonical {
803 sync_target_head: block_hash,
804 }));
805 }
806 }
807
808 self.metrics.block_validation.total_duration.record(start.elapsed().as_secs_f64());
810
811 Ok(outcome)
812 }
813
814 #[instrument(level = "debug", target = "engine::tree", skip_all)]
816 fn try_insert_payload(
817 &mut self,
818 payload: T::ExecutionData,
819 ) -> Result<TryInsertPayloadResult, InsertBlockProcessingError> {
820 let block_hash = payload.block_hash();
821 let num_hash = payload.num_hash();
822 let parent_hash = payload.parent_hash();
823 let mut latest_valid_hash = None;
824
825 match self.insert_payload(payload) {
826 Ok(status) => {
827 let (status, already_seen) = match status {
828 InsertPayloadOk::Inserted(BlockStatus::Valid) => {
829 latest_valid_hash = Some(block_hash);
830 self.try_connect_buffered_blocks(num_hash)?;
831 (PayloadStatusEnum::Valid, false)
832 }
833 InsertPayloadOk::AlreadySeen(BlockStatus::Valid) => {
834 latest_valid_hash = Some(block_hash);
835 (PayloadStatusEnum::Valid, true)
836 }
837 InsertPayloadOk::Inserted(BlockStatus::Disconnected { .. }) => {
838 (PayloadStatusEnum::Syncing, false)
839 }
840 InsertPayloadOk::AlreadySeen(BlockStatus::Disconnected { .. }) => {
841 (PayloadStatusEnum::Syncing, true)
843 }
844 };
845
846 Ok(TryInsertPayloadResult {
847 status: PayloadStatus::new(status, latest_valid_hash),
848 already_seen,
849 })
850 }
851 Err(error) => {
852 let status = match error {
853 InsertPayloadError::Block(error) => self.on_insert_block_error(error)?,
854 InsertPayloadError::Payload(error) => self
855 .on_new_payload_error(error, num_hash, parent_hash)
856 .map_err(InsertBlockFatalError::from)?,
857 };
858
859 Ok(TryInsertPayloadResult { status, already_seen: false })
860 }
861 }
862 }
863
864 fn try_buffer_payload(
873 &mut self,
874 payload: T::ExecutionData,
875 ) -> Result<PayloadStatus, InsertBlockProcessingError> {
876 let parent_hash = payload.parent_hash();
877 let num_hash = payload.num_hash();
878
879 match self.payload_validator.convert_payload_to_block(payload) {
880 Ok(block) => {
882 if let Err(error) = self.buffer_block(block) {
883 self.on_insert_block_error(error)
884 } else {
885 Ok(PayloadStatus::from_status(PayloadStatusEnum::Syncing))
886 }
887 }
888 Err(error) => Ok(self
889 .on_new_payload_error(error, num_hash, parent_hash)
890 .map_err(InsertBlockFatalError::from)?),
891 }
892 }
893
894 fn on_new_head(&self, new_head: B256) -> ProviderResult<Option<NewCanonicalChain<N>>> {
901 let Some(new_head_block) = self.state.tree_state.blocks_by_hash.get(&new_head) else {
903 debug!(target: "engine::tree", new_head=?new_head, "New head block not found in inmemory tree state");
904 self.metrics.engine.executed_new_block_cache_miss.increment(1);
905 return Ok(None)
906 };
907
908 let new_head_number = new_head_block.recovered_block().number();
909 let mut current_canonical_number = self.state.tree_state.current_canonical_head.number;
910
911 let mut new_chain = vec![new_head_block.clone()];
912 let mut current_hash = new_head_block.recovered_block().parent_hash();
913 let mut current_number = new_head_number - 1;
914
915 while current_number > current_canonical_number {
920 if let Some(block) = self.state.tree_state.executed_block_by_hash(current_hash).cloned()
921 {
922 current_hash = block.recovered_block().parent_hash();
923 current_number -= 1;
924 new_chain.push(block);
925 } else {
926 warn!(target: "engine::tree", current_hash=?current_hash, "Sidechain block not found in TreeState");
927 return Ok(None)
930 }
931 }
932
933 if current_hash == self.state.tree_state.current_canonical_head.hash {
936 new_chain.reverse();
937
938 return Ok(Some(NewCanonicalChain::Commit { new: new_chain }))
940 }
941
942 let mut old_chain = Vec::new();
944 let mut old_hash = self.state.tree_state.current_canonical_head.hash;
945
946 while current_canonical_number > current_number {
949 let block = self.canonical_block_by_hash(old_hash)?;
950 old_hash = block.recovered_block().parent_hash();
951 old_chain.push(block);
952 current_canonical_number -= 1;
953 }
954
955 debug_assert_eq!(current_number, current_canonical_number);
957
958 while old_hash != current_hash {
961 let block = self.canonical_block_by_hash(old_hash)?;
962 old_hash = block.recovered_block().parent_hash();
963 old_chain.push(block);
964
965 if let Some(block) = self.state.tree_state.executed_block_by_hash(current_hash).cloned()
966 {
967 current_hash = block.recovered_block().parent_hash();
968 new_chain.push(block);
969 } else {
970 warn!(target: "engine::tree", invalid_hash=?current_hash, "New chain block not found in TreeState");
972 return Ok(None)
973 }
974 }
975 new_chain.reverse();
976 old_chain.reverse();
977
978 Ok(Some(NewCanonicalChain::Reorg { new: new_chain, old: old_chain }))
979 }
980
981 fn update_latest_block_to_canonical_ancestor(
993 &mut self,
994 canonical_header: &SealedHeader<N::BlockHeader>,
995 ) -> ProviderResult<()> {
996 debug!(target: "engine::tree", head = ?canonical_header.num_hash(), "Update latest block to canonical ancestor");
997 let current_head_number = self.state.tree_state.canonical_block_number();
998 let new_head_number = canonical_header.number();
999 let new_head_hash = canonical_header.hash();
1000
1001 self.state.tree_state.set_canonical_head(canonical_header.num_hash());
1003
1004 if new_head_number < current_head_number {
1006 debug!(
1007 target: "engine::tree",
1008 current_head = current_head_number,
1009 new_head = new_head_number,
1010 new_head_hash = ?new_head_hash,
1011 "FCU unwind detected: reverting to canonical ancestor"
1012 );
1013
1014 self.handle_canonical_chain_unwind(current_head_number, canonical_header)
1015 } else {
1016 debug!(
1017 target: "engine::tree",
1018 previous_head = current_head_number,
1019 new_head = new_head_number,
1020 new_head_hash = ?new_head_hash,
1021 "Advancing latest block to canonical ancestor"
1022 );
1023 self.handle_chain_advance_or_same_height(canonical_header)
1024 }
1025 }
1026
1027 fn handle_canonical_chain_unwind(
1030 &self,
1031 current_head_number: u64,
1032 canonical_header: &SealedHeader<N::BlockHeader>,
1033 ) -> ProviderResult<()> {
1034 let new_head_number = canonical_header.number();
1035 debug!(
1036 target: "engine::tree",
1037 from = current_head_number,
1038 to = new_head_number,
1039 "Handling unwind: collecting blocks to remove from in-memory state"
1040 );
1041
1042 let old_blocks =
1044 self.collect_blocks_for_canonical_unwind(new_head_number, current_head_number);
1045
1046 self.apply_canonical_ancestor_via_reorg(canonical_header, old_blocks)
1048 }
1049
1050 fn collect_blocks_for_canonical_unwind(
1052 &self,
1053 new_head_number: u64,
1054 current_head_number: u64,
1055 ) -> Vec<ExecutedBlock<N>> {
1056 let mut old_blocks =
1057 Vec::with_capacity((current_head_number.saturating_sub(new_head_number)) as usize);
1058
1059 for block_num in (new_head_number + 1)..=current_head_number {
1060 if let Some(block_state) = self.canonical_in_memory_state.state_by_number(block_num) {
1061 let executed_block = block_state.block_ref().clone();
1062 old_blocks.push(executed_block);
1063 debug!(
1064 target: "engine::tree",
1065 block_number = block_num,
1066 "Collected block for removal from in-memory state"
1067 );
1068 }
1069 }
1070
1071 if old_blocks.is_empty() {
1072 debug!(
1073 target: "engine::tree",
1074 "No blocks found in memory to remove, will clear and reset state"
1075 );
1076 }
1077
1078 old_blocks
1079 }
1080
1081 fn apply_canonical_ancestor_via_reorg(
1083 &self,
1084 canonical_header: &SealedHeader<N::BlockHeader>,
1085 old_blocks: Vec<ExecutedBlock<N>>,
1086 ) -> ProviderResult<()> {
1087 let new_head_hash = canonical_header.hash();
1088 let new_head_number = canonical_header.number();
1089
1090 let executed_block = self.canonical_block_by_hash(new_head_hash)?;
1092 self.canonical_in_memory_state
1094 .update_chain(NewCanonicalChain::Reorg { new: vec![executed_block], old: old_blocks });
1095
1096 self.canonical_in_memory_state.set_canonical_head(canonical_header.clone());
1099
1100 debug!(
1101 target: "engine::tree",
1102 block_number = new_head_number,
1103 block_hash = ?new_head_hash,
1104 "Successfully loaded canonical ancestor into memory via reorg"
1105 );
1106
1107 Ok(())
1108 }
1109
1110 fn handle_chain_advance_or_same_height(
1112 &self,
1113 canonical_header: &SealedHeader<N::BlockHeader>,
1114 ) -> ProviderResult<()> {
1115 self.ensure_block_in_memory(canonical_header.number(), canonical_header.hash())?;
1117
1118 self.canonical_in_memory_state.set_canonical_head(canonical_header.clone());
1120
1121 Ok(())
1122 }
1123
1124 fn ensure_block_in_memory(&self, block_number: u64, block_hash: B256) -> ProviderResult<()> {
1126 if self.canonical_in_memory_state.state_by_number(block_number).is_some() {
1128 return Ok(());
1129 }
1130
1131 let executed_block = self.canonical_block_by_hash(block_hash)?;
1133 self.canonical_in_memory_state
1134 .update_chain(NewCanonicalChain::Commit { new: vec![executed_block] });
1135
1136 debug!(
1137 target: "engine::tree",
1138 block_number,
1139 block_hash = ?block_hash,
1140 "Added canonical block to in-memory state"
1141 );
1142
1143 Ok(())
1144 }
1145
1146 #[instrument(level = "debug", target = "engine::tree", skip_all, fields(head = % state.head_block_hash, safe = % state.safe_block_hash,finalized = % state.finalized_block_hash))]
1155 fn on_forkchoice_updated(
1156 &mut self,
1157 state: ForkchoiceState,
1158 attrs: Option<T::PayloadAttributes>,
1159 ) -> ProviderResult<TreeOutcome<OnForkChoiceUpdated>> {
1160 trace!(target: "engine::tree", ?attrs, "invoked forkchoice update");
1161
1162 self.record_forkchoice_metrics();
1164
1165 if let Some(early_result) = self.validate_forkchoice_state(state)? {
1167 return Ok(TreeOutcome::new(early_result));
1168 }
1169
1170 if let Some(result) = self.handle_canonical_head(state, &attrs)? {
1172 return Ok(result);
1173 }
1174
1175 if let Some(result) = self.apply_chain_update(state, &attrs)? {
1178 return Ok(result);
1179 }
1180
1181 self.handle_missing_block(state)
1183 }
1184
1185 fn record_forkchoice_metrics(&self) {
1187 self.canonical_in_memory_state.on_forkchoice_update_received();
1188 }
1189
1190 fn validate_forkchoice_state(
1195 &mut self,
1196 state: ForkchoiceState,
1197 ) -> ProviderResult<Option<OnForkChoiceUpdated>> {
1198 if state.head_block_hash.is_zero() {
1199 return Ok(Some(OnForkChoiceUpdated::invalid_state()));
1200 }
1201
1202 let lowest_buffered_ancestor_fcu = self.lowest_buffered_ancestor_or(state.head_block_hash);
1205 if let Some(status) = self.check_invalid_ancestor(lowest_buffered_ancestor_fcu)? {
1206 return Ok(Some(OnForkChoiceUpdated::with_invalid(status)));
1207 }
1208
1209 if !self.backfill_sync_state.is_idle() {
1210 if self.backfill_sync_state.is_pending() || self.backfill_sync_state.is_active() {
1214 self.send_event(EngineApiEvent::BackfillAction(BackfillAction::UpdateTarget(
1215 state.head_block_hash,
1216 )));
1217 self.forward_finalized(state.finalized_block_hash);
1218 }
1219 trace!(target: "engine::tree", "Pipeline is syncing, skipping forkchoice update");
1222 return Ok(Some(OnForkChoiceUpdated::syncing()));
1223 }
1224
1225 Ok(None)
1226 }
1227
1228 fn handle_canonical_head(
1234 &mut self,
1235 state: ForkchoiceState,
1236 attrs: &Option<T::PayloadAttributes>, ) -> ProviderResult<Option<TreeOutcome<OnForkChoiceUpdated>>> {
1238 if self.state.tree_state.canonical_block_hash() != state.head_block_hash {
1253 return Ok(None);
1254 }
1255
1256 trace!(target: "engine::tree", "fcu head hash is already canonical");
1257
1258 if !self.is_consistent_forkchoice_state(state, None)? {
1259 return Ok(Some(TreeOutcome::new(OnForkChoiceUpdated::invalid_state())));
1260 }
1261
1262 if let Err(outcome) = self.ensure_consistent_forkchoice_state(state) {
1264 return Ok(Some(TreeOutcome::new(outcome)));
1266 }
1267
1268 self.payload_validator.on_canonical_head_changed(state.head_block_hash, &self.state);
1269
1270 if let Some(attr) = attrs {
1272 let tip = self
1273 .sealed_header_by_hash(self.state.tree_state.canonical_block_hash())?
1274 .ok_or_else(|| {
1275 ProviderError::HeaderNotFound(state.head_block_hash.into())
1278 })?;
1279 let updated = self.process_payload_attributes(attr.clone(), &tip, state);
1281 return Ok(Some(TreeOutcome::new(updated)));
1282 }
1283
1284 Ok(Some(Self::valid_outcome(state)))
1286 }
1287
1288 fn apply_chain_update(
1300 &mut self,
1301 state: ForkchoiceState,
1302 attrs: &Option<T::PayloadAttributes>,
1303 ) -> ProviderResult<Option<TreeOutcome<OnForkChoiceUpdated>>> {
1304 if let Ok(Some(canonical_header)) = self.find_canonical_header(state.head_block_hash) {
1306 debug!(target: "engine::tree", head = canonical_header.number(), "fcu head block is already canonical");
1307
1308 let always_trigger_payload_job = self.engine_kind.is_opstack() ||
1311 self.config.always_process_payload_attributes_on_canonical_head();
1312
1313 if !always_trigger_payload_job &&
1322 self.canonical_in_memory_state
1323 .get_finalized_num_hash()
1324 .is_some_and(|finalized| canonical_header.number() < finalized.number)
1325 {
1326 debug!(target: "engine::tree", head = canonical_header.number(), "rejecting canonical ancestor fcu below the finalized block");
1327 return Ok(Some(TreeOutcome::new(OnForkChoiceUpdated::too_deep_reorg())));
1328 }
1329
1330 if !self.is_consistent_forkchoice_state(state, None)? {
1331 return Ok(Some(TreeOutcome::new(OnForkChoiceUpdated::invalid_state())));
1332 }
1333
1334 if always_trigger_payload_job && self.config.unwind_canonical_header() {
1340 self.update_latest_block_to_canonical_ancestor(&canonical_header)?;
1341 }
1342
1343 if let Some(attr) = attrs {
1348 debug!(target: "engine::tree", head = canonical_header.number(), "handling payload attributes for canonical head");
1349 let updated =
1351 self.process_payload_attributes(attr.clone(), &canonical_header, state);
1352 return Ok(Some(TreeOutcome::new(updated)));
1353 }
1354
1355 return Ok(Some(Self::valid_outcome(state)));
1358 }
1359
1360 if let Some(chain_update) = self.on_new_head(state.head_block_hash)? {
1362 if !self.is_consistent_forkchoice_state(state, Some(&chain_update))? {
1363 return Ok(Some(TreeOutcome::new(OnForkChoiceUpdated::invalid_state())));
1364 }
1365
1366 let tip = chain_update.tip().clone_sealed_header();
1367 self.on_canonical_chain_update(chain_update);
1368
1369 if let Err(outcome) = self.ensure_consistent_forkchoice_state(state) {
1371 return Ok(Some(TreeOutcome::new(outcome)));
1373 }
1374
1375 if let Some(attr) = attrs {
1376 let updated = self.process_payload_attributes(attr.clone(), &tip, state);
1378 return Ok(Some(TreeOutcome::new(updated)));
1379 }
1380
1381 return Ok(Some(Self::valid_outcome(state)));
1382 }
1383
1384 Ok(None)
1385 }
1386
1387 fn handle_missing_block(
1392 &self,
1393 state: ForkchoiceState,
1394 ) -> ProviderResult<TreeOutcome<OnForkChoiceUpdated>> {
1395 let target = if self.state.forkchoice_state_tracker.is_empty() &&
1402 !state.safe_block_hash.is_zero() &&
1404 self.find_canonical_header(state.safe_block_hash).ok().flatten().is_none()
1405 {
1406 debug!(target: "engine::tree", "missing safe block on initial FCU, downloading safe block");
1407 state.safe_block_hash
1408 } else {
1409 state.head_block_hash
1410 };
1411
1412 let target = self.lowest_buffered_ancestor_or(target);
1413 trace!(target: "engine::tree", %target, "downloading missing block");
1414
1415 Ok(TreeOutcome::new(OnForkChoiceUpdated::valid(PayloadStatus::from_status(
1416 PayloadStatusEnum::Syncing,
1417 )))
1418 .with_event(TreeEvent::Download(
1419 DownloadRequest::single_block(target)
1420 .with_access_lists(self.should_download_access_lists()),
1421 )))
1422 }
1423
1424 fn remove_blocks(&mut self, new_tip_num: u64) {
1427 debug!(target: "engine::tree", ?new_tip_num, last_persisted_block_number=?self.persistence_state.last_persisted_block.number, "Removing blocks using persistence task");
1428 if new_tip_num < self.persistence_state.last_persisted_block.number {
1429 debug!(target: "engine::tree", ?new_tip_num, "Starting remove blocks job");
1430 self.state.set_pending_sparse_trie_prune(false);
1431 let (tx, rx) = crossbeam_channel::bounded(1);
1432 let _ = self.persistence.remove_blocks_above(new_tip_num, tx);
1433 self.persistence_state.start_remove(new_tip_num, rx);
1434 }
1435 }
1436
1437 fn persist_blocks(&mut self, input: SaveBlocksInput<N>) {
1440 let highest_num_hash = input.last_block();
1441 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");
1442
1443 let (tx, rx) = crossbeam_channel::bounded(1);
1444 let _ = self.persistence.save_blocks(input, tx);
1445
1446 self.persistence_state.start_save(highest_num_hash, rx);
1447 }
1448
1449 fn advance_persistence(&mut self) -> Result<(), AdvancePersistenceError> {
1454 if !self.persistence_state.in_progress() {
1455 let payload_build_active = self.payload_builds.is_active();
1456 if let Some(new_tip_num) = self.find_disk_reorg()? {
1457 self.remove_blocks(new_tip_num)
1458 } else if self.backfill_sync_state.is_pending_revalidation() &&
1459 !payload_build_active &&
1460 self.persistence_state.last_state_trie_persisted_block !=
1461 self.persistence_state.last_persisted_block
1462 {
1463 let Some(input) = self.get_save_blocks_input(PersistTarget::Persisted) else {
1464 return Err(AdvancePersistenceError::StateTrieCatchupUnavailable)
1465 };
1466 self.persist_blocks(input);
1467 } else if self.backfill_sync_state.is_pending_revalidation() && !payload_build_active {
1468 self.revalidate_pending_backfill()?;
1469 } else if let Some(input) = self.get_save_blocks_input(PersistTarget::Threshold) {
1470 self.persist_blocks(input);
1471 }
1472 }
1473
1474 Ok(())
1475 }
1476
1477 fn persist_until_complete(&mut self) -> Result<(), AdvancePersistenceError> {
1479 loop {
1480 if let Some((rx, start_time, action)) = self.persistence_state.rx.take() {
1482 debug!(target: "engine::tree", ?action, "waiting for in-flight persistence");
1483 let result = rx.recv().map_err(|_| AdvancePersistenceError::ChannelClosed)?;
1484 self.on_persistence_complete(result, start_time)?;
1485 continue
1486 }
1487
1488 if let Some(new_tip_num) = self.find_disk_reorg()? {
1492 self.remove_blocks(new_tip_num);
1493 continue
1494 }
1495
1496 let Some(input) = self.get_save_blocks_input(PersistTarget::Head) else {
1497 debug!(target: "engine::tree", "persistence complete, signaling termination");
1498 return Ok(())
1499 };
1500
1501 debug!(target: "engine::tree", count = input.persist_rest_blocks().len(), "persisting remaining blocks before shutdown");
1502 self.persist_blocks(input);
1503 }
1504 }
1505
1506 fn try_poll_persistence(&mut self) -> Result<bool, AdvancePersistenceError> {
1510 let Some((rx, start_time, action)) = self.persistence_state.rx.take() else {
1511 return Ok(false);
1512 };
1513
1514 match rx.try_recv() {
1515 Ok(result) => {
1516 self.on_persistence_complete(result, start_time)?;
1517 Ok(true)
1518 }
1519 Err(crossbeam_channel::TryRecvError::Empty) => {
1520 self.persistence_state.rx = Some((rx, start_time, action));
1522 Ok(false)
1523 }
1524 Err(crossbeam_channel::TryRecvError::Disconnected) => {
1525 Err(AdvancePersistenceError::ChannelClosed)
1526 }
1527 }
1528 }
1529
1530 fn on_persistence_complete(
1532 &mut self,
1533 result: PersistenceResult,
1534 start_time: Instant,
1535 ) -> Result<(), AdvancePersistenceError> {
1536 self.metrics.engine.persistence_duration.record(start_time.elapsed());
1537
1538 let PersistenceResult { last_block, last_state_trie_block, commit_duration } = result;
1539 debug_assert!(
1540 last_state_trie_block.number <= last_block.number,
1541 "state/trie frontier cannot exceed the last persisted block"
1542 );
1543
1544 debug!(target: "engine::tree", ?last_block, ?last_state_trie_block, elapsed=?start_time.elapsed(), "Finished persisting, calling finish");
1545 self.persistence_state.finish(last_block, last_state_trie_block);
1546
1547 let last_block_number = last_block.number;
1548
1549 let min_threshold = last_block_number.saturating_sub(CHANGESET_CACHE_RETENTION_BLOCKS);
1553 let eviction_threshold =
1554 if let Some(finalized) = self.canonical_in_memory_state.get_finalized_num_hash() {
1555 finalized.number.min(min_threshold)
1557 } else {
1558 min_threshold
1560 };
1561 debug!(
1562 target: "engine::tree",
1563 last_persisted = last_block_number,
1564 finalized_number = ?self.canonical_in_memory_state.get_finalized_num_hash().map(|f| f.number),
1565 eviction_threshold,
1566 "Evicting changesets below threshold"
1567 );
1568 self.state.tree_state.overlay_manager.evict_cached_changesets(eviction_threshold);
1569
1570 self.on_new_persisted_block()?;
1571
1572 self.purge_timing_stats(last_block_number, commit_duration);
1573
1574 Ok(())
1575 }
1576
1577 fn on_engine_message(
1582 &mut self,
1583 msg: FromEngine<EngineApiRequest<T, N>, N::Block>,
1584 ) -> Result<ops::ControlFlow<oneshot::Sender<()>>, InsertBlockFatalError> {
1585 match msg {
1586 FromEngine::Event(event) => match event {
1587 FromOrchestrator::BackfillSyncStarted => {
1588 debug!(target: "engine::tree", "received backfill sync started event");
1589 self.backfill_sync_state = BackfillSyncState::Active;
1590 }
1591 FromOrchestrator::BackfillSyncFinished(ctrl) => {
1592 self.on_backfill_sync_finished(ctrl)?;
1593 }
1594 FromOrchestrator::Terminate { tx } => {
1595 debug!(target: "engine::tree", "received terminate request");
1596 trace!(target: "engine::tree", "finishing termination, persisting remaining blocks");
1597 if let Err(err) = self.persist_until_complete() {
1598 error!(target: "engine::tree", %err, "Termination failed");
1599 }
1600 return Ok(ops::ControlFlow::Break(tx))
1601 }
1602 },
1603 FromEngine::Request(request) => {
1604 match request {
1605 EngineApiRequest::InsertExecutedBlock(payload) => {
1606 let block_num_hash = payload.recovered_block.num_hash();
1607 if block_num_hash.number <= self.state.tree_state.canonical_block_number() {
1608 return Ok(ops::ControlFlow::Continue(()))
1610 }
1611
1612 if self.state.tree_state.contains_hash(&block_num_hash.hash) {
1613 return Ok(ops::ControlFlow::Continue(()))
1615 }
1616
1617 debug!(target: "engine::tree", block=?block_num_hash, "inserting already executed block");
1618 let now = Instant::now();
1619
1620 let block = match self.payload_validator.on_inserted_executed_block(payload)
1621 {
1622 Ok(block) => block,
1623 Err(err) => {
1624 warn!(target: "engine::tree", %err, block=?block_num_hash, "Failed to insert already executed block");
1625 return Ok(ops::ControlFlow::Continue(()))
1626 }
1627 };
1628
1629 let is_pending = self.state.tree_state.canonical_block_hash() ==
1630 block.recovered_block().parent_hash();
1631 self.state.tree_state.insert_executed(block.clone());
1632 self.metrics
1633 .engine
1634 .executed_blocks
1635 .set(self.state.tree_state.block_count() as f64);
1636
1637 if is_pending {
1638 debug!(target: "engine::tree", pending=?block_num_hash, "updating pending block");
1639 self.canonical_in_memory_state.set_pending_block(block.clone());
1640 }
1641
1642 self.metrics.engine.inserted_already_executed_blocks.increment(1);
1643 self.emit_event(EngineApiEvent::BeaconConsensus(
1644 ConsensusEngineEvent::CanonicalBlockAdded(block, now.elapsed()),
1645 ));
1646 }
1647 EngineApiRequest::Beacon(request) => {
1648 match request {
1649 BeaconEngineMessage::ForkchoiceUpdated {
1650 cause,
1651 state,
1652 payload_attrs,
1653 tx,
1654 } => {
1655 let _cause = cause.enter();
1656 let has_attrs = payload_attrs.is_some();
1657
1658 let start = Instant::now();
1659 let mut output = self.on_forkchoice_updated(state, payload_attrs);
1660
1661 if let Ok(res) = &mut output {
1662 self.state
1664 .forkchoice_state_tracker
1665 .set_latest(state, res.outcome.forkchoice_status());
1666
1667 self.emit_event(ConsensusEngineEvent::ForkchoiceUpdated(
1669 state,
1670 res.outcome.forkchoice_status(),
1671 ));
1672
1673 self.on_maybe_tree_event(res.event.take())?;
1675 }
1676
1677 if let Err(ref err) = output {
1678 error!(target: "engine::tree", %err, ?state, "Error processing forkchoice update");
1679 }
1680
1681 self.metrics.engine.forkchoice_updated.update_response_metrics(
1682 start,
1683 &mut self.metrics.engine.new_payload.latest_finish_at,
1684 has_attrs,
1685 &output,
1686 );
1687
1688 if let Err(err) =
1689 tx.send(output.map(|o| o.outcome).map_err(Into::into))
1690 {
1691 self.metrics
1692 .engine
1693 .failed_forkchoice_updated_response_deliveries
1694 .increment(1);
1695 warn!(target: "engine::tree", ?state, elapsed=?start.elapsed(), "Failed to deliver forkchoiceUpdated response, receiver dropped (request cancelled): {err:?}");
1696 }
1697 }
1698 BeaconEngineMessage::NewPayload { cause, payload, tx } => {
1699 let _cause = cause.enter();
1700 let start = Instant::now();
1701 let gas_used = payload.gas_used();
1702 let num_hash = payload.num_hash();
1703 let mut output = self.on_new_payload(payload);
1704 self.metrics.engine.new_payload.update_response_metrics(
1705 start,
1706 &mut self.metrics.engine.forkchoice_updated.latest_finish_at,
1707 &output,
1708 gas_used,
1709 );
1710
1711 let maybe_event =
1712 output.as_mut().ok().and_then(|out| out.event.take());
1713
1714 if let Err(err) =
1716 tx.send(output.map(|o| o.outcome).map_err(Into::into))
1717 {
1718 warn!(target: "engine::tree", payload=?num_hash, elapsed=?start.elapsed(), "Failed to deliver newPayload response, receiver dropped (request cancelled): {err:?}");
1719 self.metrics
1720 .engine
1721 .failed_new_payload_response_deliveries
1722 .increment(1);
1723 }
1724
1725 self.on_maybe_tree_event(maybe_event)?;
1727 }
1728 BeaconEngineMessage::RethNewPayload {
1729 cause,
1730 payload,
1731 wait_for_persistence,
1732 wait_for_caches,
1733 tx,
1734 enqueued_at,
1735 } => {
1736 let _cause = cause.enter();
1737 debug!(
1738 target: "engine::tree",
1739 wait_for_persistence,
1740 wait_for_caches,
1741 "Processing reth_newPayload"
1742 );
1743
1744 let backpressure_wait = enqueued_at.elapsed();
1745
1746 let explicit_persistence_wait = if wait_for_persistence {
1747 let pending_persistence = self.persistence_state.rx.take();
1748 if let Some((rx, start_time, _action)) = pending_persistence {
1749 let (persistence_tx, persistence_rx) =
1750 std::sync::mpsc::channel();
1751 self.runtime.spawn_blocking_named(
1752 "wait-persist",
1753 move || {
1754 let start = Instant::now();
1755 let result = rx
1756 .recv()
1757 .expect("persistence state channel closed");
1758 let _ = persistence_tx.send((
1759 result,
1760 start_time,
1761 start.elapsed(),
1762 ));
1763 },
1764 );
1765 let (result, start_time, wait_duration) = persistence_rx
1766 .recv()
1767 .expect("persistence result channel closed");
1768 let _ = self.on_persistence_complete(result, start_time);
1769 wait_duration
1770 } else {
1771 Duration::ZERO
1772 }
1773 } else {
1774 Duration::ZERO
1775 };
1776
1777 let cache_wait = wait_for_caches
1778 .then(|| self.payload_validator.wait_for_caches());
1779
1780 let start = Instant::now();
1781 let gas_used = payload.gas_used();
1782 let num_hash = payload.num_hash();
1783 let mut output = self.on_new_payload(payload);
1784 let latency = start.elapsed();
1785 self.metrics.engine.new_payload.update_response_metrics(
1786 start,
1787 &mut self.metrics.engine.forkchoice_updated.latest_finish_at,
1788 &output,
1789 gas_used,
1790 );
1791
1792 let maybe_event =
1793 output.as_mut().ok().and_then(|out| out.event.take());
1794
1795 let timings = NewPayloadTimings {
1796 latency,
1797 persistence_wait: backpressure_wait + explicit_persistence_wait,
1798 execution_cache_wait: cache_wait
1799 .map(|wait| wait.execution_cache),
1800 sparse_trie_wait: cache_wait.map(|wait| wait.sparse_trie),
1801 };
1802 if let Err(err) = tx
1803 .send(output.map(|o| (o.outcome, timings)).map_err(Into::into))
1804 {
1805 error!(
1806 target: "engine::tree",
1807 payload=?num_hash,
1808 elapsed=?latency,
1809 "Failed to send event: {err:?}"
1810 );
1811 self.metrics
1812 .engine
1813 .failed_new_payload_response_deliveries
1814 .increment(1);
1815 }
1816
1817 self.on_maybe_tree_event(maybe_event)?;
1818 }
1819 }
1820 }
1821 }
1822 }
1823 FromEngine::DownloadedBlocks(blocks) => {
1824 if let Some(event) = self.on_downloaded(blocks)? {
1825 self.on_tree_event(event)?;
1826 }
1827 }
1828 }
1829 Ok(ops::ControlFlow::Continue(()))
1830 }
1831
1832 fn on_backfill_sync_finished(
1846 &mut self,
1847 ctrl: ControlFlow,
1848 ) -> Result<(), InsertBlockFatalError> {
1849 debug!(target: "engine::tree", "received backfill sync finished event");
1850 self.backfill_sync_state = BackfillSyncState::Idle;
1851
1852 let backfill_height = if let ControlFlow::Unwind { bad_block, target } = &ctrl {
1854 warn!(target: "engine::tree", invalid_block=?bad_block, "Bad block detected in unwind");
1855 self.state.invalid_headers.insert(**bad_block);
1857
1858 Some(*target)
1860 } else {
1861 ctrl.block_number()
1863 };
1864
1865 let Some(backfill_height) = backfill_height else { return Ok(()) };
1867
1868 let Some(backfill_num_hash) = self
1874 .provider
1875 .block_hash(backfill_height)?
1876 .map(|hash| BlockNumHash { hash, number: backfill_height })
1877 else {
1878 debug!(target: "engine::tree", ?ctrl, "Backfill block not found");
1879 return Ok(())
1880 };
1881
1882 if ctrl.is_unwind() {
1883 self.state.set_pending_sparse_trie_prune(false);
1886 self.state.tree_state.reset(backfill_num_hash)
1887 } else {
1888 self.state.tree_state.remove_until(
1889 backfill_num_hash,
1890 self.persistence_state.last_persisted_block.hash,
1891 Some(backfill_num_hash),
1892 );
1893 }
1894
1895 self.metrics.engine.executed_blocks.set(self.state.tree_state.block_count() as f64);
1896 self.metrics.tree.canonical_chain_height.set(backfill_height as f64);
1897
1898 self.state.buffer.remove_old_blocks(backfill_height);
1900 self.purge_timing_stats(backfill_height, None);
1901 self.canonical_in_memory_state.clear_state();
1904
1905 if let Ok(Some(new_head)) = self.provider.sealed_header(backfill_height) {
1906 self.state.tree_state.set_canonical_head(new_head.num_hash());
1909 self.persistence_state.finish(new_head.num_hash(), new_head.num_hash());
1910
1911 self.canonical_in_memory_state.set_canonical_head(new_head);
1913
1914 if !ctrl.is_unwind() {
1918 self.on_canonicalized_sync_target(backfill_num_hash.hash);
1919 }
1920 }
1921
1922 let Some(sync_target_state) = self.state.forkchoice_state_tracker.sync_target_state()
1925 else {
1926 return Ok(())
1927 };
1928 if !self.engine_kind.is_opstack() && sync_target_state.finalized_block_hash.is_zero() {
1929 return Ok(())
1931 }
1932 let target_hash = self.backfill_target_hash(sync_target_state);
1933 if target_hash.is_zero() {
1934 return Ok(())
1935 }
1936 let newest_target = self.state.buffer.block(&target_hash).map(|block| block.number());
1938
1939 if let Some(backfill_target) =
1945 ctrl.block_number().zip(newest_target).and_then(|(progress, target_number)| {
1946 self.backfill_sync_target(progress, target_number, None)
1949 })
1950 {
1951 self.emit_event(EngineApiEvent::BackfillAction(BackfillAction::Start(
1953 backfill_target.into(),
1954 )));
1955 return Ok(())
1956 };
1957
1958 if let Some(lowest_buffered) =
1960 self.state.buffer.lowest_ancestor(&sync_target_state.head_block_hash)
1961 {
1962 let current_head_num = self.state.tree_state.current_canonical_head.number;
1963 let target_head_num = lowest_buffered.number();
1964
1965 if let Some(distance) = self.distance_from_local_tip(current_head_num, target_head_num)
1966 {
1967 debug!(
1969 target: "engine::tree",
1970 %current_head_num,
1971 %target_head_num,
1972 %distance,
1973 "Backfill complete, downloading remaining blocks to reach FCU target"
1974 );
1975
1976 self.emit_event(EngineApiEvent::Download(
1977 DownloadRequest::block_range(lowest_buffered.parent_hash(), distance)
1978 .with_access_lists(self.should_download_access_lists()),
1979 ));
1980 return Ok(());
1981 }
1982 } else {
1983 debug!(
1986 target: "engine::tree",
1987 head_hash = %sync_target_state.head_block_hash,
1988 "Backfill complete but head block not buffered, requesting download"
1989 );
1990 self.emit_event(EngineApiEvent::Download(
1991 DownloadRequest::single_block(sync_target_state.head_block_hash)
1992 .with_access_lists(self.should_download_access_lists()),
1993 ));
1994 return Ok(());
1995 }
1996
1997 self.try_connect_buffered_blocks(self.state.tree_state.current_canonical_head)
1999 }
2000
2001 fn make_canonical(&mut self, target: B256) -> ProviderResult<()> {
2005 if let Some(chain_update) = self.on_new_head(target)? {
2006 self.on_canonical_chain_update(chain_update);
2007 }
2008
2009 self.on_canonicalized_sync_target(target);
2010
2011 Ok(())
2012 }
2013
2014 fn on_canonicalized_sync_target(&mut self, target: B256) {
2016 let Some(sync_target_state) = self
2017 .state
2018 .forkchoice_state_tracker
2019 .sync_target_state()
2020 .filter(|state| state.head_block_hash == target)
2021 else {
2022 return;
2023 };
2024
2025 if let Err(outcome) = self.ensure_consistent_forkchoice_state(sync_target_state) {
2026 debug!(
2027 target: "engine::tree",
2028 head = %sync_target_state.head_block_hash,
2029 safe = %sync_target_state.safe_block_hash,
2030 finalized = %sync_target_state.finalized_block_hash,
2031 ?outcome,
2032 "Canonicalized sync target head before safe/finalized could be applied"
2033 );
2034 return;
2035 }
2036
2037 self.state.forkchoice_state_tracker.promote_sync_target_to_valid(sync_target_state);
2038 }
2039
2040 fn on_maybe_tree_event(&mut self, event: Option<TreeEvent>) -> ProviderResult<()> {
2042 if let Some(event) = event {
2043 self.on_tree_event(event)?;
2044 }
2045
2046 Ok(())
2047 }
2048
2049 fn on_tree_event(&mut self, event: TreeEvent) -> ProviderResult<()> {
2053 match event {
2054 TreeEvent::TreeAction(action) => match action {
2055 TreeAction::MakeCanonical { sync_target_head } => {
2056 self.make_canonical(sync_target_head)?;
2057 }
2058 },
2059 TreeEvent::BackfillAction(action) => {
2060 self.emit_event(EngineApiEvent::BackfillAction(action));
2061 }
2062 TreeEvent::Download(action) => {
2063 self.emit_event(EngineApiEvent::Download(action));
2064 }
2065 }
2066
2067 Ok(())
2068 }
2069
2070 fn purge_timing_stats(&mut self, below_number: u64, commit_duration: Option<Duration>) {
2077 let threshold = self.config.slow_block_threshold();
2078 let check_slow = commit_duration.is_some() && threshold.is_some();
2079
2080 let keys_to_remove: Vec<B256> = self
2082 .execution_timing_stats
2083 .iter()
2084 .filter(|(_, stats)| stats.block_number <= below_number)
2085 .map(|(k, _)| *k)
2086 .collect();
2087
2088 for key in keys_to_remove {
2089 let stats = self.execution_timing_stats.remove(&key).expect("key just found");
2090 if check_slow {
2091 let commit_dur = commit_duration.expect("checked above");
2092 let total_duration =
2094 stats.execution_duration + stats.state_hash_duration + commit_dur;
2095
2096 if total_duration > threshold.expect("checked above") {
2097 self.emit_event(ConsensusEngineEvent::SlowBlock(SlowBlockInfo {
2098 stats,
2099 commit_duration: Some(commit_dur),
2100 total_duration,
2101 }));
2102 }
2103 }
2104 }
2105 }
2106
2107 fn revalidate_pending_backfill(&mut self) -> ProviderResult<()> {
2109 debug_assert!(self.backfill_sync_state.is_pending_revalidation());
2110
2111 let sync_target_state = self.state.forkchoice_state_tracker.sync_target_state();
2112 let backfill_target = if let Some(state) = sync_target_state {
2113 let configured_target = self.backfill_target_hash(state);
2114 let target_hash =
2115 if configured_target.is_zero() { state.head_block_hash } else { configured_target };
2116 let target_number = if let Some(block) = self.state.buffer.block(&target_hash) {
2117 Some(block.number())
2118 } else {
2119 self.sealed_header_by_hash(target_hash)?.map(|header| header.number())
2120 };
2121
2122 target_number.and_then(|target_number| {
2123 self.backfill_sync_target(
2124 self.state.tree_state.canonical_block_number(),
2125 target_number,
2126 None,
2127 )
2128 })
2129 } else {
2130 None
2131 };
2132
2133 if let Some(target) = backfill_target {
2134 self.dispatch_backfill_action(BackfillAction::Start(target.into()));
2135 return Ok(())
2136 }
2137
2138 self.backfill_sync_state = BackfillSyncState::Idle;
2139 debug!(target: "engine::tree", "dropping deferred backfill after re-evaluation");
2140
2141 if let Some(state) = sync_target_state &&
2144 state.head_block_hash != self.state.tree_state.canonical_block_hash()
2145 {
2146 let target = self.lowest_buffered_ancestor_or(state.head_block_hash);
2147 self.send_event(EngineApiEvent::Download(DownloadRequest::single_block(target)));
2148 }
2149
2150 Ok(())
2151 }
2152
2153 fn emit_event(&mut self, event: impl Into<EngineApiEvent<N>>) {
2155 let event = event.into();
2156
2157 if let EngineApiEvent::BackfillAction(action) = event {
2158 debug_assert_eq!(
2159 self.backfill_sync_state,
2160 BackfillSyncState::Idle,
2161 "backfill action should only be emitted when backfill is idle"
2162 );
2163
2164 let persistence_in_progress = self.persistence_state.in_progress();
2165 let state_trie_needs_catchup = self.persistence_state.last_state_trie_persisted_block !=
2166 self.persistence_state.last_persisted_block;
2167 if self.payload_builds.is_active() ||
2168 persistence_in_progress ||
2169 state_trie_needs_catchup
2170 {
2171 debug!(
2175 target: "engine::tree",
2176 last_persisted_block = self.persistence_state.last_persisted_block.number,
2177 last_state_trie_persisted_block = self
2178 .persistence_state
2179 .last_state_trie_persisted_block
2180 .number,
2181 "deferring backfill until persistence and payload jobs drain"
2182 );
2183 self.backfill_sync_state = BackfillSyncState::PendingRevalidation;
2184 return
2185 }
2186
2187 self.dispatch_backfill_action(action);
2188 return
2189 }
2190
2191 self.send_event(event);
2192 }
2193
2194 fn dispatch_backfill_action(&mut self, action: BackfillAction) {
2196 debug_assert!(
2197 self.backfill_sync_state.is_idle() ||
2198 self.backfill_sync_state.is_pending_revalidation(),
2199 "backfill action can only be dispatched while idle or pending revalidation"
2200 );
2201 self.backfill_sync_state = BackfillSyncState::Pending;
2202 self.metrics.engine.pipeline_runs.increment(1);
2203 if let Some(state) = self.state.forkchoice_state_tracker.latest_state() {
2206 self.forward_finalized(state.finalized_block_hash);
2207 }
2208 debug!(target: "engine::tree", "emitting backfill action event");
2209 self.send_event(EngineApiEvent::BackfillAction(action));
2210 }
2211
2212 fn forward_finalized(&self, finalized: B256) {
2214 if !finalized.is_zero() {
2215 self.send_event(EngineApiEvent::BackfillAction(BackfillAction::UpdateFinalized(
2216 finalized,
2217 )));
2218 }
2219 }
2220
2221 fn send_event(&self, event: EngineApiEvent<N>) {
2223 let _ = self.outgoing.send(event).inspect_err(
2224 |err| error!(target: "engine::tree", "Failed to send internal event: {err:?}"),
2225 );
2226 }
2227
2228 fn get_save_blocks_input(&self, target: PersistTarget) -> Option<SaveBlocksInput<N>> {
2235 debug_assert!(!self.persistence_state.in_progress());
2238
2239 let prev_partial_state_trie = self.persistence_state.last_state_trie_persisted_block.number;
2240 let prev_db_tip = self.persistence_state.last_persisted_block.number;
2241 let canonical_head_number = self.state.tree_state.canonical_block_number();
2242
2243 let (new_db_tip, new_partial_state_trie) = match target {
2244 PersistTarget::Head => (canonical_head_number, canonical_head_number),
2245 PersistTarget::Persisted => {
2246 debug_assert!(self.backfill_sync_state.is_pending_revalidation());
2249 debug_assert!(!self.payload_builds.is_active());
2250 (prev_db_tip, prev_db_tip)
2251 }
2252 PersistTarget::Threshold => {
2253 if (self.config.suppress_persistence_during_build() &&
2254 self.payload_builds.is_active()) ||
2255 !self.backfill_sync_state.is_idle()
2256 {
2257 return None
2258 }
2259
2260 let persistence_threshold =
2261 usize::try_from(self.config.persistence_threshold()).unwrap_or(usize::MAX);
2262 if self.canonical_in_memory_state.canonical_chain().count() <= persistence_threshold
2263 {
2264 return None
2265 }
2266
2267 let new_db_tip =
2268 canonical_head_number.saturating_sub(self.config.memory_block_buffer_target());
2269 if new_db_tip <= prev_db_tip {
2270 return None
2271 }
2272
2273 let new_partial_state_trie = new_db_tip
2274 .saturating_sub(self.config.num_state_masking_blocks())
2275 .max(prev_partial_state_trie);
2276 (new_db_tip, new_partial_state_trie)
2277 }
2278 };
2279
2280 debug_assert!(
2281 new_db_tip >= prev_db_tip,
2282 "disk reorg must be resolved before saving blocks"
2283 );
2284 debug_assert!(
2285 new_partial_state_trie >= prev_partial_state_trie,
2286 "disk reorg must be resolved before saving state/trie"
2287 );
2288
2289 if new_db_tip == prev_db_tip && new_partial_state_trie == prev_partial_state_trie {
2290 return None
2291 }
2292
2293 let mut blocks = Vec::new();
2294 let mut current_hash = self.state.tree_state.canonical_block_hash();
2295
2296 debug!(
2297 target: "engine::tree",
2298 ?current_hash,
2299 ?prev_partial_state_trie,
2300 ?prev_db_tip,
2301 ?canonical_head_number,
2302 ?new_partial_state_trie,
2303 ?new_db_tip,
2304 target = ?target,
2305 "Returning save input"
2306 );
2307 while let Some(block) = self.state.tree_state.blocks_by_hash.get(¤t_hash) {
2308 if block.recovered_block().number() <= prev_partial_state_trie {
2309 break;
2310 }
2311
2312 if block.recovered_block().number() <= new_db_tip {
2313 blocks.push(block.clone());
2314 }
2315
2316 current_hash = block.recovered_block().parent_hash();
2317 }
2318
2319 blocks.reverse();
2321
2322 Some(SaveBlocksInput::new(
2323 blocks,
2324 prev_db_tip,
2325 prev_partial_state_trie,
2326 new_db_tip,
2327 new_partial_state_trie,
2328 ))
2329 }
2330
2331 fn on_new_persisted_block(&mut self) -> ProviderResult<()> {
2339 let in_memory_persisted_block = self.persistence_state.last_state_trie_persisted_block;
2340
2341 if let Some(remove_above) = self.find_disk_reorg()? {
2344 self.remove_blocks(remove_above);
2345 return Ok(())
2346 }
2347
2348 let finalized = self.state.forkchoice_state_tracker.last_valid_finalized();
2349 self.canonical_in_memory_state.remove_persisted_blocks_until(
2354 self.persistence_state.last_persisted_block,
2355 in_memory_persisted_block.number,
2356 );
2357 self.remove_before(in_memory_persisted_block, finalized)?;
2358 self.state.tree_state.overlay_manager.precompute_execution_overlay(
2361 self.state.tree_state.canonical_block_hash(),
2362 in_memory_persisted_block.hash,
2363 );
2364 self.state.set_pending_sparse_trie_prune(self.should_prune_sparse_trie());
2365 Ok(())
2366 }
2367
2368 const fn should_prune_sparse_trie(&self) -> bool {
2370 self.config.use_state_root_task()
2371 }
2372
2373 #[instrument(level = "debug", target = "engine::tree", skip(self))]
2380 fn canonical_block_by_hash(&self, hash: B256) -> ProviderResult<ExecutedBlock<N>> {
2381 trace!(target: "engine::tree", ?hash, "Fetching executed block by hash");
2382 if let Some(block) = self.state.tree_state.executed_block_by_hash(hash) {
2384 return Ok(block.clone())
2385 }
2386
2387 let (block, senders) = self
2388 .provider
2389 .sealed_block_with_senders(hash.into(), TransactionVariant::WithHash)?
2390 .ok_or_else(|| ProviderError::HeaderNotFound(hash.into()))?
2391 .split_sealed();
2392 let mut execution_output = self
2393 .provider
2394 .get_state(block.header().number())?
2395 .ok_or_else(|| ProviderError::StateForNumberNotFound(block.header().number()))?;
2396 let bundle_state = execution_output.state();
2397 let hashed_state = if block.parent_hash().is_zero() {
2402 HashedPostState::from_bundle_state::<KeccakKeyHasher>(bundle_state.state())
2403 } else {
2404 self.provider
2405 .state_by_block_hash(block.parent_hash())?
2406 .hashed_post_state(bundle_state)?
2407 };
2408
2409 debug!(
2410 target: "engine::tree",
2411 number = ?block.number(),
2412 "computing block trie updates",
2413 );
2414 let db_provider = self.provider.database_provider_ro()?;
2415 let trie_updates = self
2416 .state
2417 .tree_state
2418 .overlay_manager
2419 .compute_block_trie_updates(&db_provider, block.number())?;
2420
2421 let sorted_hashed_state = Arc::new(hashed_state.into_sorted());
2422 let sorted_trie_updates = Arc::new(trie_updates);
2423 let trie_data = SortedTrieData::new(sorted_hashed_state, sorted_trie_updates);
2424
2425 let execution_output = Arc::new(BlockExecutionOutput {
2426 state: execution_output.bundle,
2427 result: BlockExecutionResult {
2428 receipts: execution_output.receipts.pop().unwrap_or_default(),
2429 requests: execution_output.requests.pop().unwrap_or_default(),
2430 gas_used: block.gas_used(),
2431 blob_gas_used: block.blob_gas_used().unwrap_or_default(),
2432 },
2433 });
2434
2435 Ok(ExecutedBlock::new(
2436 Arc::new(RecoveredBlock::new_sealed(block, senders)),
2437 execution_output,
2438 trie_data,
2439 ))
2440 }
2441
2442 fn has_block_by_hash(&self, hash: B256) -> ProviderResult<bool> {
2446 if self.state.tree_state.contains_hash(&hash) {
2447 Ok(true)
2448 } else {
2449 self.provider.is_known(hash)
2450 }
2451 }
2452
2453 fn sealed_header_by_hash(
2455 &self,
2456 hash: B256,
2457 ) -> ProviderResult<Option<SealedHeader<N::BlockHeader>>> {
2458 let header = self.state.tree_state.sealed_header_by_hash(&hash);
2460
2461 if header.is_some() {
2462 Ok(header)
2463 } else {
2464 self.provider.sealed_header_by_hash(hash)
2465 }
2466 }
2467
2468 fn lowest_buffered_ancestor_or(&self, hash: B256) -> B256 {
2475 self.state
2476 .buffer
2477 .lowest_ancestor(&hash)
2478 .map(|block| block.parent_hash())
2479 .unwrap_or_else(|| hash)
2480 }
2481
2482 fn should_download_access_lists(&self) -> bool {
2491 self.canonical_in_memory_state.get_canonical_head().block_access_list_hash().is_some()
2492 }
2493
2494 fn latest_valid_hash_for_invalid_payload(
2505 &mut self,
2506 parent_hash: B256,
2507 ) -> ProviderResult<Option<B256>> {
2508 if self.has_block_by_hash(parent_hash)? {
2510 return Ok(Some(parent_hash))
2511 }
2512
2513 let mut current_hash = parent_hash;
2516 let mut current_block = self.state.invalid_headers.get(¤t_hash);
2517 while let Some(block_with_parent) = current_block {
2518 current_hash = block_with_parent.parent;
2519 current_block = self.state.invalid_headers.get(¤t_hash);
2520
2521 if current_block.is_none() && self.has_block_by_hash(current_hash)? {
2524 return Ok(Some(current_hash))
2525 }
2526 }
2527 Ok(None)
2528 }
2529
2530 fn prepare_invalid_response(&mut self, parent_hash: B256) -> ProviderResult<PayloadStatus> {
2534 let valid_parent_hash = match self.sealed_header_by_hash(parent_hash)? {
2535 Some(parent) if !parent.difficulty().is_zero() => Some(B256::ZERO),
2539 Some(_) => Some(parent_hash),
2540 None => self.latest_valid_hash_for_invalid_payload(parent_hash)?,
2541 };
2542
2543 Ok(PayloadStatus::from_status(PayloadStatusEnum::Invalid {
2544 validation_error: PayloadValidationError::LinksToRejectedPayload.to_string(),
2545 })
2546 .with_latest_valid_hash(valid_parent_hash.unwrap_or_default()))
2547 }
2548
2549 fn is_sync_target_head(&self, block_hash: B256) -> bool {
2553 if let Some(target) = self.state.forkchoice_state_tracker.sync_target_state() {
2554 return target.head_block_hash == block_hash
2555 }
2556 false
2557 }
2558
2559 fn is_any_sync_target(&self, block_hash: B256) -> bool {
2563 if let Some(target) = self.state.forkchoice_state_tracker.sync_target_state() {
2564 return target.contains(block_hash)
2565 }
2566 false
2567 }
2568
2569 fn check_invalid_ancestor_with_head(
2575 &mut self,
2576 check: B256,
2577 head: &SealedBlock<N::Block>,
2578 ) -> ProviderResult<Option<PayloadStatus>> {
2579 let Some(header) = self.state.invalid_headers.get(&check) else { return Ok(None) };
2581
2582 Ok(Some(self.on_invalid_new_payload(head.clone(), header)?))
2583 }
2584
2585 fn on_invalid_new_payload(
2587 &mut self,
2588 head: SealedBlock<N::Block>,
2589 invalid: BlockWithParent,
2590 ) -> ProviderResult<PayloadStatus> {
2591 let status = self.prepare_invalid_response(invalid.parent)?;
2593
2594 self.state.invalid_headers.insert_with_invalid_ancestor(head.hash(), invalid);
2596 self.emit_event(ConsensusEngineEvent::InvalidBlock {
2597 block: Box::new(head),
2598 error: PayloadValidationError::LinksToRejectedPayload.to_string(),
2599 });
2600
2601 Ok(status)
2602 }
2603
2604 fn find_invalid_ancestor(&mut self, payload: &T::ExecutionData) -> Option<BlockWithParent> {
2618 let parent_hash = payload.parent_hash();
2619 let block_hash = payload.block_hash();
2620
2621 if let Some(entry) = self.state.invalid_headers.get(&block_hash) {
2623 return Some(entry);
2624 }
2625
2626 let mut lowest_buffered_ancestor = self.lowest_buffered_ancestor_or(block_hash);
2627 if lowest_buffered_ancestor == block_hash {
2628 lowest_buffered_ancestor = parent_hash;
2629 }
2630
2631 self.state.invalid_headers.get(&lowest_buffered_ancestor)
2633 }
2634
2635 fn handle_invalid_ancestor_payload(
2644 &mut self,
2645 payload: T::ExecutionData,
2646 invalid: BlockWithParent,
2647 ) -> Result<PayloadStatus, InsertBlockFatalError> {
2648 let parent_hash = payload.parent_hash();
2649 let num_hash = payload.num_hash();
2650
2651 let block = match self.payload_validator.convert_payload_to_block(payload) {
2657 Ok(block) => block,
2658 Err(error) => return Ok(self.on_new_payload_error(error, num_hash, parent_hash)?),
2659 };
2660
2661 Ok(self.on_invalid_new_payload(block, invalid)?)
2662 }
2663
2664 fn check_invalid_ancestor(&mut self, head: B256) -> ProviderResult<Option<PayloadStatus>> {
2667 let Some(header) = self.state.invalid_headers.get(&head) else { return Ok(None) };
2669
2670 match self.prepare_invalid_response(header.parent) {
2672 Ok(status) => Ok(Some(status)),
2673 Err(err) => {
2674 debug!(target: "engine::tree", %err, "Failed to prepare invalid response for ancestor check");
2675 Ok(Some(PayloadStatus::from_status(PayloadStatusEnum::Invalid {
2677 validation_error: PayloadValidationError::LinksToRejectedPayload.to_string(),
2678 })))
2679 }
2680 }
2681 }
2682
2683 fn validate_block(&self, block: &SealedBlock<N::Block>) -> Result<(), ConsensusError> {
2686 if let Err(e) = self.consensus.validate_header(block.sealed_header()) {
2687 error!(target: "engine::tree", block = ?block.block_with_parent(), %e, "Failed to validate header");
2688 return Err(e)
2689 }
2690
2691 if let Err(e) = self.consensus.validate_block_pre_execution(block) {
2692 error!(target: "engine::tree", block = ?block.block_with_parent(), %e, "Failed to validate block");
2693 return Err(e)
2694 }
2695
2696 Ok(())
2697 }
2698
2699 #[instrument(level = "debug", target = "engine::tree", skip(self))]
2701 fn try_connect_buffered_blocks(
2702 &mut self,
2703 parent: BlockNumHash,
2704 ) -> Result<(), InsertBlockFatalError> {
2705 let blocks = self.state.buffer.remove_block_with_children(&parent.hash);
2706
2707 if blocks.is_empty() {
2708 return Ok(())
2710 }
2711
2712 let now = Instant::now();
2713 let block_count = blocks.len();
2714 for child in blocks {
2715 let child_num_hash = child.num_hash();
2716 match self.insert_block(child) {
2717 Ok(res) => {
2718 debug!(target: "engine::tree", child =?child_num_hash, ?res, "connected buffered block");
2719 if self.is_any_sync_target(child_num_hash.hash) &&
2720 matches!(res, InsertPayloadOk::Inserted(BlockStatus::Valid))
2721 {
2722 debug!(target: "engine::tree", child =?child_num_hash, "connected sync target block");
2723 self.make_canonical(child_num_hash.hash)?;
2726 }
2727 }
2728 Err(err) => {
2729 if let InsertPayloadError::Block(err) = err {
2730 debug!(target: "engine::tree", ?err, "failed to connect buffered block to tree");
2731 if let Err(InsertBlockProcessingError::Fatal(fatal)) =
2732 self.on_insert_block_error(err)
2733 {
2734 warn!(target: "engine::tree", %fatal, "fatal error occurred while connecting buffered blocks");
2735 }
2736 }
2737 }
2738 }
2739 }
2740
2741 debug!(target: "engine::tree", elapsed = ?now.elapsed(), %block_count, "connected buffered blocks");
2742 Ok(())
2743 }
2744
2745 fn buffer_block(
2747 &mut self,
2748 block: SealedBlock<N::Block>,
2749 ) -> Result<(), InsertBlockError<N::Block>> {
2750 if let Err(err) = self.validate_block(&block) {
2751 return Err(InsertBlockError::consensus_error(err, block))
2752 }
2753 self.state.buffer.insert_block(block.into());
2754 Ok(())
2755 }
2756
2757 #[inline]
2762 const fn exceeds_backfill_run_threshold(&self, local_tip: u64, block: u64) -> bool {
2763 block > local_tip && block - local_tip > self.config.backfill_run_threshold()
2764 }
2765
2766 #[inline]
2769 const fn distance_from_local_tip(&self, local_tip: u64, block: u64) -> Option<u64> {
2770 if block > local_tip {
2771 Some(block - local_tip)
2772 } else {
2773 None
2774 }
2775 }
2776
2777 const fn backfill_target_hash(&self, state: ForkchoiceState) -> B256 {
2785 if self.engine_kind.is_opstack() {
2786 state.head_block_hash
2787 } else {
2788 state.finalized_block_hash
2789 }
2790 }
2791
2792 fn backfill_sync_target(
2799 &self,
2800 canonical_tip_num: u64,
2801 target_block_number: u64,
2802 downloaded_block: Option<BlockNumHash>,
2803 ) -> Option<B256> {
2804 let state = self.state.forkchoice_state_tracker.sync_target_state()?;
2805 let target_hash = self.backfill_target_hash(state);
2806
2807 let exceeds_backfill_threshold = match downloaded_block.as_ref() {
2809 Some(downloaded_block) if downloaded_block.hash == target_hash => {
2811 self.exceeds_backfill_run_threshold(canonical_tip_num, downloaded_block.number)
2812 }
2813 _ => match self.state.buffer.block(&target_hash) {
2814 Some(buffered_target) => {
2816 self.exceeds_backfill_run_threshold(canonical_tip_num, buffered_target.number())
2817 }
2818 None => self.exceeds_backfill_run_threshold(canonical_tip_num, target_block_number),
2820 },
2821 };
2822
2823 if !exceeds_backfill_threshold {
2824 return None
2825 }
2826
2827 match self.provider.header_by_hash_or_number(target_hash.into()) {
2829 Err(err) => {
2830 warn!(target: "engine::tree", %err, "Failed to get backfill target block header");
2831 None
2832 }
2833 Ok(None) if !target_hash.is_zero() => Some(target_hash),
2835 Ok(None) => {
2836 debug!(target: "engine::tree", hash=?state.head_block_hash, "Setting head hash as an optimistic backfill target.");
2849 Some(state.head_block_hash)
2850 }
2851 Ok(Some(_)) => None,
2853 }
2854 }
2855
2856 fn find_disk_reorg(&self) -> ProviderResult<Option<u64>> {
2859 let mut canonical = self.state.tree_state.current_canonical_head;
2860 let mut persisted = self.persistence_state.last_persisted_block;
2861
2862 let parent_num_hash = |num_hash: NumHash| -> ProviderResult<NumHash> {
2863 Ok(self
2864 .sealed_header_by_hash(num_hash.hash)?
2865 .ok_or(ProviderError::BlockHashNotFound(num_hash.hash))?
2866 .parent_num_hash())
2867 };
2868
2869 while canonical.number > persisted.number {
2872 canonical = parent_num_hash(canonical)?;
2873 }
2874
2875 if canonical == persisted {
2877 return Ok(None);
2878 }
2879
2880 while persisted.number > canonical.number {
2886 persisted = parent_num_hash(persisted)?;
2887 }
2888
2889 debug_assert_eq!(persisted.number, canonical.number);
2890
2891 while persisted.hash != canonical.hash {
2893 canonical = parent_num_hash(canonical)?;
2894 persisted = parent_num_hash(persisted)?;
2895 }
2896
2897 debug!(target: "engine::tree", remove_above=persisted.number, "on-disk reorg detected");
2898
2899 Ok(Some(persisted.number))
2900 }
2901
2902 fn on_canonical_chain_update(&mut self, chain_update: NewCanonicalChain<N>) {
2906 trace!(target: "engine::tree", new_blocks = %chain_update.new_block_count(), reorged_blocks = %chain_update.reorged_block_count(), "applying new chain update");
2907 let start = Instant::now();
2908
2909 self.state.tree_state.set_canonical_head(chain_update.tip().num_hash());
2911
2912 let tip = chain_update.tip().clone_sealed_header();
2913 let notification = chain_update.to_chain_notification();
2914
2915 if let NewCanonicalChain::Reorg { new, old } = &chain_update {
2917 let new_first = new.first().map(|first| first.recovered_block().num_hash());
2918 let old_first = old.first().map(|first| first.recovered_block().num_hash());
2919 trace!(target: "engine::tree", ?new_first, ?old_first, "Reorg detected, new and old first blocks");
2920
2921 self.state.set_pending_sparse_trie_prune(false);
2922 self.update_reorg_metrics(old.len(), old_first);
2923 self.reinsert_reorged_blocks(new.clone());
2924 self.reinsert_reorged_blocks(old.clone());
2925 }
2926
2927 self.canonical_in_memory_state.update_chain(chain_update);
2929 self.canonical_in_memory_state.set_canonical_head(tip.clone());
2930 self.payload_validator.on_canonical_head_changed(tip.hash(), &self.state);
2931
2932 self.metrics.tree.canonical_chain_height.set(tip.number() as f64);
2934
2935 self.canonical_in_memory_state.notify_canon_state(notification);
2937
2938 self.emit_event(ConsensusEngineEvent::CanonicalChainCommitted(
2940 Box::new(tip),
2941 start.elapsed(),
2942 ));
2943 }
2944
2945 fn update_reorg_metrics(&self, old_chain_length: usize, first_reorged_block: Option<NumHash>) {
2947 if let Some(first_reorged_block) = first_reorged_block.map(|block| block.number) {
2948 if let Some(finalized) = self.canonical_in_memory_state.get_finalized_num_hash() &&
2949 first_reorged_block <= finalized.number
2950 {
2951 self.metrics.tree.reorgs.finalized.increment(1);
2952 } else if let Some(safe) = self.canonical_in_memory_state.get_safe_num_hash() &&
2953 first_reorged_block <= safe.number
2954 {
2955 self.metrics.tree.reorgs.safe.increment(1);
2956 } else {
2957 self.metrics.tree.reorgs.head.increment(1);
2958 }
2959 } else {
2960 debug_unreachable!("Reorged chain doesn't have any blocks");
2961 }
2962 self.metrics.tree.latest_reorg_depth.set(old_chain_length as f64);
2963 }
2964
2965 fn reinsert_reorged_blocks(&mut self, new_chain: Vec<ExecutedBlock<N>>) {
2967 for block in new_chain {
2968 if self
2969 .state
2970 .tree_state
2971 .executed_block_by_hash(block.recovered_block().hash())
2972 .is_none()
2973 {
2974 trace!(target: "engine::tree", num=?block.recovered_block().number(), hash=?block.recovered_block().hash(), "Reinserting block into tree state");
2975 self.state.tree_state.insert_executed(block);
2976 }
2977 }
2978 self.metrics.engine.executed_blocks.set(self.state.tree_state.block_count() as f64);
2979 }
2980
2981 fn on_disconnected_downloaded_block(
2986 &self,
2987 downloaded_block: BlockNumHash,
2988 missing_parent: BlockNumHash,
2989 head: BlockNumHash,
2990 ) -> Option<TreeEvent> {
2991 if let Some(target) =
2993 self.backfill_sync_target(head.number, missing_parent.number, Some(downloaded_block))
2994 {
2995 trace!(target: "engine::tree", %target, "triggering backfill on downloaded block");
2996 return Some(TreeEvent::BackfillAction(BackfillAction::Start(target.into())));
2997 }
2998
2999 let request = if let Some(distance) =
3009 self.distance_from_local_tip(head.number, missing_parent.number)
3010 {
3011 trace!(target: "engine::tree", %distance, missing=?missing_parent, "downloading missing parent block range");
3012 DownloadRequest::block_range(missing_parent.hash, distance)
3013 } else {
3014 trace!(target: "engine::tree", missing=?missing_parent, "downloading missing parent block");
3015 DownloadRequest::single_block(missing_parent.hash)
3018 };
3019
3020 Some(TreeEvent::Download(request.with_access_lists(self.should_download_access_lists())))
3021 }
3022
3023 fn on_valid_downloaded_block(
3030 &mut self,
3031 block_num_hash: BlockNumHash,
3032 ) -> Result<Option<TreeEvent>, InsertBlockFatalError> {
3033 if let Some(sync_target) = self.state.forkchoice_state_tracker.sync_target_state() &&
3036 sync_target.contains(block_num_hash.hash)
3037 {
3038 debug!(target: "engine::tree", ?sync_target, "appended downloaded sync target block");
3039
3040 if sync_target.head_block_hash == block_num_hash.hash {
3041 return Ok(Some(TreeEvent::TreeAction(TreeAction::MakeCanonical {
3043 sync_target_head: block_num_hash.hash,
3044 })))
3045 }
3046
3047 self.make_canonical(block_num_hash.hash)?;
3051 self.try_connect_buffered_blocks(block_num_hash)?;
3052
3053 if self.state.tree_state.canonical_block_hash() != sync_target.head_block_hash {
3056 let target = self.lowest_buffered_ancestor_or(sync_target.head_block_hash);
3057 trace!(target: "engine::tree", %target, "sync target head not yet reached, downloading head block");
3058 return Ok(Some(TreeEvent::Download(
3059 DownloadRequest::single_block(target)
3060 .with_access_lists(self.should_download_access_lists()),
3061 )))
3062 }
3063
3064 return Ok(None)
3065 }
3066 trace!(target: "engine::tree", "appended downloaded block");
3067 self.try_connect_buffered_blocks(block_num_hash)?;
3068 Ok(None)
3069 }
3070
3071 #[instrument(level = "debug", target = "engine::tree", skip_all, fields(block_hash = %block.hash(), block_num = %block.number()))]
3077 fn on_downloaded_block(
3078 &mut self,
3079 block: SealedBlockWithAccessList<N::Block>,
3080 ) -> Result<Option<TreeEvent>, InsertBlockFatalError> {
3081 let block_num_hash = block.num_hash();
3082 let lowest_buffered_ancestor = self.lowest_buffered_ancestor_or(block_num_hash.hash);
3083 if self.check_invalid_ancestor_with_head(lowest_buffered_ancestor, &block)?.is_some() {
3084 return Ok(None)
3085 }
3086
3087 if !self.backfill_sync_state.is_idle() {
3088 return Ok(None)
3089 }
3090
3091 match self.insert_block(block) {
3093 Ok(InsertPayloadOk::Inserted(BlockStatus::Valid)) => {
3094 return self.on_valid_downloaded_block(block_num_hash);
3095 }
3096 Ok(InsertPayloadOk::Inserted(BlockStatus::Disconnected { head, missing_ancestor })) => {
3097 return Ok(self.on_disconnected_downloaded_block(
3100 block_num_hash,
3101 missing_ancestor,
3102 head,
3103 ))
3104 }
3105 Ok(InsertPayloadOk::AlreadySeen(_)) => {
3106 trace!(target: "engine::tree", "downloaded block already executed");
3107 }
3108 Err(err) => {
3109 if let InsertPayloadError::Block(err) = err {
3110 debug!(target: "engine::tree", err=%err.kind(), "failed to insert downloaded block");
3111 if let Err(InsertBlockProcessingError::Fatal(fatal)) =
3112 self.on_insert_block_error(err)
3113 {
3114 warn!(target: "engine::tree", %fatal, "fatal error occurred while inserting downloaded block");
3115 }
3116 }
3117 }
3118 }
3119 Ok(None)
3120 }
3121
3122 fn insert_payload(
3131 &mut self,
3132 payload: T::ExecutionData,
3133 ) -> Result<InsertPayloadOk, InsertPayloadError<N::Block>> {
3134 self.insert_block_or_payload(
3135 payload.block_with_parent(),
3136 payload,
3137 |validator, payload, ctx| validator.validate_payload(payload, ctx),
3138 |this, payload| Ok(this.payload_validator.convert_payload_to_block(payload)?.into()),
3139 )
3140 }
3141
3142 fn insert_block(
3143 &mut self,
3144 block: SealedBlockWithAccessList<N::Block>,
3145 ) -> Result<InsertPayloadOk, InsertPayloadError<N::Block>> {
3146 self.insert_block_or_payload(
3147 block.block_with_parent(),
3148 block,
3149 |validator, block, ctx| validator.validate_block(block, ctx),
3150 |_, block| Ok(block),
3151 )
3152 }
3153
3154 #[instrument(level = "debug", target = "engine::tree", skip_all, fields(?block_id))]
3171 fn insert_block_or_payload<Input, Err>(
3172 &mut self,
3173 block_id: BlockWithParent,
3174 input: Input,
3175 execute: impl FnOnce(&mut V, Input, TreeCtx<'_, N>) -> Result<ValidationOutput<N>, Err>,
3176 convert_to_block: impl FnOnce(
3177 &mut Self,
3178 Input,
3179 ) -> Result<SealedBlockWithAccessList<N::Block>, Err>,
3180 ) -> Result<InsertPayloadOk, Err>
3181 where
3182 Err: From<InsertBlockError<N::Block>>,
3183 {
3184 let block_insert_start = Instant::now();
3185 let block_num_hash = block_id.block;
3186 debug!(target: "engine::tree", block=?block_num_hash, parent = ?block_id.parent, "Inserting new block into tree");
3187
3188 if self.state.tree_state.contains_hash(&block_num_hash.hash) {
3190 convert_to_block(self, input)?;
3191 return Ok(InsertPayloadOk::AlreadySeen(BlockStatus::Valid));
3192 }
3193
3194 if block_num_hash.number <= self.persistence_state.last_persisted_block.number {
3197 match self.provider.sealed_header_by_hash(block_num_hash.hash) {
3198 Err(err) => {
3199 let block = convert_to_block(self, input)?;
3200 return Err(InsertBlockError::new(block.split().0, err.into()).into());
3201 }
3202 Ok(Some(_)) => {
3203 convert_to_block(self, input)?;
3204 return Ok(InsertPayloadOk::AlreadySeen(BlockStatus::Valid));
3205 }
3206 Ok(None) => {}
3207 }
3208 }
3209
3210 if !self.state.tree_state.contains_hash(&block_id.parent) {
3212 let parent_exists = match self.provider.header(block_id.parent) {
3213 Ok(header) => header.is_some(),
3214 Err(err) => {
3215 let block = convert_to_block(self, input)?;
3216 return Err(InsertBlockError::new(block.split().0, err.into()).into());
3217 }
3218 };
3219
3220 if !parent_exists {
3221 let block = convert_to_block(self, input)?;
3222 let missing_ancestor = self
3225 .state
3226 .buffer
3227 .lowest_ancestor(&block.parent_hash())
3228 .map(|block| block.parent_num_hash())
3229 .unwrap_or_else(|| block.parent_num_hash());
3230
3231 self.state.buffer.insert_block(block);
3232
3233 return Ok(InsertPayloadOk::Inserted(BlockStatus::Disconnected {
3234 head: self.state.tree_state.current_canonical_head,
3235 missing_ancestor,
3236 }))
3237 }
3238 }
3239
3240 let is_fork = block_id.block.number <= self.state.tree_state.current_canonical_head.number;
3245
3246 let ctx = TreeCtx::new(&mut self.state, &self.canonical_in_memory_state);
3247
3248 let start = Instant::now();
3249
3250 let ValidationOutput { executed_block: executed, execution_timing_stats: timing_stats } =
3251 execute(&mut self.payload_validator, input, ctx)?;
3252
3253 if let Some(raw_bal) = executed.bal().map(|bal| bal.as_raw_bal().clone()) {
3254 let num_hash = executed.recovered_block().num_hash();
3255 if let Err(err) = self.provider.bal_store().insert(num_hash, raw_bal) {
3256 warn!(
3257 target: "engine::tree",
3258 ?num_hash,
3259 %err,
3260 "Failed to store validated block access list"
3261 );
3262 }
3263 }
3264
3265 if let Some(stats) = timing_stats {
3268 if let Some(threshold) = self.config.slow_block_threshold() {
3269 let total_duration = stats.execution_duration + stats.state_hash_duration;
3270 if total_duration > threshold {
3271 self.emit_event(ConsensusEngineEvent::SlowBlock(SlowBlockInfo {
3272 stats: stats.clone(),
3273 commit_duration: None,
3274 total_duration,
3275 }));
3276 }
3277 }
3278 self.execution_timing_stats.insert(executed.recovered_block().hash(), stats);
3279 }
3280
3281 let is_pending = self.state.tree_state.canonical_block_hash() ==
3282 executed.recovered_block().parent_hash();
3283 self.state.tree_state.insert_executed(executed.clone());
3284
3285 if is_pending {
3286 debug!(target: "engine::tree", pending=?block_num_hash, "updating pending block");
3287 self.canonical_in_memory_state.set_pending_block(executed.clone());
3288 }
3289
3290 self.metrics.engine.executed_blocks.set(self.state.tree_state.block_count() as f64);
3291
3292 let elapsed = start.elapsed();
3294 let engine_event = if is_fork {
3295 ConsensusEngineEvent::ForkBlockAdded(executed, elapsed)
3296 } else {
3297 ConsensusEngineEvent::CanonicalBlockAdded(executed, elapsed)
3298 };
3299 self.emit_event(EngineApiEvent::BeaconConsensus(engine_event));
3300
3301 self.metrics
3302 .engine
3303 .block_insert_total_duration
3304 .record(block_insert_start.elapsed().as_secs_f64());
3305 debug!(target: "engine::tree", block=?block_num_hash, "Finished inserting block");
3306 Ok(InsertPayloadOk::Inserted(BlockStatus::Valid))
3307 }
3308
3309 fn on_insert_block_error(
3315 &mut self,
3316 error: InsertBlockError<N::Block>,
3317 ) -> Result<PayloadStatus, InsertBlockProcessingError> {
3318 let (block, error) = error.split();
3319
3320 let validation_err = error.ensure_validation_error()?;
3321
3322 warn!(
3326 target: "engine::tree",
3327 invalid_hash=%block.hash(),
3328 invalid_number=block.number(),
3329 %validation_err,
3330 "Invalid block error on new payload",
3331 );
3332 let latest_valid_hash =
3335 if matches!(&validation_err, InsertBlockValidationError::BlockAccessListDecode(_)) {
3336 None
3337 } else {
3338 self.latest_valid_hash_for_invalid_payload(block.parent_hash())
3339 .map_err(InsertBlockFatalError::from)?
3340 };
3341
3342 let is_transient = match &validation_err {
3344 InsertBlockValidationError::Consensus(err) => self.consensus.is_transient_error(err),
3345 _ => false,
3346 };
3347 if is_transient {
3348 warn!(
3349 target: "engine::tree",
3350 invalid_hash=%block.hash(),
3351 invalid_number=block.number(),
3352 %validation_err,
3353 "Skipping invalid header cache insert for transient validation error",
3354 );
3355 } else {
3356 self.state.invalid_headers.insert(block.block_with_parent());
3357 }
3358 self.emit_event(EngineApiEvent::BeaconConsensus(ConsensusEngineEvent::InvalidBlock {
3359 block: Box::new(block),
3360 error: validation_err.to_string(),
3361 }));
3362
3363 Ok(PayloadStatus::new(
3364 PayloadStatusEnum::Invalid { validation_error: validation_err.to_string() },
3365 latest_valid_hash,
3366 ))
3367 }
3368
3369 fn on_new_payload_error(
3371 &mut self,
3372 error: NewPayloadError,
3373 payload_num_hash: NumHash,
3374 parent_hash: B256,
3375 ) -> ProviderResult<PayloadStatus> {
3376 error!(target: "engine::tree", payload=?payload_num_hash, %error, "Invalid payload");
3377 let latest_valid_hash =
3380 if error.is_block_hash_mismatch() || error.is_invalid_versioned_hashes() {
3381 None
3385 } else {
3386 self.latest_valid_hash_for_invalid_payload(parent_hash)?
3387 };
3388
3389 let status = PayloadStatusEnum::from(error);
3390 Ok(PayloadStatus::new(status, latest_valid_hash))
3391 }
3392
3393 pub fn find_canonical_header(
3399 &self,
3400 hash: B256,
3401 ) -> Result<Option<SealedHeader<N::BlockHeader>>, ProviderError> {
3402 if let Some(header) = self.canonical_in_memory_state.header_by_hash(hash) {
3403 return Ok(Some(header))
3404 }
3405
3406 let Some(header) = self.provider.sealed_header_by_hash(hash)? else { return Ok(None) };
3407 let number = header.number();
3410 if number > self.canonical_in_memory_state.get_canonical_block_number() {
3411 return Ok(None)
3412 }
3413 let canonical_hash =
3414 if let Some(hash) = self.canonical_in_memory_state.hash_by_number(number) {
3415 Some(hash)
3416 } else {
3417 self.provider.block_hash(number)?
3418 };
3419
3420 Ok((canonical_hash == Some(hash)).then_some(header))
3421 }
3422
3423 fn is_consistent_forkchoice_state(
3441 &self,
3442 state: ForkchoiceState,
3443 chain_update: Option<&NewCanonicalChain<N>>,
3444 ) -> ProviderResult<bool> {
3445 let canonical_head_number = match chain_update {
3446 Some(chain_update) => {
3447 let new = chain_update.new_blocks();
3448 new.first().expect("non empty chain").block_number() - 1
3450 }
3451 None => {
3452 let Some(head) = self.find_canonical_header(state.head_block_hash)? else {
3453 return Ok(false)
3454 };
3455 head.number()
3456 }
3457 };
3458
3459 for hash in [state.finalized_block_hash, state.safe_block_hash] {
3460 if hash.is_zero() || chain_update.is_some_and(|update| update.contains(hash)) {
3461 continue
3462 }
3463 let Some(header) = self.find_canonical_header(hash)? else { return Ok(false) };
3464 if header.number() > canonical_head_number {
3465 return Ok(false)
3466 }
3467 }
3468 Ok(true)
3469 }
3470
3471 fn update_finalized_block(
3473 &self,
3474 finalized_block_hash: B256,
3475 ) -> Result<(), OnForkChoiceUpdated> {
3476 if finalized_block_hash.is_zero() {
3477 return Ok(())
3478 }
3479
3480 match self.find_canonical_header(finalized_block_hash) {
3481 Ok(None) => {
3482 debug!(target: "engine::tree", "Finalized block not found in canonical chain");
3483 return Err(OnForkChoiceUpdated::invalid_state())
3485 }
3486 Ok(Some(finalized)) => {
3487 if Some(finalized.num_hash()) !=
3488 self.canonical_in_memory_state.get_finalized_num_hash()
3489 {
3490 let _ = self.persistence.save_finalized_block_number(finalized.number());
3493 self.canonical_in_memory_state.set_finalized(finalized.clone());
3494 self.metrics.tree.finalized_block_height.set(finalized.number() as f64);
3496 }
3497 }
3498 Err(err) => {
3499 error!(target: "engine::tree", %err, "Failed to fetch finalized block header");
3500 }
3501 }
3502
3503 Ok(())
3504 }
3505
3506 fn update_safe_block(&self, safe_block_hash: B256) -> Result<(), OnForkChoiceUpdated> {
3508 if safe_block_hash.is_zero() {
3509 return Ok(())
3510 }
3511
3512 match self.find_canonical_header(safe_block_hash) {
3513 Ok(None) => {
3514 debug!(target: "engine::tree", "Safe block not found in canonical chain");
3515 return Err(OnForkChoiceUpdated::invalid_state())
3517 }
3518 Ok(Some(safe)) => {
3519 if Some(safe.num_hash()) != self.canonical_in_memory_state.get_safe_num_hash() {
3520 let _ = self.persistence.save_safe_block_number(safe.number());
3523 self.canonical_in_memory_state.set_safe(safe.clone());
3524 self.metrics.tree.safe_block_height.set(safe.number() as f64);
3526 }
3527 }
3528 Err(err) => {
3529 error!(target: "engine::tree", %err, "Failed to fetch safe block header");
3530 }
3531 }
3532
3533 Ok(())
3534 }
3535
3536 fn ensure_consistent_forkchoice_state(
3545 &self,
3546 state: ForkchoiceState,
3547 ) -> Result<(), OnForkChoiceUpdated> {
3548 self.update_finalized_block(state.finalized_block_hash)?;
3554
3555 self.update_safe_block(state.safe_block_hash)
3561 }
3562
3563 fn process_payload_attributes(
3578 &mut self,
3579 attributes: T::PayloadAttributes,
3580 head: &N::BlockHeader,
3581 state: ForkchoiceState,
3582 ) -> OnForkChoiceUpdated {
3583 if let Err(err) =
3584 self.payload_validator.validate_payload_attributes_against_header(&attributes, head)
3585 {
3586 warn!(target: "engine::tree", %err, ?head, "Invalid payload attributes");
3587 return OnForkChoiceUpdated::invalid_payload_attributes()
3588 }
3589
3590 let payload_build = self.payload_builds.acquire();
3598
3599 let resources = self
3600 .payload_validator
3601 .payload_builder_resources(
3602 state.head_block_hash,
3603 head,
3604 attributes.timestamp(),
3605 &mut self.state,
3606 )
3607 .with_lease(PayloadBuilderLease::new(payload_build));
3608
3609 let pending_payload_id = self.payload_builder.send_new_payload(BuildNewPayload {
3612 parent_hash: state.head_block_hash,
3613 attributes,
3614 resources,
3615 });
3616
3617 OnForkChoiceUpdated::updated_with_pending_payload_id(
3629 PayloadStatus::new(PayloadStatusEnum::Valid, Some(state.head_block_hash)),
3630 pending_payload_id,
3631 )
3632 }
3633
3634 pub(crate) fn remove_before(
3641 &mut self,
3642 upper_bound: BlockNumHash,
3643 finalized_hash: Option<B256>,
3644 ) -> ProviderResult<()> {
3645 let num = if let Some(hash) = finalized_hash {
3648 self.provider.block_number(hash)?.map(|number| BlockNumHash { number, hash })
3649 } else {
3650 None
3651 };
3652
3653 self.state.tree_state.remove_until(
3654 upper_bound,
3655 self.persistence_state.last_persisted_block.hash,
3656 num,
3657 );
3658 self.metrics.engine.executed_blocks.set(self.state.tree_state.block_count() as f64);
3659 Ok(())
3660 }
3661}
3662
3663#[derive(Debug)]
3665enum LoopEvent<T, N>
3666where
3667 N: NodePrimitives,
3668 T: PayloadTypes,
3669{
3670 EngineMessage(FromEngine<EngineApiRequest<T, N>, N::Block>),
3672 PersistenceComplete {
3674 result: PersistenceResult,
3676 start_time: Instant,
3678 },
3679 PayloadBuildFinished,
3681 Disconnected,
3683}
3684
3685#[derive(Clone, Debug)]
3687struct PayloadBuildTracker {
3688 active: Arc<AtomicUsize>,
3689 finished_tx: Sender<()>,
3690}
3691
3692impl PayloadBuildTracker {
3693 fn new() -> (Self, Receiver<()>) {
3695 let (finished_tx, finished_rx) = crossbeam_channel::bounded(1);
3696 (Self { active: Arc::new(AtomicUsize::new(0)), finished_tx }, finished_rx)
3697 }
3698
3699 fn acquire(&self) -> PayloadBuildLease {
3701 self.active.fetch_add(1, Ordering::AcqRel);
3702 PayloadBuildLease {
3703 active: Arc::clone(&self.active),
3704 finished_tx: self.finished_tx.clone(),
3705 }
3706 }
3707
3708 fn is_active(&self) -> bool {
3710 self.active.load(Ordering::Acquire) != 0
3711 }
3712}
3713
3714#[derive(Debug)]
3716struct PayloadBuildLease {
3717 active: Arc<AtomicUsize>,
3718 finished_tx: Sender<()>,
3719}
3720
3721impl Drop for PayloadBuildLease {
3722 fn drop(&mut self) {
3723 let previous = self.active.fetch_sub(1, Ordering::AcqRel);
3724 debug_assert!(previous > 0, "payload build lease count underflow");
3725
3726 if previous == 1 {
3727 let _ = self.finished_tx.try_send(());
3730 }
3731 }
3732}
3733
3734#[derive(Clone, Copy, Debug, Eq, PartialEq)]
3740pub enum BlockStatus {
3741 Valid,
3747 Disconnected {
3749 head: BlockNumHash,
3751 missing_ancestor: BlockNumHash,
3753 },
3754}
3755
3756#[derive(Clone, Copy, Debug, Eq, PartialEq)]
3761pub enum InsertPayloadOk {
3762 AlreadySeen(BlockStatus),
3764 Inserted(BlockStatus),
3766}
3767
3768#[derive(Debug, Clone, Copy)]
3770enum PersistTarget {
3771 Threshold,
3773 Head,
3775 Persisted,
3777}
3778
3779#[derive(Debug, Clone, Copy, Default)]
3781pub struct CacheWaitDurations {
3782 pub execution_cache: Duration,
3784 pub sparse_trie: Duration,
3786}
3787
3788pub trait WaitForCaches {
3793 fn wait_for_caches(&self) -> CacheWaitDurations;
3797}