1use crate::{
2 backfill::{BackfillAction, BackfillSyncState},
3 chain::FromOrchestrator,
4 engine::{DownloadRequest, EngineApiEvent, EngineApiKind, EngineApiRequest, FromEngine},
5 persistence::PersistenceHandle,
6 tree::{error::InsertPayloadError, payload_validator::TreeCtx},
7};
8use alloy_consensus::BlockHeader;
9use alloy_eips::{eip1898::BlockWithParent, merge::EPOCH_SLOTS, BlockNumHash, NumHash};
10use alloy_primitives::{map::B256Map, B256};
11use alloy_rpc_types_engine::{
12 ForkchoiceState, PayloadStatus, PayloadStatusEnum, PayloadValidationError,
13};
14use error::{InsertBlockError, InsertBlockFatalError, InsertBlockValidationError};
15use reth_chain_state::{
16 CanonicalInMemoryState, ExecutedBlock, ExecutionTimingStats, MemoryOverlayStateProvider,
17 NewCanonicalChain, StateTrieOverlayManager,
18};
19use reth_consensus::{Consensus, FullConsensus};
20use reth_engine_primitives::{
21 BeaconEngineMessage, BeaconOnNewPayloadError, ConsensusEngineEvent, ExecutionPayload,
22 ForkchoiceStateTracker, NewPayloadTimings, OnForkChoiceUpdated, SlowBlockInfo,
23};
24use reth_errors::{ConsensusError, ProviderResult};
25use reth_evm::ConfigureEvm;
26use reth_payload_builder::{BuildNewPayload, PayloadBuilderHandle};
27use reth_payload_primitives::{BuiltPayload, NewPayloadError, PayloadAttributes, PayloadTypes};
28use reth_primitives_traits::{
29 FastInstant as Instant, NodePrimitives, RecoveredBlock, SealedBlock, SealedHeader,
30};
31use reth_provider::{
32 BalProvider, BlockExecutionOutput, BlockExecutionResult, BlockReader, ChangeSetReader,
33 DatabaseProviderFactory, HashedPostStateProvider, ProviderError, StageCheckpointReader,
34 StateProviderBox, StateProviderFactory, StateReader, StorageChangeSetReader,
35 StorageSettingsCache, TransactionVariant,
36};
37use reth_revm::database::StateProviderDatabase;
38use reth_stages_api::ControlFlow;
39use reth_tasks::{spawn_os_thread, utils::increase_thread_priority};
40use reth_trie::ComputedTrieData;
41use reth_trie_db::ChangesetCache;
42use revm::interpreter::debug_unreachable;
43use state::TreeState;
44use std::{fmt::Debug, ops, sync::Arc, time::Duration};
45
46use crossbeam_channel::{Receiver, Sender};
47use tokio::sync::{
48 mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
49 oneshot,
50};
51use tracing::*;
52
53mod block_buffer;
54pub mod error;
55pub mod instrumented_state;
56mod invalid_headers;
57mod metrics;
58pub mod payload_processor;
59pub mod payload_validator;
60mod persistence_state;
61pub mod precompile_cache;
62pub mod state_root_strategy;
63#[cfg(test)]
64mod tests;
65mod trie_updates;
66pub mod types;
67
68use crate::{persistence::PersistenceResult, tree::error::AdvancePersistenceError};
69pub use block_buffer::BlockBuffer;
70pub use invalid_headers::InvalidHeaderCache;
71pub use metrics::EngineApiMetrics;
72pub use payload_processor::*;
73pub use payload_validator::{BasicEngineValidator, EngineValidator};
74pub use persistence_state::PersistenceState;
75pub use reth_engine_primitives::TreeConfig;
76pub use reth_execution_cache::{
77 CachedStateCacheMetrics, CachedStateMetrics, CachedStateMetricsSource, CachedStateProvider,
78 ExecutionCache, PayloadExecutionCache, SavedCache,
79};
80pub use types::{ExecutionEnv, ValidationOutcome, ValidationOutput};
81
82pub mod state;
83
84pub(crate) const MIN_BLOCKS_FOR_PIPELINE_RUN: u64 = EPOCH_SLOTS;
94
95const CHANGESET_CACHE_RETENTION_BLOCKS: u64 = 64;
100
101#[derive(Clone, Debug)]
103pub struct StateProviderBuilder<N: NodePrimitives, P> {
104 provider_factory: P,
106 historical: B256,
108 overlay: Option<Vec<ExecutedBlock<N>>>,
110}
111
112impl<N: NodePrimitives, P> StateProviderBuilder<N, P> {
113 pub const fn new(
116 provider_factory: P,
117 historical: B256,
118 overlay: Option<Vec<ExecutedBlock<N>>>,
119 ) -> Self {
120 Self { provider_factory, historical, overlay }
121 }
122}
123
124impl<N: NodePrimitives, P> StateProviderBuilder<N, P>
125where
126 P: BlockReader + StateProviderFactory + StateReader + Clone,
127{
128 pub fn build(&self) -> ProviderResult<StateProviderBox> {
130 let mut provider = self.provider_factory.state_by_block_hash(self.historical)?;
131 if let Some(overlay) = self.overlay.clone() {
132 provider = Box::new(MemoryOverlayStateProvider::new(provider, overlay))
133 }
134 Ok(provider)
135 }
136}
137
138#[derive(Debug)]
142pub struct EngineApiTreeState<N: NodePrimitives> {
143 tree_state: TreeState<N>,
145 pending_sparse_trie_prune: bool,
147 forkchoice_state_tracker: ForkchoiceStateTracker,
149 buffer: BlockBuffer<N::Block>,
151 invalid_headers: InvalidHeaderCache,
154}
155
156impl<N: NodePrimitives> EngineApiTreeState<N> {
157 fn new(
158 block_buffer_limit: u32,
159 max_invalid_header_cache_length: u32,
160 invalid_header_hit_eviction_threshold: u8,
161 canonical_block: BlockNumHash,
162 engine_kind: EngineApiKind,
163 state_trie_overlays: StateTrieOverlayManager<N>,
164 ) -> Self {
165 Self {
166 invalid_headers: InvalidHeaderCache::new(
167 max_invalid_header_cache_length,
168 invalid_header_hit_eviction_threshold,
169 ),
170 buffer: BlockBuffer::new(block_buffer_limit),
171 tree_state: TreeState::new(canonical_block, engine_kind, state_trie_overlays),
172 pending_sparse_trie_prune: false,
173 forkchoice_state_tracker: ForkchoiceStateTracker::default(),
174 }
175 }
176
177 pub const fn tree_state(&self) -> &TreeState<N> {
179 &self.tree_state
180 }
181
182 pub const fn pending_sparse_trie_prune(&self) -> bool {
184 self.pending_sparse_trie_prune
185 }
186
187 pub const fn set_pending_sparse_trie_prune(&mut self, pending: bool) {
189 self.pending_sparse_trie_prune = pending;
190 }
191
192 pub fn take_sparse_trie_prune_blocks(
199 &mut self,
200 parent_hash: B256,
201 ) -> Option<Vec<ExecutedBlock<N>>> {
202 if !self.pending_sparse_trie_prune {
203 return None
204 }
205
206 self.pending_sparse_trie_prune = false;
207 Some(
208 self.tree_state
209 .blocks_by_hash(parent_hash)
210 .map(|(_, blocks)| blocks)
211 .unwrap_or_default(),
212 )
213 }
214
215 pub fn has_invalid_header(&mut self, hash: &B256) -> bool {
217 self.invalid_headers.get(hash).is_some()
218 }
219}
220
221#[derive(Debug)]
223pub struct TreeOutcome<T> {
224 pub outcome: T,
226 pub event: Option<TreeEvent>,
228 pub already_seen: bool,
231}
232
233impl<T> TreeOutcome<T> {
234 pub const fn new(outcome: T) -> Self {
236 Self { outcome, event: None, already_seen: false }
237 }
238
239 pub fn with_event(mut self, event: TreeEvent) -> Self {
241 self.event = Some(event);
242 self
243 }
244
245 pub const fn with_already_seen(mut self, value: bool) -> Self {
247 self.already_seen = value;
248 self
249 }
250}
251
252#[derive(Debug)]
254pub struct TryInsertPayloadResult {
255 pub status: PayloadStatus,
259 pub already_seen: bool,
261}
262
263impl TryInsertPayloadResult {
264 #[inline]
266 pub fn into_outcome(self) -> TreeOutcome<PayloadStatus> {
267 TreeOutcome::new(self.status).with_already_seen(self.already_seen)
268 }
269}
270
271#[derive(Debug)]
273pub enum TreeEvent {
274 TreeAction(TreeAction),
276 BackfillAction(BackfillAction),
278 Download(DownloadRequest),
280}
281
282impl TreeEvent {
283 const fn is_backfill_action(&self) -> bool {
285 matches!(self, Self::BackfillAction(_))
286 }
287}
288
289#[derive(Debug)]
291pub enum TreeAction {
292 MakeCanonical {
294 sync_target_head: B256,
296 },
297}
298
299pub struct EngineApiTreeHandler<N, P, T, V, C>
304where
305 N: NodePrimitives,
306 T: PayloadTypes,
307 C: ConfigureEvm<Primitives = N> + 'static,
308{
309 provider: P,
310 consensus: Arc<dyn FullConsensus<N>>,
311 payload_validator: V,
312 state: EngineApiTreeState<N>,
314 incoming_tx: Sender<FromEngine<EngineApiRequest<T, N>, N::Block>>,
323 incoming: Receiver<FromEngine<EngineApiRequest<T, N>, N::Block>>,
325 outgoing: UnboundedSender<EngineApiEvent<N>>,
327 persistence: PersistenceHandle<N>,
329 persistence_state: PersistenceState,
331 backfill_sync_state: BackfillSyncState,
333 canonical_in_memory_state: CanonicalInMemoryState<N>,
336 payload_builder: PayloadBuilderHandle<T>,
339 config: TreeConfig,
341 metrics: EngineApiMetrics,
343 engine_kind: EngineApiKind,
345 evm_config: C,
347 changeset_cache: ChangesetCache,
349 execution_timing_stats: B256Map<Box<ExecutionTimingStats>>,
353 building_payload: bool,
356 runtime: reth_tasks::Runtime,
358}
359
360impl<N, P: Debug, T: PayloadTypes + Debug, V: Debug, C> std::fmt::Debug
361 for EngineApiTreeHandler<N, P, T, V, C>
362where
363 N: NodePrimitives,
364 C: Debug + ConfigureEvm<Primitives = N>,
365{
366 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
367 f.debug_struct("EngineApiTreeHandler")
368 .field("provider", &self.provider)
369 .field("consensus", &self.consensus)
370 .field("payload_validator", &self.payload_validator)
371 .field("state", &self.state)
372 .field("incoming_tx", &self.incoming_tx)
373 .field("persistence", &self.persistence)
374 .field("persistence_state", &self.persistence_state)
375 .field("backfill_sync_state", &self.backfill_sync_state)
376 .field("canonical_in_memory_state", &self.canonical_in_memory_state)
377 .field("payload_builder", &self.payload_builder)
378 .field("config", &self.config)
379 .field("metrics", &self.metrics)
380 .field("engine_kind", &self.engine_kind)
381 .field("evm_config", &self.evm_config)
382 .field("changeset_cache", &self.changeset_cache)
383 .field("execution_timing_stats", &self.execution_timing_stats.len())
384 .field("runtime", &self.runtime)
385 .finish()
386 }
387}
388
389impl<N, P, T, V, C> EngineApiTreeHandler<N, P, T, V, C>
390where
391 N: NodePrimitives,
392 P: DatabaseProviderFactory
393 + BlockReader<Block = N::Block, Header = N::BlockHeader>
394 + StateProviderFactory
395 + StateReader<Receipt = N::Receipt>
396 + HashedPostStateProvider
397 + BalProvider
398 + Clone
399 + 'static,
400 P::Provider: BlockReader<Block = N::Block, Header = N::BlockHeader>
401 + StageCheckpointReader
402 + ChangeSetReader
403 + StorageChangeSetReader
404 + StorageSettingsCache,
405 C: ConfigureEvm<Primitives = N> + 'static,
406 T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>,
407 V: EngineValidator<T> + WaitForCaches,
408{
409 #[expect(clippy::too_many_arguments)]
411 pub fn new(
412 provider: P,
413 consensus: Arc<dyn FullConsensus<N>>,
414 payload_validator: V,
415 outgoing: UnboundedSender<EngineApiEvent<N>>,
416 state: EngineApiTreeState<N>,
417 canonical_in_memory_state: CanonicalInMemoryState<N>,
418 persistence: PersistenceHandle<N>,
419 persistence_state: PersistenceState,
420 payload_builder: PayloadBuilderHandle<T>,
421 config: TreeConfig,
422 engine_kind: EngineApiKind,
423 evm_config: C,
424 changeset_cache: ChangesetCache,
425 runtime: reth_tasks::Runtime,
426 ) -> Self {
427 let (incoming_tx, incoming) = crossbeam_channel::unbounded();
428
429 Self {
430 provider,
431 consensus,
432 payload_validator,
433 incoming,
434 outgoing,
435 persistence,
436 persistence_state,
437 backfill_sync_state: BackfillSyncState::Idle,
438 state,
439 canonical_in_memory_state,
440 payload_builder,
441 config,
442 metrics: Default::default(),
443 incoming_tx,
444 engine_kind,
445 evm_config,
446 changeset_cache,
447 execution_timing_stats: B256Map::default(),
448 building_payload: false,
449 runtime,
450 }
451 }
452
453 #[expect(clippy::complexity)]
459 pub fn spawn_new(
460 provider: P,
461 consensus: Arc<dyn FullConsensus<N>>,
462 payload_validator: V,
463 persistence: PersistenceHandle<N>,
464 payload_builder: PayloadBuilderHandle<T>,
465 canonical_in_memory_state: CanonicalInMemoryState<N>,
466 state_trie_overlays: StateTrieOverlayManager<N>,
467 config: TreeConfig,
468 kind: EngineApiKind,
469 evm_config: C,
470 changeset_cache: ChangesetCache,
471 runtime: reth_tasks::Runtime,
472 ) -> (Sender<FromEngine<EngineApiRequest<T, N>, N::Block>>, UnboundedReceiver<EngineApiEvent<N>>)
473 {
474 let best_block_number = provider.best_block_number().unwrap_or(0);
475 let header = provider.sealed_header(best_block_number).ok().flatten().unwrap_or_default();
476
477 let persistence_state = PersistenceState {
478 last_persisted_block: BlockNumHash::new(best_block_number, header.hash()),
479 rx: None,
480 };
481
482 let (tx, outgoing) = unbounded_channel();
483 let state = EngineApiTreeState::new(
484 config.block_buffer_limit(),
485 config.max_invalid_header_cache_length(),
486 config.invalid_header_hit_eviction_threshold(),
487 header.num_hash(),
488 kind,
489 state_trie_overlays,
490 );
491
492 let task = Self::new(
493 provider,
494 consensus,
495 payload_validator,
496 tx,
497 state,
498 canonical_in_memory_state,
499 persistence,
500 persistence_state,
501 payload_builder,
502 config,
503 kind,
504 evm_config,
505 changeset_cache,
506 runtime,
507 );
508 let incoming = task.incoming_tx.clone();
509 spawn_os_thread("engine", || {
510 increase_thread_priority();
511 task.run()
512 });
513 (incoming, outgoing)
514 }
515
516 fn valid_outcome(state: ForkchoiceState) -> TreeOutcome<OnForkChoiceUpdated> {
518 TreeOutcome::new(OnForkChoiceUpdated::valid(PayloadStatus::new(
519 PayloadStatusEnum::Valid,
520 Some(state.head_block_hash),
521 )))
522 }
523
524 pub fn sender(&self) -> Sender<FromEngine<EngineApiRequest<T, N>, N::Block>> {
526 self.incoming_tx.clone()
527 }
528
529 const fn persistence_gap(&self) -> u64 {
532 self.state
533 .tree_state
534 .canonical_block_number()
535 .saturating_sub(self.persistence_state.last_persisted_block.number)
536 }
537
538 const fn should_backpressure(&self) -> bool {
543 self.persistence_state.in_progress() &&
544 self.persistence_gap() >= self.config.persistence_backpressure_threshold()
545 }
546
547 pub fn run(mut self) {
551 loop {
552 match self.try_poll_persistence() {
576 Ok(true) => {
577 if let Err(err) = self.advance_persistence() {
578 error!(target: "engine::tree", %err, "Advancing persistence failed");
579 return
580 }
581 continue;
582 }
583 Ok(false) => {}
584 Err(err) => {
585 error!(target: "engine::tree", %err, "Polling persistence failed");
586 return
587 }
588 }
589
590 let event = if self.should_backpressure() {
591 self.metrics.engine.backpressure_active.set(1.0);
592 let stall_start = Instant::now();
593 let event = self.wait_for_persistence_event();
594 self.metrics.engine.backpressure_stall_duration.record(stall_start.elapsed());
595 event
596 } else {
597 self.metrics.engine.backpressure_active.set(0.0);
598 self.wait_for_event()
599 };
600
601 match event {
602 LoopEvent::EngineMessage(msg) => {
603 debug!(target: "engine::tree", %msg, "received new engine message");
604 match self.on_engine_message(msg) {
605 Ok(ops::ControlFlow::Break(())) => return,
606 Ok(ops::ControlFlow::Continue(())) => {}
607 Err(fatal) => {
608 error!(target: "engine::tree", %fatal, "insert block fatal error");
609 return
610 }
611 }
612 }
613 LoopEvent::PersistenceComplete { result, start_time } => {
614 if let Err(err) = self.on_persistence_complete(result, start_time) {
615 error!(target: "engine::tree", %err, "Persistence complete handling failed");
616 return
617 }
618 }
619 LoopEvent::Disconnected => {
620 error!(target: "engine::tree", "Channel disconnected");
621 return
622 }
623 }
624
625 if let Err(err) = self.advance_persistence() {
630 error!(target: "engine::tree", %err, "Advancing persistence failed");
631 return
632 }
633 }
634 }
635
636 fn wait_for_persistence_event(&mut self) -> LoopEvent<T, N> {
642 let maybe_persistence = self.persistence_state.rx.take();
643
644 if let Some((persistence_rx, start_time, _action)) = maybe_persistence {
645 match persistence_rx.recv() {
646 Ok(result) => LoopEvent::PersistenceComplete { result, start_time },
647 Err(_) => LoopEvent::Disconnected,
648 }
649 } else {
650 self.wait_for_event()
651 }
652 }
653
654 fn wait_for_event(&mut self) -> LoopEvent<T, N> {
660 let maybe_persistence = self.persistence_state.rx.take();
662
663 if let Some((persistence_rx, start_time, action)) = maybe_persistence {
664 crossbeam_channel::select_biased! {
667 recv(persistence_rx) -> result => {
668 match result {
670 Ok(result) => LoopEvent::PersistenceComplete {
671 result,
672 start_time,
673 },
674 Err(_) => LoopEvent::Disconnected,
675 }
676 },
677 recv(self.incoming) -> msg => {
678 self.persistence_state.rx = Some((persistence_rx, start_time, action));
680 match msg {
681 Ok(m) => LoopEvent::EngineMessage(m),
682 Err(_) => LoopEvent::Disconnected,
683 }
684 },
685 }
686 } else {
687 match self.incoming.recv() {
689 Ok(m) => LoopEvent::EngineMessage(m),
690 Err(_) => LoopEvent::Disconnected,
691 }
692 }
693 }
694
695 fn on_downloaded(
701 &mut self,
702 mut blocks: Vec<SealedBlock<N::Block>>,
703 ) -> Result<Option<TreeEvent>, InsertBlockFatalError> {
704 if blocks.is_empty() {
705 return Ok(None)
707 }
708
709 trace!(target: "engine::tree", block_count = %blocks.len(), "received downloaded blocks");
710 let batch = self.config.max_execute_block_batch_size().min(blocks.len());
711 for block in blocks.drain(..batch) {
712 if let Some(event) = self.on_downloaded_block(block)? {
713 let needs_backfill = event.is_backfill_action();
714 self.on_tree_event(event)?;
715 if needs_backfill {
716 return Ok(None)
718 }
719 }
720 }
721
722 if !blocks.is_empty() {
724 let _ = self.incoming_tx.send(FromEngine::DownloadedBlocks(blocks));
725 }
726
727 Ok(None)
728 }
729
730 #[instrument(
745 level = "debug",
746 target = "engine::tree",
747 skip_all,
748 fields(block_hash = %payload.block_hash(), block_num = %payload.block_number()),
749 )]
750 fn on_new_payload(
751 &mut self,
752 payload: T::ExecutionData,
753 ) -> Result<TreeOutcome<PayloadStatus>, InsertBlockFatalError> {
754 trace!(target: "engine::tree", "invoked new payload");
755
756 let start = Instant::now();
758
759 let num_hash = payload.num_hash();
786 let engine_event = ConsensusEngineEvent::BlockReceived(num_hash);
787 self.emit_event(EngineApiEvent::BeaconConsensus(engine_event));
788
789 let block_hash = num_hash.hash;
790
791 if let Some(invalid) = self.find_invalid_ancestor(&payload) {
793 let status = self.handle_invalid_ancestor_payload(payload, invalid)?;
794 return Ok(TreeOutcome::new(status));
795 }
796
797 self.metrics.block_validation.record_payload_validation(start.elapsed().as_secs_f64());
799
800 let mut outcome = if self.backfill_sync_state.is_idle() {
801 self.try_insert_payload(payload)?.into_outcome()
802 } else {
803 TreeOutcome::new(self.try_buffer_payload(payload)?)
804 };
805
806 if outcome.outcome.is_valid() && self.is_sync_target_head(block_hash) {
808 if self.state.tree_state.canonical_block_hash() != block_hash {
810 outcome = outcome.with_event(TreeEvent::TreeAction(TreeAction::MakeCanonical {
811 sync_target_head: block_hash,
812 }));
813 }
814 }
815
816 self.metrics.block_validation.total_duration.record(start.elapsed().as_secs_f64());
818
819 Ok(outcome)
820 }
821
822 #[instrument(level = "debug", target = "engine::tree", skip_all)]
824 fn try_insert_payload(
825 &mut self,
826 payload: T::ExecutionData,
827 ) -> Result<TryInsertPayloadResult, InsertBlockFatalError> {
828 let block_hash = payload.block_hash();
829 let num_hash = payload.num_hash();
830 let parent_hash = payload.parent_hash();
831 let mut latest_valid_hash = None;
832
833 match self.insert_payload(payload) {
834 Ok(status) => {
835 let (status, already_seen) = match status {
836 InsertPayloadOk::Inserted(BlockStatus::Valid) => {
837 latest_valid_hash = Some(block_hash);
838 self.try_connect_buffered_blocks(num_hash)?;
839 (PayloadStatusEnum::Valid, false)
840 }
841 InsertPayloadOk::AlreadySeen(BlockStatus::Valid) => {
842 latest_valid_hash = Some(block_hash);
843 (PayloadStatusEnum::Valid, true)
844 }
845 InsertPayloadOk::Inserted(BlockStatus::Disconnected { .. }) => {
846 (PayloadStatusEnum::Syncing, false)
847 }
848 InsertPayloadOk::AlreadySeen(BlockStatus::Disconnected { .. }) => {
849 (PayloadStatusEnum::Syncing, true)
851 }
852 };
853
854 Ok(TryInsertPayloadResult {
855 status: PayloadStatus::new(status, latest_valid_hash),
856 already_seen,
857 })
858 }
859 Err(error) => {
860 let status = match error {
861 InsertPayloadError::Block(error) => self.on_insert_block_error(error)?,
862 InsertPayloadError::Payload(error) => {
863 self.on_new_payload_error(error, num_hash, parent_hash)?
864 }
865 };
866
867 Ok(TryInsertPayloadResult { status, already_seen: false })
868 }
869 }
870 }
871
872 fn try_buffer_payload(
881 &mut self,
882 payload: T::ExecutionData,
883 ) -> Result<PayloadStatus, InsertBlockFatalError> {
884 let parent_hash = payload.parent_hash();
885 let num_hash = payload.num_hash();
886
887 match self.payload_validator.convert_payload_to_block(payload) {
888 Ok(block) => {
890 if let Err(error) = self.buffer_block(block) {
891 Ok(self.on_insert_block_error(error)?)
892 } else {
893 Ok(PayloadStatus::from_status(PayloadStatusEnum::Syncing))
894 }
895 }
896 Err(error) => Ok(self.on_new_payload_error(error, num_hash, parent_hash)?),
897 }
898 }
899
900 fn on_new_head(&self, new_head: B256) -> ProviderResult<Option<NewCanonicalChain<N>>> {
907 let Some(new_head_block) = self.state.tree_state.blocks_by_hash.get(&new_head) else {
909 debug!(target: "engine::tree", new_head=?new_head, "New head block not found in inmemory tree state");
910 self.metrics.engine.executed_new_block_cache_miss.increment(1);
911 return Ok(None)
912 };
913
914 let new_head_number = new_head_block.recovered_block().number();
915 let mut current_canonical_number = self.state.tree_state.current_canonical_head.number;
916
917 let mut new_chain = vec![new_head_block.clone()];
918 let mut current_hash = new_head_block.recovered_block().parent_hash();
919 let mut current_number = new_head_number - 1;
920
921 while current_number > current_canonical_number {
926 if let Some(block) = self.state.tree_state.executed_block_by_hash(current_hash).cloned()
927 {
928 current_hash = block.recovered_block().parent_hash();
929 current_number -= 1;
930 new_chain.push(block);
931 } else {
932 warn!(target: "engine::tree", current_hash=?current_hash, "Sidechain block not found in TreeState");
933 return Ok(None)
936 }
937 }
938
939 if current_hash == self.state.tree_state.current_canonical_head.hash {
942 new_chain.reverse();
943
944 return Ok(Some(NewCanonicalChain::Commit { new: new_chain }))
946 }
947
948 let mut old_chain = Vec::new();
950 let mut old_hash = self.state.tree_state.current_canonical_head.hash;
951
952 while current_canonical_number > current_number {
955 let block = self.canonical_block_by_hash(old_hash)?;
956 old_hash = block.recovered_block().parent_hash();
957 old_chain.push(block);
958 current_canonical_number -= 1;
959 }
960
961 debug_assert_eq!(current_number, current_canonical_number);
963
964 while old_hash != current_hash {
967 let block = self.canonical_block_by_hash(old_hash)?;
968 old_hash = block.recovered_block().parent_hash();
969 old_chain.push(block);
970
971 if let Some(block) = self.state.tree_state.executed_block_by_hash(current_hash).cloned()
972 {
973 current_hash = block.recovered_block().parent_hash();
974 new_chain.push(block);
975 } else {
976 warn!(target: "engine::tree", invalid_hash=?current_hash, "New chain block not found in TreeState");
978 return Ok(None)
979 }
980 }
981 new_chain.reverse();
982 old_chain.reverse();
983
984 Ok(Some(NewCanonicalChain::Reorg { new: new_chain, old: old_chain }))
985 }
986
987 fn update_latest_block_to_canonical_ancestor(
999 &mut self,
1000 canonical_header: &SealedHeader<N::BlockHeader>,
1001 ) -> ProviderResult<()> {
1002 debug!(target: "engine::tree", head = ?canonical_header.num_hash(), "Update latest block to canonical ancestor");
1003 let current_head_number = self.state.tree_state.canonical_block_number();
1004 let new_head_number = canonical_header.number();
1005 let new_head_hash = canonical_header.hash();
1006
1007 self.state.tree_state.set_canonical_head(canonical_header.num_hash());
1009
1010 if new_head_number < current_head_number {
1012 debug!(
1013 target: "engine::tree",
1014 current_head = current_head_number,
1015 new_head = new_head_number,
1016 new_head_hash = ?new_head_hash,
1017 "FCU unwind detected: reverting to canonical ancestor"
1018 );
1019
1020 self.handle_canonical_chain_unwind(current_head_number, canonical_header)
1021 } else {
1022 debug!(
1023 target: "engine::tree",
1024 previous_head = current_head_number,
1025 new_head = new_head_number,
1026 new_head_hash = ?new_head_hash,
1027 "Advancing latest block to canonical ancestor"
1028 );
1029 self.handle_chain_advance_or_same_height(canonical_header)
1030 }
1031 }
1032
1033 fn handle_canonical_chain_unwind(
1036 &self,
1037 current_head_number: u64,
1038 canonical_header: &SealedHeader<N::BlockHeader>,
1039 ) -> ProviderResult<()> {
1040 let new_head_number = canonical_header.number();
1041 debug!(
1042 target: "engine::tree",
1043 from = current_head_number,
1044 to = new_head_number,
1045 "Handling unwind: collecting blocks to remove from in-memory state"
1046 );
1047
1048 let old_blocks =
1050 self.collect_blocks_for_canonical_unwind(new_head_number, current_head_number);
1051
1052 self.apply_canonical_ancestor_via_reorg(canonical_header, old_blocks)
1054 }
1055
1056 fn collect_blocks_for_canonical_unwind(
1058 &self,
1059 new_head_number: u64,
1060 current_head_number: u64,
1061 ) -> Vec<ExecutedBlock<N>> {
1062 let mut old_blocks =
1063 Vec::with_capacity((current_head_number.saturating_sub(new_head_number)) as usize);
1064
1065 for block_num in (new_head_number + 1)..=current_head_number {
1066 if let Some(block_state) = self.canonical_in_memory_state.state_by_number(block_num) {
1067 let executed_block = block_state.block_ref().clone();
1068 old_blocks.push(executed_block);
1069 debug!(
1070 target: "engine::tree",
1071 block_number = block_num,
1072 "Collected block for removal from in-memory state"
1073 );
1074 }
1075 }
1076
1077 if old_blocks.is_empty() {
1078 debug!(
1079 target: "engine::tree",
1080 "No blocks found in memory to remove, will clear and reset state"
1081 );
1082 }
1083
1084 old_blocks
1085 }
1086
1087 fn apply_canonical_ancestor_via_reorg(
1089 &self,
1090 canonical_header: &SealedHeader<N::BlockHeader>,
1091 old_blocks: Vec<ExecutedBlock<N>>,
1092 ) -> ProviderResult<()> {
1093 let new_head_hash = canonical_header.hash();
1094 let new_head_number = canonical_header.number();
1095
1096 let executed_block = self.canonical_block_by_hash(new_head_hash)?;
1098 self.canonical_in_memory_state
1100 .update_chain(NewCanonicalChain::Reorg { new: vec![executed_block], old: old_blocks });
1101
1102 self.canonical_in_memory_state.set_canonical_head(canonical_header.clone());
1105
1106 debug!(
1107 target: "engine::tree",
1108 block_number = new_head_number,
1109 block_hash = ?new_head_hash,
1110 "Successfully loaded canonical ancestor into memory via reorg"
1111 );
1112
1113 Ok(())
1114 }
1115
1116 fn handle_chain_advance_or_same_height(
1118 &self,
1119 canonical_header: &SealedHeader<N::BlockHeader>,
1120 ) -> ProviderResult<()> {
1121 self.ensure_block_in_memory(canonical_header.number(), canonical_header.hash())?;
1123
1124 self.canonical_in_memory_state.set_canonical_head(canonical_header.clone());
1126
1127 Ok(())
1128 }
1129
1130 fn ensure_block_in_memory(&self, block_number: u64, block_hash: B256) -> ProviderResult<()> {
1132 if self.canonical_in_memory_state.state_by_number(block_number).is_some() {
1134 return Ok(());
1135 }
1136
1137 let executed_block = self.canonical_block_by_hash(block_hash)?;
1139 self.canonical_in_memory_state
1140 .update_chain(NewCanonicalChain::Commit { new: vec![executed_block] });
1141
1142 debug!(
1143 target: "engine::tree",
1144 block_number,
1145 block_hash = ?block_hash,
1146 "Added canonical block to in-memory state"
1147 );
1148
1149 Ok(())
1150 }
1151
1152 #[instrument(level = "debug", target = "engine::tree", skip_all, fields(head = % state.head_block_hash, safe = % state.safe_block_hash,finalized = % state.finalized_block_hash))]
1161 fn on_forkchoice_updated(
1162 &mut self,
1163 state: ForkchoiceState,
1164 attrs: Option<T::PayloadAttributes>,
1165 ) -> ProviderResult<TreeOutcome<OnForkChoiceUpdated>> {
1166 trace!(target: "engine::tree", ?attrs, "invoked forkchoice update");
1167
1168 self.building_payload = attrs.is_some() && self.config.suppress_persistence_during_build();
1169
1170 self.record_forkchoice_metrics();
1172
1173 if let Some(early_result) = self.validate_forkchoice_state(state)? {
1175 return Ok(TreeOutcome::new(early_result));
1176 }
1177
1178 if let Some(result) = self.handle_canonical_head(state, &attrs)? {
1180 return Ok(result);
1181 }
1182
1183 if let Some(result) = self.apply_chain_update(state, &attrs)? {
1186 return Ok(result);
1187 }
1188
1189 self.handle_missing_block(state)
1191 }
1192
1193 fn record_forkchoice_metrics(&self) {
1195 self.canonical_in_memory_state.on_forkchoice_update_received();
1196 }
1197
1198 fn validate_forkchoice_state(
1203 &mut self,
1204 state: ForkchoiceState,
1205 ) -> ProviderResult<Option<OnForkChoiceUpdated>> {
1206 if state.head_block_hash.is_zero() {
1207 return Ok(Some(OnForkChoiceUpdated::invalid_state()));
1208 }
1209
1210 let lowest_buffered_ancestor_fcu = self.lowest_buffered_ancestor_or(state.head_block_hash);
1213 if let Some(status) = self.check_invalid_ancestor(lowest_buffered_ancestor_fcu)? {
1214 return Ok(Some(OnForkChoiceUpdated::with_invalid(status)));
1215 }
1216
1217 if !self.backfill_sync_state.is_idle() {
1218 trace!(target: "engine::tree", "Pipeline is syncing, skipping forkchoice update");
1221 return Ok(Some(OnForkChoiceUpdated::syncing()));
1222 }
1223
1224 Ok(None)
1225 }
1226
1227 fn handle_canonical_head(
1233 &mut self,
1234 state: ForkchoiceState,
1235 attrs: &Option<T::PayloadAttributes>, ) -> ProviderResult<Option<TreeOutcome<OnForkChoiceUpdated>>> {
1237 if self.state.tree_state.canonical_block_hash() != state.head_block_hash {
1252 return Ok(None);
1253 }
1254
1255 trace!(target: "engine::tree", "fcu head hash is already canonical");
1256
1257 if let Err(outcome) = self.ensure_consistent_forkchoice_state(state) {
1259 return Ok(Some(TreeOutcome::new(outcome)));
1261 }
1262
1263 if let Some(attr) = attrs {
1265 let tip = self
1266 .sealed_header_by_hash(self.state.tree_state.canonical_block_hash())?
1267 .ok_or_else(|| {
1268 ProviderError::HeaderNotFound(state.head_block_hash.into())
1271 })?;
1272 let updated = self.process_payload_attributes(attr.clone(), &tip, state);
1274 return Ok(Some(TreeOutcome::new(updated)));
1275 }
1276
1277 Ok(Some(Self::valid_outcome(state)))
1279 }
1280
1281 fn apply_chain_update(
1293 &mut self,
1294 state: ForkchoiceState,
1295 attrs: &Option<T::PayloadAttributes>,
1296 ) -> ProviderResult<Option<TreeOutcome<OnForkChoiceUpdated>>> {
1297 if let Ok(Some(canonical_header)) = self.find_canonical_header(state.head_block_hash) {
1299 debug!(target: "engine::tree", head = canonical_header.number(), "fcu head block is already canonical");
1300
1301 if self.engine_kind.is_opstack() ||
1304 self.config.always_process_payload_attributes_on_canonical_head()
1305 {
1306 if self.config.unwind_canonical_header() {
1312 self.update_latest_block_to_canonical_ancestor(&canonical_header)?;
1313 }
1314
1315 if let Some(attr) = attrs {
1316 debug!(target: "engine::tree", head = canonical_header.number(), "handling payload attributes for canonical head");
1317 let updated =
1319 self.process_payload_attributes(attr.clone(), &canonical_header, state);
1320 return Ok(Some(TreeOutcome::new(updated)));
1321 }
1322 }
1323
1324 return Ok(Some(Self::valid_outcome(state)));
1335 }
1336
1337 if let Some(chain_update) = self.on_new_head(state.head_block_hash)? {
1339 let tip = chain_update.tip().clone_sealed_header();
1340 self.on_canonical_chain_update(chain_update);
1341
1342 if let Err(outcome) = self.ensure_consistent_forkchoice_state(state) {
1344 return Ok(Some(TreeOutcome::new(outcome)));
1346 }
1347
1348 if let Some(attr) = attrs {
1349 let updated = self.process_payload_attributes(attr.clone(), &tip, state);
1351 return Ok(Some(TreeOutcome::new(updated)));
1352 }
1353
1354 return Ok(Some(Self::valid_outcome(state)));
1355 }
1356
1357 Ok(None)
1358 }
1359
1360 fn handle_missing_block(
1365 &self,
1366 state: ForkchoiceState,
1367 ) -> ProviderResult<TreeOutcome<OnForkChoiceUpdated>> {
1368 let target = if self.state.forkchoice_state_tracker.is_empty() &&
1375 !state.safe_block_hash.is_zero() &&
1377 self.find_canonical_header(state.safe_block_hash).ok().flatten().is_none()
1378 {
1379 debug!(target: "engine::tree", "missing safe block on initial FCU, downloading safe block");
1380 state.safe_block_hash
1381 } else {
1382 state.head_block_hash
1383 };
1384
1385 let target = self.lowest_buffered_ancestor_or(target);
1386 trace!(target: "engine::tree", %target, "downloading missing block");
1387
1388 Ok(TreeOutcome::new(OnForkChoiceUpdated::valid(PayloadStatus::from_status(
1389 PayloadStatusEnum::Syncing,
1390 )))
1391 .with_event(TreeEvent::Download(DownloadRequest::single_block(target))))
1392 }
1393
1394 fn remove_blocks(&mut self, new_tip_num: u64) {
1397 debug!(target: "engine::tree", ?new_tip_num, last_persisted_block_number=?self.persistence_state.last_persisted_block.number, "Removing blocks using persistence task");
1398 if new_tip_num < self.persistence_state.last_persisted_block.number {
1399 debug!(target: "engine::tree", ?new_tip_num, "Starting remove blocks job");
1400 self.state.set_pending_sparse_trie_prune(false);
1401 let (tx, rx) = crossbeam_channel::bounded(1);
1402 let _ = self.persistence.remove_blocks_above(new_tip_num, tx);
1403 self.persistence_state.start_remove(new_tip_num, rx);
1404 }
1405 }
1406
1407 fn persist_blocks(&mut self, blocks_to_persist: Vec<ExecutedBlock<N>>) {
1410 if blocks_to_persist.is_empty() {
1411 debug!(target: "engine::tree", "Returned empty set of blocks to persist");
1412 return
1413 }
1414
1415 let highest_num_hash = blocks_to_persist
1417 .iter()
1418 .max_by_key(|block| block.recovered_block().number())
1419 .map(|b| b.recovered_block().num_hash())
1420 .expect("Checked non-empty persisting blocks");
1421
1422 debug!(target: "engine::tree", count=blocks_to_persist.len(), blocks = ?blocks_to_persist.iter().map(|block| block.recovered_block().num_hash()).collect::<Vec<_>>(), "Persisting blocks");
1423 let (tx, rx) = crossbeam_channel::bounded(1);
1424 let _ = self.persistence.save_blocks(blocks_to_persist, tx);
1425
1426 self.persistence_state.start_save(highest_num_hash, rx);
1427 }
1428
1429 fn advance_persistence(&mut self) -> Result<(), AdvancePersistenceError> {
1434 if !self.persistence_state.in_progress() {
1435 if let Some(new_tip_num) = self.find_disk_reorg()? {
1436 self.remove_blocks(new_tip_num)
1437 } else if self.should_persist() {
1438 let blocks_to_persist =
1439 self.get_canonical_blocks_to_persist(PersistTarget::Threshold)?;
1440 self.persist_blocks(blocks_to_persist);
1441 }
1442 }
1443
1444 Ok(())
1445 }
1446
1447 fn finish_termination(
1452 &mut self,
1453 pending_termination: oneshot::Sender<()>,
1454 ) -> Result<(), AdvancePersistenceError> {
1455 trace!(target: "engine::tree", "finishing termination, persisting remaining blocks");
1456 let result = self.persist_until_complete();
1457 let _ = pending_termination.send(());
1458 result
1459 }
1460
1461 fn persist_until_complete(&mut self) -> Result<(), AdvancePersistenceError> {
1463 loop {
1464 if let Some((rx, start_time, action)) = self.persistence_state.rx.take() {
1466 debug!(target: "engine::tree", ?action, "waiting for in-flight persistence");
1467 let result = rx.recv().map_err(|_| AdvancePersistenceError::ChannelClosed)?;
1468 self.on_persistence_complete(result, start_time)?;
1469 }
1470
1471 let blocks_to_persist = self.get_canonical_blocks_to_persist(PersistTarget::Head)?;
1472
1473 if blocks_to_persist.is_empty() {
1474 debug!(target: "engine::tree", "persistence complete, signaling termination");
1475 return Ok(())
1476 }
1477
1478 debug!(target: "engine::tree", count = blocks_to_persist.len(), "persisting remaining blocks before shutdown");
1479 self.persist_blocks(blocks_to_persist);
1480 }
1481 }
1482
1483 fn try_poll_persistence(&mut self) -> Result<bool, AdvancePersistenceError> {
1487 let Some((rx, start_time, action)) = self.persistence_state.rx.take() else {
1488 return Ok(false);
1489 };
1490
1491 match rx.try_recv() {
1492 Ok(result) => {
1493 self.on_persistence_complete(result, start_time)?;
1494 Ok(true)
1495 }
1496 Err(crossbeam_channel::TryRecvError::Empty) => {
1497 self.persistence_state.rx = Some((rx, start_time, action));
1499 Ok(false)
1500 }
1501 Err(crossbeam_channel::TryRecvError::Disconnected) => {
1502 Err(AdvancePersistenceError::ChannelClosed)
1503 }
1504 }
1505 }
1506
1507 fn on_persistence_complete(
1509 &mut self,
1510 result: PersistenceResult,
1511 start_time: Instant,
1512 ) -> Result<(), AdvancePersistenceError> {
1513 self.metrics.engine.persistence_duration.record(start_time.elapsed());
1514
1515 let commit_duration = result.commit_duration;
1516 let Some(BlockNumHash {
1517 hash: last_persisted_block_hash,
1518 number: last_persisted_block_number,
1519 }) = result.last_block
1520 else {
1521 warn!(target: "engine::tree", "Persistence task completed but did not persist any blocks");
1523 return Ok(())
1524 };
1525
1526 debug!(target: "engine::tree", ?last_persisted_block_hash, ?last_persisted_block_number, elapsed=?start_time.elapsed(), "Finished persisting, calling finish");
1527 self.persistence_state.finish(last_persisted_block_hash, last_persisted_block_number);
1528
1529 let min_threshold =
1533 last_persisted_block_number.saturating_sub(CHANGESET_CACHE_RETENTION_BLOCKS);
1534 let eviction_threshold =
1535 if let Some(finalized) = self.canonical_in_memory_state.get_finalized_num_hash() {
1536 finalized.number.min(min_threshold)
1538 } else {
1539 min_threshold
1541 };
1542 debug!(
1543 target: "engine::tree",
1544 last_persisted = last_persisted_block_number,
1545 finalized_number = ?self.canonical_in_memory_state.get_finalized_num_hash().map(|f| f.number),
1546 eviction_threshold,
1547 "Evicting changesets below threshold"
1548 );
1549 self.changeset_cache.evict(eviction_threshold);
1550
1551 self.on_new_persisted_block()?;
1552
1553 self.purge_timing_stats(last_persisted_block_number, commit_duration);
1554
1555 Ok(())
1556 }
1557
1558 fn on_engine_message(
1562 &mut self,
1563 msg: FromEngine<EngineApiRequest<T, N>, N::Block>,
1564 ) -> Result<ops::ControlFlow<()>, InsertBlockFatalError> {
1565 match msg {
1566 FromEngine::Event(event) => match event {
1567 FromOrchestrator::BackfillSyncStarted => {
1568 debug!(target: "engine::tree", "received backfill sync started event");
1569 self.backfill_sync_state = BackfillSyncState::Active;
1570 }
1571 FromOrchestrator::BackfillSyncFinished(ctrl) => {
1572 self.on_backfill_sync_finished(ctrl)?;
1573 }
1574 FromOrchestrator::Terminate { tx } => {
1575 debug!(target: "engine::tree", "received terminate request");
1576 if let Err(err) = self.finish_termination(tx) {
1577 error!(target: "engine::tree", %err, "Termination failed");
1578 }
1579 return Ok(ops::ControlFlow::Break(()))
1580 }
1581 },
1582 FromEngine::Request(request) => {
1583 match request {
1584 EngineApiRequest::InsertExecutedBlock(payload) => {
1585 let block_num_hash = payload.recovered_block.num_hash();
1586 if block_num_hash.number <= self.state.tree_state.canonical_block_number() {
1587 return Ok(ops::ControlFlow::Continue(()))
1589 }
1590
1591 if self.state.tree_state.contains_hash(&block_num_hash.hash) {
1592 return Ok(ops::ControlFlow::Continue(()))
1594 }
1595
1596 debug!(target: "engine::tree", block=?block_num_hash, "inserting already executed block");
1597 let now = Instant::now();
1598
1599 let block = match self.payload_validator.on_inserted_executed_block(payload)
1600 {
1601 Ok(block) => block,
1602 Err(err) => {
1603 warn!(target: "engine::tree", %err, block=?block_num_hash, "Failed to insert already executed block");
1604 return Ok(ops::ControlFlow::Continue(()))
1605 }
1606 };
1607
1608 if self.state.tree_state.canonical_block_hash() ==
1611 block.recovered_block().parent_hash()
1612 {
1613 debug!(target: "engine::tree", pending=?block_num_hash, "updating pending block");
1614 self.canonical_in_memory_state.set_pending_block(block.clone());
1615 }
1616
1617 self.state.tree_state.insert_executed(block.clone());
1618 self.metrics.engine.inserted_already_executed_blocks.increment(1);
1619 self.emit_event(EngineApiEvent::BeaconConsensus(
1620 ConsensusEngineEvent::CanonicalBlockAdded(block, now.elapsed()),
1621 ));
1622 }
1623 EngineApiRequest::Beacon(request) => {
1624 match request {
1625 BeaconEngineMessage::ForkchoiceUpdated { state, payload_attrs, tx } => {
1626 let has_attrs = payload_attrs.is_some();
1627
1628 let start = Instant::now();
1629 let mut output = self.on_forkchoice_updated(state, payload_attrs);
1630
1631 if let Ok(res) = &mut output {
1632 self.state
1634 .forkchoice_state_tracker
1635 .set_latest(state, res.outcome.forkchoice_status());
1636
1637 self.emit_event(ConsensusEngineEvent::ForkchoiceUpdated(
1639 state,
1640 res.outcome.forkchoice_status(),
1641 ));
1642
1643 self.on_maybe_tree_event(res.event.take())?;
1645 }
1646
1647 if let Err(ref err) = output {
1648 error!(target: "engine::tree", %err, ?state, "Error processing forkchoice update");
1649 }
1650
1651 self.metrics.engine.forkchoice_updated.update_response_metrics(
1652 start,
1653 &mut self.metrics.engine.new_payload.latest_finish_at,
1654 has_attrs,
1655 &output,
1656 );
1657
1658 if let Err(err) =
1659 tx.send(output.map(|o| o.outcome).map_err(Into::into))
1660 {
1661 self.metrics
1662 .engine
1663 .failed_forkchoice_updated_response_deliveries
1664 .increment(1);
1665 warn!(target: "engine::tree", ?state, elapsed=?start.elapsed(), "Failed to deliver forkchoiceUpdated response, receiver dropped (request cancelled): {err:?}");
1666 }
1667 }
1668 BeaconEngineMessage::NewPayload { payload, tx } => {
1669 let start = Instant::now();
1670 let gas_used = payload.gas_used();
1671 let num_hash = payload.num_hash();
1672 let mut output = self.on_new_payload(payload);
1673 self.metrics.engine.new_payload.update_response_metrics(
1674 start,
1675 &mut self.metrics.engine.forkchoice_updated.latest_finish_at,
1676 &output,
1677 gas_used,
1678 );
1679
1680 let maybe_event =
1681 output.as_mut().ok().and_then(|out| out.event.take());
1682
1683 if let Err(err) =
1685 tx.send(output.map(|o| o.outcome).map_err(|e| {
1686 BeaconOnNewPayloadError::Internal(Box::new(e))
1687 }))
1688 {
1689 warn!(target: "engine::tree", payload=?num_hash, elapsed=?start.elapsed(), "Failed to deliver newPayload response, receiver dropped (request cancelled): {err:?}");
1690 self.metrics
1691 .engine
1692 .failed_new_payload_response_deliveries
1693 .increment(1);
1694 }
1695
1696 self.on_maybe_tree_event(maybe_event)?;
1698 }
1699 BeaconEngineMessage::RethNewPayload {
1700 payload,
1701 wait_for_persistence,
1702 wait_for_caches,
1703 tx,
1704 enqueued_at,
1705 } => {
1706 debug!(
1707 target: "engine::tree",
1708 wait_for_persistence,
1709 wait_for_caches,
1710 "Processing reth_newPayload"
1711 );
1712
1713 let backpressure_wait = enqueued_at.elapsed();
1714
1715 let explicit_persistence_wait = if wait_for_persistence {
1716 let pending_persistence = self.persistence_state.rx.take();
1717 if let Some((rx, start_time, _action)) = pending_persistence {
1718 let (persistence_tx, persistence_rx) =
1719 std::sync::mpsc::channel();
1720 self.runtime.spawn_blocking_named(
1721 "wait-persist",
1722 move || {
1723 let start = Instant::now();
1724 let result = rx
1725 .recv()
1726 .expect("persistence state channel closed");
1727 let _ = persistence_tx.send((
1728 result,
1729 start_time,
1730 start.elapsed(),
1731 ));
1732 },
1733 );
1734 let (result, start_time, wait_duration) = persistence_rx
1735 .recv()
1736 .expect("persistence result channel closed");
1737 let _ = self.on_persistence_complete(result, start_time);
1738 wait_duration
1739 } else {
1740 Duration::ZERO
1741 }
1742 } else {
1743 Duration::ZERO
1744 };
1745
1746 let cache_wait = wait_for_caches
1747 .then(|| self.payload_validator.wait_for_caches());
1748
1749 let start = Instant::now();
1750 let gas_used = payload.gas_used();
1751 let num_hash = payload.num_hash();
1752 let mut output = self.on_new_payload(payload);
1753 let latency = start.elapsed();
1754 self.metrics.engine.new_payload.update_response_metrics(
1755 start,
1756 &mut self.metrics.engine.forkchoice_updated.latest_finish_at,
1757 &output,
1758 gas_used,
1759 );
1760
1761 let maybe_event =
1762 output.as_mut().ok().and_then(|out| out.event.take());
1763
1764 let timings = NewPayloadTimings {
1765 latency,
1766 persistence_wait: backpressure_wait + explicit_persistence_wait,
1767 execution_cache_wait: cache_wait
1768 .map(|wait| wait.execution_cache),
1769 sparse_trie_wait: cache_wait.map(|wait| wait.sparse_trie),
1770 };
1771 if let Err(err) =
1772 tx.send(output.map(|o| (o.outcome, timings)).map_err(|e| {
1773 BeaconOnNewPayloadError::Internal(Box::new(e))
1774 }))
1775 {
1776 error!(target: "engine::tree", payload=?num_hash, elapsed=?start.elapsed(), "Failed to send event: {err:?}");
1777 self.metrics
1778 .engine
1779 .failed_new_payload_response_deliveries
1780 .increment(1);
1781 }
1782
1783 self.on_maybe_tree_event(maybe_event)?;
1784 }
1785 }
1786 }
1787 }
1788 }
1789 FromEngine::DownloadedBlocks(blocks) => {
1790 if let Some(event) = self.on_downloaded(blocks)? {
1791 self.on_tree_event(event)?;
1792 }
1793 }
1794 }
1795 Ok(ops::ControlFlow::Continue(()))
1796 }
1797
1798 fn on_backfill_sync_finished(
1812 &mut self,
1813 ctrl: ControlFlow,
1814 ) -> Result<(), InsertBlockFatalError> {
1815 debug!(target: "engine::tree", "received backfill sync finished event");
1816 self.backfill_sync_state = BackfillSyncState::Idle;
1817
1818 let backfill_height = if let ControlFlow::Unwind { bad_block, target } = &ctrl {
1820 warn!(target: "engine::tree", invalid_block=?bad_block, "Bad block detected in unwind");
1821 self.state.invalid_headers.insert(**bad_block);
1823
1824 Some(*target)
1826 } else {
1827 ctrl.block_number()
1829 };
1830
1831 let Some(backfill_height) = backfill_height else { return Ok(()) };
1833
1834 let Some(backfill_num_hash) = self
1840 .provider
1841 .block_hash(backfill_height)?
1842 .map(|hash| BlockNumHash { hash, number: backfill_height })
1843 else {
1844 debug!(target: "engine::tree", ?ctrl, "Backfill block not found");
1845 return Ok(())
1846 };
1847
1848 if ctrl.is_unwind() {
1849 self.state.set_pending_sparse_trie_prune(false);
1852 self.state.tree_state.reset(backfill_num_hash)
1853 } else {
1854 self.state.tree_state.remove_until(
1855 backfill_num_hash,
1856 self.persistence_state.last_persisted_block.hash,
1857 Some(backfill_num_hash),
1858 );
1859 }
1860
1861 self.metrics.engine.executed_blocks.set(self.state.tree_state.block_count() as f64);
1862 self.metrics.tree.canonical_chain_height.set(backfill_height as f64);
1863
1864 self.state.buffer.remove_old_blocks(backfill_height);
1866 self.purge_timing_stats(backfill_height, None);
1867 self.canonical_in_memory_state.clear_state();
1870
1871 if let Ok(Some(new_head)) = self.provider.sealed_header(backfill_height) {
1872 self.state.tree_state.set_canonical_head(new_head.num_hash());
1875 self.persistence_state.finish(new_head.hash(), new_head.number());
1876
1877 self.canonical_in_memory_state.set_canonical_head(new_head);
1879 }
1880
1881 let Some(sync_target_state) = self.state.forkchoice_state_tracker.sync_target_state()
1884 else {
1885 return Ok(())
1886 };
1887 if !self.engine_kind.is_opstack() && sync_target_state.finalized_block_hash.is_zero() {
1888 return Ok(())
1890 }
1891 let target_hash = self.backfill_target_hash(sync_target_state);
1892 if target_hash.is_zero() {
1893 return Ok(())
1894 }
1895 let newest_target = self.state.buffer.block(&target_hash).map(|block| block.number());
1897
1898 if let Some(backfill_target) =
1904 ctrl.block_number().zip(newest_target).and_then(|(progress, target_number)| {
1905 self.backfill_sync_target(progress, target_number, None)
1908 })
1909 {
1910 self.emit_event(EngineApiEvent::BackfillAction(BackfillAction::Start(
1912 backfill_target.into(),
1913 )));
1914 return Ok(())
1915 };
1916
1917 if let Some(lowest_buffered) =
1919 self.state.buffer.lowest_ancestor(&sync_target_state.head_block_hash)
1920 {
1921 let current_head_num = self.state.tree_state.current_canonical_head.number;
1922 let target_head_num = lowest_buffered.number();
1923
1924 if let Some(distance) = self.distance_from_local_tip(current_head_num, target_head_num)
1925 {
1926 debug!(
1928 target: "engine::tree",
1929 %current_head_num,
1930 %target_head_num,
1931 %distance,
1932 "Backfill complete, downloading remaining blocks to reach FCU target"
1933 );
1934
1935 self.emit_event(EngineApiEvent::Download(DownloadRequest::BlockRange(
1936 lowest_buffered.parent_hash(),
1937 distance,
1938 )));
1939 return Ok(());
1940 }
1941 } else {
1942 debug!(
1945 target: "engine::tree",
1946 head_hash = %sync_target_state.head_block_hash,
1947 "Backfill complete but head block not buffered, requesting download"
1948 );
1949 self.emit_event(EngineApiEvent::Download(DownloadRequest::single_block(
1950 sync_target_state.head_block_hash,
1951 )));
1952 return Ok(());
1953 }
1954
1955 self.try_connect_buffered_blocks(self.state.tree_state.current_canonical_head)
1957 }
1958
1959 fn make_canonical(&mut self, target: B256) -> ProviderResult<()> {
1963 if let Some(chain_update) = self.on_new_head(target)? {
1964 self.on_canonical_chain_update(chain_update);
1965 }
1966
1967 self.on_canonicalized_sync_target(target);
1968
1969 Ok(())
1970 }
1971
1972 fn on_canonicalized_sync_target(&mut self, target: B256) {
1974 let Some(sync_target_state) = self
1975 .state
1976 .forkchoice_state_tracker
1977 .sync_target_state()
1978 .filter(|state| state.head_block_hash == target)
1979 else {
1980 return;
1981 };
1982
1983 if let Err(outcome) = self.ensure_consistent_forkchoice_state(sync_target_state) {
1984 debug!(
1985 target: "engine::tree",
1986 head = %sync_target_state.head_block_hash,
1987 safe = %sync_target_state.safe_block_hash,
1988 finalized = %sync_target_state.finalized_block_hash,
1989 ?outcome,
1990 "Canonicalized sync target head before safe/finalized could be applied"
1991 );
1992 return;
1993 }
1994
1995 self.state.forkchoice_state_tracker.promote_sync_target_to_valid(sync_target_state);
1996 }
1997
1998 fn on_maybe_tree_event(&mut self, event: Option<TreeEvent>) -> ProviderResult<()> {
2000 if let Some(event) = event {
2001 self.on_tree_event(event)?;
2002 }
2003
2004 Ok(())
2005 }
2006
2007 fn on_tree_event(&mut self, event: TreeEvent) -> ProviderResult<()> {
2011 match event {
2012 TreeEvent::TreeAction(action) => match action {
2013 TreeAction::MakeCanonical { sync_target_head } => {
2014 self.make_canonical(sync_target_head)?;
2015 }
2016 },
2017 TreeEvent::BackfillAction(action) => {
2018 self.emit_event(EngineApiEvent::BackfillAction(action));
2019 }
2020 TreeEvent::Download(action) => {
2021 self.emit_event(EngineApiEvent::Download(action));
2022 }
2023 }
2024
2025 Ok(())
2026 }
2027
2028 fn purge_timing_stats(&mut self, below_number: u64, commit_duration: Option<Duration>) {
2035 let threshold = self.config.slow_block_threshold();
2036 let check_slow = commit_duration.is_some() && threshold.is_some();
2037
2038 let keys_to_remove: Vec<B256> = self
2040 .execution_timing_stats
2041 .iter()
2042 .filter(|(_, stats)| stats.block_number <= below_number)
2043 .map(|(k, _)| *k)
2044 .collect();
2045
2046 for key in keys_to_remove {
2047 let stats = self.execution_timing_stats.remove(&key).expect("key just found");
2048 if check_slow {
2049 let commit_dur = commit_duration.expect("checked above");
2050 let total_duration =
2052 stats.execution_duration + stats.state_hash_duration + commit_dur;
2053
2054 if total_duration > threshold.expect("checked above") {
2055 self.emit_event(ConsensusEngineEvent::SlowBlock(SlowBlockInfo {
2056 stats,
2057 commit_duration: Some(commit_dur),
2058 total_duration,
2059 }));
2060 }
2061 }
2062 }
2063 }
2064
2065 fn emit_event(&mut self, event: impl Into<EngineApiEvent<N>>) {
2067 let event = event.into();
2068
2069 if event.is_backfill_action() {
2070 debug_assert_eq!(
2071 self.backfill_sync_state,
2072 BackfillSyncState::Idle,
2073 "backfill action should only be emitted when backfill is idle"
2074 );
2075
2076 if self.persistence_state.in_progress() {
2077 debug!(target: "engine::tree", "skipping backfill file while persistence task is active");
2080 return
2081 }
2082
2083 self.backfill_sync_state = BackfillSyncState::Pending;
2084 self.metrics.engine.pipeline_runs.increment(1);
2085 debug!(target: "engine::tree", "emitting backfill action event");
2086 }
2087
2088 let _ = self.outgoing.send(event).inspect_err(
2089 |err| error!(target: "engine::tree", "Failed to send internal event: {err:?}"),
2090 );
2091 }
2092
2093 pub const fn should_persist(&self) -> bool {
2097 if self.building_payload {
2098 return false
2099 }
2100
2101 if !self.backfill_sync_state.is_idle() {
2102 return false
2104 }
2105
2106 let min_block = self.persistence_state.last_persisted_block.number;
2107 self.state.tree_state.canonical_block_number().saturating_sub(min_block) >
2108 self.config.persistence_threshold()
2109 }
2110
2111 fn get_canonical_blocks_to_persist(
2114 &self,
2115 target: PersistTarget,
2116 ) -> Result<Vec<ExecutedBlock<N>>, AdvancePersistenceError> {
2117 debug_assert!(!self.persistence_state.in_progress());
2120
2121 let mut blocks_to_persist = Vec::new();
2122 let mut current_hash = self.state.tree_state.canonical_block_hash();
2123 let last_persisted_number = self.persistence_state.last_persisted_block.number;
2124 let canonical_head_number = self.state.tree_state.canonical_block_number();
2125
2126 let target_number = match target {
2127 PersistTarget::Head => canonical_head_number,
2128 PersistTarget::Threshold => {
2129 canonical_head_number.saturating_sub(self.config.memory_block_buffer_target())
2130 }
2131 };
2132
2133 debug!(
2134 target: "engine::tree",
2135 ?current_hash,
2136 ?last_persisted_number,
2137 ?canonical_head_number,
2138 ?target_number,
2139 "Returning canonical blocks to persist"
2140 );
2141 while let Some(block) = self.state.tree_state.blocks_by_hash.get(¤t_hash) {
2142 if block.recovered_block().number() <= last_persisted_number {
2143 break;
2144 }
2145
2146 if block.recovered_block().number() <= target_number {
2147 blocks_to_persist.push(block.clone());
2148 }
2149
2150 current_hash = block.recovered_block().parent_hash();
2151 }
2152
2153 blocks_to_persist.reverse();
2155
2156 Ok(blocks_to_persist)
2157 }
2158
2159 fn on_new_persisted_block(&mut self) -> ProviderResult<()> {
2167 if let Some(remove_above) = self.find_disk_reorg()? {
2170 self.remove_blocks(remove_above);
2171 return Ok(())
2172 }
2173
2174 let finalized = self.state.forkchoice_state_tracker.last_valid_finalized();
2175 self.remove_before(self.persistence_state.last_persisted_block, finalized)?;
2176 self.canonical_in_memory_state.remove_persisted_blocks(BlockNumHash {
2177 number: self.persistence_state.last_persisted_block.number,
2178 hash: self.persistence_state.last_persisted_block.hash,
2179 });
2180 self.state.set_pending_sparse_trie_prune(self.should_prune_sparse_trie());
2181 Ok(())
2182 }
2183
2184 const fn should_prune_sparse_trie(&self) -> bool {
2186 self.config.use_state_root_task()
2187 }
2188
2189 #[instrument(level = "debug", target = "engine::tree", skip(self))]
2196 fn canonical_block_by_hash(&self, hash: B256) -> ProviderResult<ExecutedBlock<N>> {
2197 trace!(target: "engine::tree", ?hash, "Fetching executed block by hash");
2198 if let Some(block) = self.state.tree_state.executed_block_by_hash(hash) {
2200 return Ok(block.clone())
2201 }
2202
2203 let (block, senders) = self
2204 .provider
2205 .sealed_block_with_senders(hash.into(), TransactionVariant::WithHash)?
2206 .ok_or_else(|| ProviderError::HeaderNotFound(hash.into()))?
2207 .split_sealed();
2208 let mut execution_output = self
2209 .provider
2210 .get_state(block.header().number())?
2211 .ok_or_else(|| ProviderError::StateForNumberNotFound(block.header().number()))?;
2212 let hashed_state = self.provider.hashed_post_state(execution_output.state());
2213
2214 debug!(
2215 target: "engine::tree",
2216 number = ?block.number(),
2217 "computing block trie updates",
2218 );
2219 let db_provider = self.provider.database_provider_ro()?;
2220 let trie_updates = reth_trie_db::compute_block_trie_updates(
2221 &self.changeset_cache,
2222 &db_provider,
2223 block.number(),
2224 )?;
2225
2226 let sorted_hashed_state = Arc::new(hashed_state.into_sorted());
2227 let sorted_trie_updates = Arc::new(trie_updates);
2228 let trie_data = ComputedTrieData::new(sorted_hashed_state, sorted_trie_updates);
2229
2230 let execution_output = Arc::new(BlockExecutionOutput {
2231 state: execution_output.bundle,
2232 result: BlockExecutionResult {
2233 receipts: execution_output.receipts.pop().unwrap_or_default(),
2234 requests: execution_output.requests.pop().unwrap_or_default(),
2235 gas_used: block.gas_used(),
2236 blob_gas_used: block.blob_gas_used().unwrap_or_default(),
2237 },
2238 });
2239
2240 Ok(ExecutedBlock::new(
2241 Arc::new(RecoveredBlock::new_sealed(block, senders)),
2242 execution_output,
2243 trie_data,
2244 ))
2245 }
2246
2247 fn has_block_by_hash(&self, hash: B256) -> ProviderResult<bool> {
2251 if self.state.tree_state.contains_hash(&hash) {
2252 Ok(true)
2253 } else {
2254 self.provider.is_known(hash)
2255 }
2256 }
2257
2258 fn sealed_header_by_hash(
2260 &self,
2261 hash: B256,
2262 ) -> ProviderResult<Option<SealedHeader<N::BlockHeader>>> {
2263 let header = self.state.tree_state.sealed_header_by_hash(&hash);
2265
2266 if header.is_some() {
2267 Ok(header)
2268 } else {
2269 self.provider.sealed_header_by_hash(hash)
2270 }
2271 }
2272
2273 fn lowest_buffered_ancestor_or(&self, hash: B256) -> B256 {
2280 self.state
2281 .buffer
2282 .lowest_ancestor(&hash)
2283 .map(|block| block.parent_hash())
2284 .unwrap_or_else(|| hash)
2285 }
2286
2287 fn latest_valid_hash_for_invalid_payload(
2298 &mut self,
2299 parent_hash: B256,
2300 ) -> ProviderResult<Option<B256>> {
2301 if self.has_block_by_hash(parent_hash)? {
2303 return Ok(Some(parent_hash))
2304 }
2305
2306 let mut current_hash = parent_hash;
2309 let mut current_block = self.state.invalid_headers.get(¤t_hash);
2310 while let Some(block_with_parent) = current_block {
2311 current_hash = block_with_parent.parent;
2312 current_block = self.state.invalid_headers.get(¤t_hash);
2313
2314 if current_block.is_none() && self.has_block_by_hash(current_hash)? {
2317 return Ok(Some(current_hash))
2318 }
2319 }
2320 Ok(None)
2321 }
2322
2323 fn prepare_invalid_response(&mut self, parent_hash: B256) -> ProviderResult<PayloadStatus> {
2327 let valid_parent_hash = match self.sealed_header_by_hash(parent_hash)? {
2328 Some(parent) if !parent.difficulty().is_zero() => Some(B256::ZERO),
2332 Some(_) => Some(parent_hash),
2333 None => self.latest_valid_hash_for_invalid_payload(parent_hash)?,
2334 };
2335
2336 Ok(PayloadStatus::from_status(PayloadStatusEnum::Invalid {
2337 validation_error: PayloadValidationError::LinksToRejectedPayload.to_string(),
2338 })
2339 .with_latest_valid_hash(valid_parent_hash.unwrap_or_default()))
2340 }
2341
2342 fn is_sync_target_head(&self, block_hash: B256) -> bool {
2346 if let Some(target) = self.state.forkchoice_state_tracker.sync_target_state() {
2347 return target.head_block_hash == block_hash
2348 }
2349 false
2350 }
2351
2352 fn is_any_sync_target(&self, block_hash: B256) -> bool {
2356 if let Some(target) = self.state.forkchoice_state_tracker.sync_target_state() {
2357 return target.contains(block_hash)
2358 }
2359 false
2360 }
2361
2362 fn check_invalid_ancestor_with_head(
2368 &mut self,
2369 check: B256,
2370 head: &SealedBlock<N::Block>,
2371 ) -> ProviderResult<Option<PayloadStatus>> {
2372 let Some(header) = self.state.invalid_headers.get(&check) else { return Ok(None) };
2374
2375 Ok(Some(self.on_invalid_new_payload(head.clone(), header)?))
2376 }
2377
2378 fn on_invalid_new_payload(
2380 &mut self,
2381 head: SealedBlock<N::Block>,
2382 invalid: BlockWithParent,
2383 ) -> ProviderResult<PayloadStatus> {
2384 let status = self.prepare_invalid_response(invalid.parent)?;
2386
2387 self.state.invalid_headers.insert_with_invalid_ancestor(head.hash(), invalid);
2389 self.emit_event(ConsensusEngineEvent::InvalidBlock {
2390 block: Box::new(head),
2391 error: PayloadValidationError::LinksToRejectedPayload.to_string(),
2392 });
2393
2394 Ok(status)
2395 }
2396
2397 fn find_invalid_ancestor(&mut self, payload: &T::ExecutionData) -> Option<BlockWithParent> {
2411 let parent_hash = payload.parent_hash();
2412 let block_hash = payload.block_hash();
2413
2414 if let Some(entry) = self.state.invalid_headers.get(&block_hash) {
2416 return Some(entry);
2417 }
2418
2419 let mut lowest_buffered_ancestor = self.lowest_buffered_ancestor_or(block_hash);
2420 if lowest_buffered_ancestor == block_hash {
2421 lowest_buffered_ancestor = parent_hash;
2422 }
2423
2424 self.state.invalid_headers.get(&lowest_buffered_ancestor)
2426 }
2427
2428 fn handle_invalid_ancestor_payload(
2437 &mut self,
2438 payload: T::ExecutionData,
2439 invalid: BlockWithParent,
2440 ) -> Result<PayloadStatus, InsertBlockFatalError> {
2441 let parent_hash = payload.parent_hash();
2442 let num_hash = payload.num_hash();
2443
2444 let block = match self.payload_validator.convert_payload_to_block(payload) {
2450 Ok(block) => block,
2451 Err(error) => return Ok(self.on_new_payload_error(error, num_hash, parent_hash)?),
2452 };
2453
2454 Ok(self.on_invalid_new_payload(block, invalid)?)
2455 }
2456
2457 fn check_invalid_ancestor(&mut self, head: B256) -> ProviderResult<Option<PayloadStatus>> {
2460 let Some(header) = self.state.invalid_headers.get(&head) else { return Ok(None) };
2462
2463 match self.prepare_invalid_response(header.parent) {
2465 Ok(status) => Ok(Some(status)),
2466 Err(err) => {
2467 debug!(target: "engine::tree", %err, "Failed to prepare invalid response for ancestor check");
2468 Ok(Some(PayloadStatus::from_status(PayloadStatusEnum::Invalid {
2470 validation_error: PayloadValidationError::LinksToRejectedPayload.to_string(),
2471 })))
2472 }
2473 }
2474 }
2475
2476 fn validate_block(&self, block: &SealedBlock<N::Block>) -> Result<(), ConsensusError> {
2479 if let Err(e) = self.consensus.validate_header(block.sealed_header()) {
2480 error!(target: "engine::tree", ?block, "Failed to validate header {}: {e}", block.hash());
2481 return Err(e)
2482 }
2483
2484 if let Err(e) = self.consensus.validate_block_pre_execution(block) {
2485 error!(target: "engine::tree", ?block, "Failed to validate block {}: {e}", block.hash());
2486 return Err(e)
2487 }
2488
2489 Ok(())
2490 }
2491
2492 #[instrument(level = "debug", target = "engine::tree", skip(self))]
2494 fn try_connect_buffered_blocks(
2495 &mut self,
2496 parent: BlockNumHash,
2497 ) -> Result<(), InsertBlockFatalError> {
2498 let blocks = self.state.buffer.remove_block_with_children(&parent.hash);
2499
2500 if blocks.is_empty() {
2501 return Ok(())
2503 }
2504
2505 let now = Instant::now();
2506 let block_count = blocks.len();
2507 for child in blocks {
2508 let child_num_hash = child.num_hash();
2509 match self.insert_block(child) {
2510 Ok(res) => {
2511 debug!(target: "engine::tree", child =?child_num_hash, ?res, "connected buffered block");
2512 if self.is_any_sync_target(child_num_hash.hash) &&
2513 matches!(res, InsertPayloadOk::Inserted(BlockStatus::Valid))
2514 {
2515 debug!(target: "engine::tree", child =?child_num_hash, "connected sync target block");
2516 self.make_canonical(child_num_hash.hash)?;
2519 }
2520 }
2521 Err(err) => {
2522 if let InsertPayloadError::Block(err) = err {
2523 debug!(target: "engine::tree", ?err, "failed to connect buffered block to tree");
2524 if let Err(fatal) = self.on_insert_block_error(err) {
2525 warn!(target: "engine::tree", %fatal, "fatal error occurred while connecting buffered blocks");
2526 return Err(fatal)
2527 }
2528 }
2529 }
2530 }
2531 }
2532
2533 debug!(target: "engine::tree", elapsed = ?now.elapsed(), %block_count, "connected buffered blocks");
2534 Ok(())
2535 }
2536
2537 fn buffer_block(
2539 &mut self,
2540 block: SealedBlock<N::Block>,
2541 ) -> Result<(), InsertBlockError<N::Block>> {
2542 if let Err(err) = self.validate_block(&block) {
2543 return Err(InsertBlockError::consensus_error(err, block))
2544 }
2545 self.state.buffer.insert_block(block);
2546 Ok(())
2547 }
2548
2549 #[inline]
2554 const fn exceeds_backfill_run_threshold(&self, local_tip: u64, block: u64) -> bool {
2555 block > local_tip && block - local_tip > MIN_BLOCKS_FOR_PIPELINE_RUN
2556 }
2557
2558 #[inline]
2561 const fn distance_from_local_tip(&self, local_tip: u64, block: u64) -> Option<u64> {
2562 if block > local_tip {
2563 Some(block - local_tip)
2564 } else {
2565 None
2566 }
2567 }
2568
2569 const fn backfill_target_hash(&self, state: ForkchoiceState) -> B256 {
2577 if self.engine_kind.is_opstack() {
2578 state.head_block_hash
2579 } else {
2580 state.finalized_block_hash
2581 }
2582 }
2583
2584 fn backfill_sync_target(
2591 &self,
2592 canonical_tip_num: u64,
2593 target_block_number: u64,
2594 downloaded_block: Option<BlockNumHash>,
2595 ) -> Option<B256> {
2596 let state = self.state.forkchoice_state_tracker.sync_target_state()?;
2597 let target_hash = self.backfill_target_hash(state);
2598
2599 let exceeds_backfill_threshold = match downloaded_block.as_ref() {
2601 Some(downloaded_block) if downloaded_block.hash == target_hash => {
2603 self.exceeds_backfill_run_threshold(canonical_tip_num, downloaded_block.number)
2604 }
2605 _ => match self.state.buffer.block(&target_hash) {
2606 Some(buffered_target) => {
2608 self.exceeds_backfill_run_threshold(canonical_tip_num, buffered_target.number())
2609 }
2610 None => self.exceeds_backfill_run_threshold(canonical_tip_num, target_block_number),
2612 },
2613 };
2614
2615 if !exceeds_backfill_threshold {
2616 return None
2617 }
2618
2619 match self.provider.header_by_hash_or_number(target_hash.into()) {
2621 Err(err) => {
2622 warn!(target: "engine::tree", %err, "Failed to get backfill target block header");
2623 None
2624 }
2625 Ok(None) if !target_hash.is_zero() => Some(target_hash),
2627 Ok(None) => {
2628 debug!(target: "engine::tree", hash=?state.head_block_hash, "Setting head hash as an optimistic backfill target.");
2641 Some(state.head_block_hash)
2642 }
2643 Ok(Some(_)) => None,
2645 }
2646 }
2647
2648 fn find_disk_reorg(&self) -> ProviderResult<Option<u64>> {
2651 let mut canonical = self.state.tree_state.current_canonical_head;
2652 let mut persisted = self.persistence_state.last_persisted_block;
2653
2654 let parent_num_hash = |num_hash: NumHash| -> ProviderResult<NumHash> {
2655 Ok(self
2656 .sealed_header_by_hash(num_hash.hash)?
2657 .ok_or(ProviderError::BlockHashNotFound(num_hash.hash))?
2658 .parent_num_hash())
2659 };
2660
2661 while canonical.number > persisted.number {
2664 canonical = parent_num_hash(canonical)?;
2665 }
2666
2667 if canonical == persisted {
2669 return Ok(None);
2670 }
2671
2672 while persisted.number > canonical.number {
2678 persisted = parent_num_hash(persisted)?;
2679 }
2680
2681 debug_assert_eq!(persisted.number, canonical.number);
2682
2683 while persisted.hash != canonical.hash {
2685 canonical = parent_num_hash(canonical)?;
2686 persisted = parent_num_hash(persisted)?;
2687 }
2688
2689 debug!(target: "engine::tree", remove_above=persisted.number, "on-disk reorg detected");
2690
2691 Ok(Some(persisted.number))
2692 }
2693
2694 fn on_canonical_chain_update(&mut self, chain_update: NewCanonicalChain<N>) {
2698 trace!(target: "engine::tree", new_blocks = %chain_update.new_block_count(), reorged_blocks = %chain_update.reorged_block_count(), "applying new chain update");
2699 let start = Instant::now();
2700
2701 self.state.tree_state.set_canonical_head(chain_update.tip().num_hash());
2703
2704 let tip = chain_update.tip().clone_sealed_header();
2705 let notification = chain_update.to_chain_notification();
2706
2707 if let NewCanonicalChain::Reorg { new, old } = &chain_update {
2709 let new_first = new.first().map(|first| first.recovered_block().num_hash());
2710 let old_first = old.first().map(|first| first.recovered_block().num_hash());
2711 trace!(target: "engine::tree", ?new_first, ?old_first, "Reorg detected, new and old first blocks");
2712
2713 self.state.set_pending_sparse_trie_prune(false);
2714 self.update_reorg_metrics(old.len(), old_first);
2715 self.reinsert_reorged_blocks(new.clone());
2716 self.reinsert_reorged_blocks(old.clone());
2717 }
2718
2719 self.canonical_in_memory_state.update_chain(chain_update);
2721 self.canonical_in_memory_state.set_canonical_head(tip.clone());
2722
2723 self.metrics.tree.canonical_chain_height.set(tip.number() as f64);
2725
2726 self.canonical_in_memory_state.notify_canon_state(notification);
2728
2729 self.emit_event(ConsensusEngineEvent::CanonicalChainCommitted(
2731 Box::new(tip),
2732 start.elapsed(),
2733 ));
2734 }
2735
2736 fn update_reorg_metrics(&self, old_chain_length: usize, first_reorged_block: Option<NumHash>) {
2738 if let Some(first_reorged_block) = first_reorged_block.map(|block| block.number) {
2739 if let Some(finalized) = self.canonical_in_memory_state.get_finalized_num_hash() &&
2740 first_reorged_block <= finalized.number
2741 {
2742 self.metrics.tree.reorgs.finalized.increment(1);
2743 } else if let Some(safe) = self.canonical_in_memory_state.get_safe_num_hash() &&
2744 first_reorged_block <= safe.number
2745 {
2746 self.metrics.tree.reorgs.safe.increment(1);
2747 } else {
2748 self.metrics.tree.reorgs.head.increment(1);
2749 }
2750 } else {
2751 debug_unreachable!("Reorged chain doesn't have any blocks");
2752 }
2753 self.metrics.tree.latest_reorg_depth.set(old_chain_length as f64);
2754 }
2755
2756 fn reinsert_reorged_blocks(&mut self, new_chain: Vec<ExecutedBlock<N>>) {
2758 for block in new_chain {
2759 if self
2760 .state
2761 .tree_state
2762 .executed_block_by_hash(block.recovered_block().hash())
2763 .is_none()
2764 {
2765 trace!(target: "engine::tree", num=?block.recovered_block().number(), hash=?block.recovered_block().hash(), "Reinserting block into tree state");
2766 self.state.tree_state.insert_executed(block);
2767 }
2768 }
2769 }
2770
2771 fn on_disconnected_downloaded_block(
2776 &self,
2777 downloaded_block: BlockNumHash,
2778 missing_parent: BlockNumHash,
2779 head: BlockNumHash,
2780 ) -> Option<TreeEvent> {
2781 if let Some(target) =
2783 self.backfill_sync_target(head.number, missing_parent.number, Some(downloaded_block))
2784 {
2785 trace!(target: "engine::tree", %target, "triggering backfill on downloaded block");
2786 return Some(TreeEvent::BackfillAction(BackfillAction::Start(target.into())));
2787 }
2788
2789 let request = if let Some(distance) =
2799 self.distance_from_local_tip(head.number, missing_parent.number)
2800 {
2801 trace!(target: "engine::tree", %distance, missing=?missing_parent, "downloading missing parent block range");
2802 DownloadRequest::BlockRange(missing_parent.hash, distance)
2803 } else {
2804 trace!(target: "engine::tree", missing=?missing_parent, "downloading missing parent block");
2805 DownloadRequest::single_block(missing_parent.hash)
2808 };
2809
2810 Some(TreeEvent::Download(request))
2811 }
2812
2813 fn on_valid_downloaded_block(
2820 &mut self,
2821 block_num_hash: BlockNumHash,
2822 ) -> Result<Option<TreeEvent>, InsertBlockFatalError> {
2823 if let Some(sync_target) = self.state.forkchoice_state_tracker.sync_target_state() &&
2826 sync_target.contains(block_num_hash.hash)
2827 {
2828 debug!(target: "engine::tree", ?sync_target, "appended downloaded sync target block");
2829
2830 if sync_target.head_block_hash == block_num_hash.hash {
2831 return Ok(Some(TreeEvent::TreeAction(TreeAction::MakeCanonical {
2833 sync_target_head: block_num_hash.hash,
2834 })))
2835 }
2836
2837 self.make_canonical(block_num_hash.hash)?;
2841 self.try_connect_buffered_blocks(block_num_hash)?;
2842
2843 if self.state.tree_state.canonical_block_hash() != sync_target.head_block_hash {
2846 let target = self.lowest_buffered_ancestor_or(sync_target.head_block_hash);
2847 trace!(target: "engine::tree", %target, "sync target head not yet reached, downloading head block");
2848 return Ok(Some(TreeEvent::Download(DownloadRequest::single_block(target))))
2849 }
2850
2851 return Ok(None)
2852 }
2853 trace!(target: "engine::tree", "appended downloaded block");
2854 self.try_connect_buffered_blocks(block_num_hash)?;
2855 Ok(None)
2856 }
2857
2858 #[instrument(level = "debug", target = "engine::tree", skip_all, fields(block_hash = %block.hash(), block_num = %block.number()))]
2864 fn on_downloaded_block(
2865 &mut self,
2866 block: SealedBlock<N::Block>,
2867 ) -> Result<Option<TreeEvent>, InsertBlockFatalError> {
2868 let block_num_hash = block.num_hash();
2869 let lowest_buffered_ancestor = self.lowest_buffered_ancestor_or(block_num_hash.hash);
2870 if self.check_invalid_ancestor_with_head(lowest_buffered_ancestor, &block)?.is_some() {
2871 return Ok(None)
2872 }
2873
2874 if !self.backfill_sync_state.is_idle() {
2875 return Ok(None)
2876 }
2877
2878 match self.insert_block(block) {
2880 Ok(InsertPayloadOk::Inserted(BlockStatus::Valid)) => {
2881 return self.on_valid_downloaded_block(block_num_hash);
2882 }
2883 Ok(InsertPayloadOk::Inserted(BlockStatus::Disconnected { head, missing_ancestor })) => {
2884 return Ok(self.on_disconnected_downloaded_block(
2887 block_num_hash,
2888 missing_ancestor,
2889 head,
2890 ))
2891 }
2892 Ok(InsertPayloadOk::AlreadySeen(_)) => {
2893 trace!(target: "engine::tree", "downloaded block already executed");
2894 }
2895 Err(err) => {
2896 if let InsertPayloadError::Block(err) = err {
2897 debug!(target: "engine::tree", err=%err.kind(), "failed to insert downloaded block");
2898 if let Err(fatal) = self.on_insert_block_error(err) {
2899 warn!(target: "engine::tree", %fatal, "fatal error occurred while inserting downloaded block");
2900 return Err(fatal)
2901 }
2902 }
2903 }
2904 }
2905 Ok(None)
2906 }
2907
2908 fn insert_payload(
2917 &mut self,
2918 payload: T::ExecutionData,
2919 ) -> Result<InsertPayloadOk, InsertPayloadError<N::Block>> {
2920 self.insert_block_or_payload(
2921 payload.block_with_parent(),
2922 payload,
2923 |validator, payload, ctx| validator.validate_payload(payload, ctx),
2924 |this, payload| Ok(this.payload_validator.convert_payload_to_block(payload)?),
2925 )
2926 }
2927
2928 fn insert_block(
2929 &mut self,
2930 block: SealedBlock<N::Block>,
2931 ) -> Result<InsertPayloadOk, InsertPayloadError<N::Block>> {
2932 self.insert_block_or_payload(
2933 block.block_with_parent(),
2934 block,
2935 |validator, block, ctx| validator.validate_block(block, ctx),
2936 |_, block| Ok(block),
2937 )
2938 }
2939
2940 #[instrument(level = "debug", target = "engine::tree", skip_all, fields(?block_id))]
2957 fn insert_block_or_payload<Input, Err>(
2958 &mut self,
2959 block_id: BlockWithParent,
2960 input: Input,
2961 execute: impl FnOnce(&mut V, Input, TreeCtx<'_, N>) -> Result<ValidationOutput<N>, Err>,
2962 convert_to_block: impl FnOnce(&mut Self, Input) -> Result<SealedBlock<N::Block>, Err>,
2963 ) -> Result<InsertPayloadOk, Err>
2964 where
2965 Err: From<InsertBlockError<N::Block>>,
2966 {
2967 let block_insert_start = Instant::now();
2968 let block_num_hash = block_id.block;
2969 debug!(target: "engine::tree", block=?block_num_hash, parent = ?block_id.parent, "Inserting new block into tree");
2970
2971 if self.state.tree_state.contains_hash(&block_num_hash.hash) {
2973 convert_to_block(self, input)?;
2974 return Ok(InsertPayloadOk::AlreadySeen(BlockStatus::Valid));
2975 }
2976
2977 if block_num_hash.number <= self.persistence_state.last_persisted_block.number {
2980 match self.provider.sealed_header_by_hash(block_num_hash.hash) {
2981 Err(err) => {
2982 let block = convert_to_block(self, input)?;
2983 return Err(InsertBlockError::new(block, err.into()).into());
2984 }
2985 Ok(Some(_)) => {
2986 convert_to_block(self, input)?;
2987 return Ok(InsertPayloadOk::AlreadySeen(BlockStatus::Valid));
2988 }
2989 Ok(None) => {}
2990 }
2991 }
2992
2993 match self.state_provider_builder(block_id.parent) {
2995 Err(err) => {
2996 let block = convert_to_block(self, input)?;
2997 return Err(InsertBlockError::new(block, err.into()).into());
2998 }
2999 Ok(None) => {
3000 let block = convert_to_block(self, input)?;
3001
3002 let missing_ancestor = self
3005 .state
3006 .buffer
3007 .lowest_ancestor(&block.parent_hash())
3008 .map(|block| block.parent_num_hash())
3009 .unwrap_or_else(|| block.parent_num_hash());
3010
3011 self.state.buffer.insert_block(block);
3012
3013 return Ok(InsertPayloadOk::Inserted(BlockStatus::Disconnected {
3014 head: self.state.tree_state.current_canonical_head,
3015 missing_ancestor,
3016 }))
3017 }
3018 Ok(Some(_)) => {}
3019 }
3020
3021 let is_fork = block_id.block.number <= self.state.tree_state.current_canonical_head.number;
3026
3027 let ctx = TreeCtx::new(&mut self.state, &self.canonical_in_memory_state);
3028
3029 let start = Instant::now();
3030
3031 let ValidationOutput {
3032 executed_block: executed,
3033 execution_timing_stats: timing_stats,
3034 raw_bal,
3035 } = execute(&mut self.payload_validator, input, ctx)?;
3036
3037 if let Some(raw_bal) = raw_bal {
3038 let num_hash = executed.recovered_block().num_hash();
3039 if let Err(err) = self.provider.bal_store().insert(num_hash, raw_bal) {
3040 warn!(
3041 target: "engine::tree",
3042 ?num_hash,
3043 %err,
3044 "Failed to store validated block access list"
3045 );
3046 }
3047 }
3048
3049 if let Some(stats) = timing_stats {
3052 if let Some(threshold) = self.config.slow_block_threshold() {
3053 let total_duration = stats.execution_duration + stats.state_hash_duration;
3054 if total_duration > threshold {
3055 self.emit_event(ConsensusEngineEvent::SlowBlock(SlowBlockInfo {
3056 stats: stats.clone(),
3057 commit_duration: None,
3058 total_duration,
3059 }));
3060 }
3061 }
3062 self.execution_timing_stats.insert(executed.recovered_block().hash(), stats);
3063 }
3064
3065 if self.state.tree_state.canonical_block_hash() == executed.recovered_block().parent_hash()
3067 {
3068 debug!(target: "engine::tree", pending=?block_num_hash, "updating pending block");
3069 self.canonical_in_memory_state.set_pending_block(executed.clone());
3070 }
3071
3072 self.state.tree_state.insert_executed(executed.clone());
3073 self.metrics.engine.executed_blocks.set(self.state.tree_state.block_count() as f64);
3074
3075 let elapsed = start.elapsed();
3077 let engine_event = if is_fork {
3078 ConsensusEngineEvent::ForkBlockAdded(executed, elapsed)
3079 } else {
3080 ConsensusEngineEvent::CanonicalBlockAdded(executed, elapsed)
3081 };
3082 self.emit_event(EngineApiEvent::BeaconConsensus(engine_event));
3083
3084 self.metrics
3085 .engine
3086 .block_insert_total_duration
3087 .record(block_insert_start.elapsed().as_secs_f64());
3088 debug!(target: "engine::tree", block=?block_num_hash, "Finished inserting block");
3089 Ok(InsertPayloadOk::Inserted(BlockStatus::Valid))
3090 }
3091
3092 fn on_insert_block_error(
3098 &mut self,
3099 error: InsertBlockError<N::Block>,
3100 ) -> Result<PayloadStatus, InsertBlockFatalError> {
3101 let (block, error) = error.split();
3102
3103 let validation_err = error.ensure_validation_error()?;
3106
3107 warn!(
3111 target: "engine::tree",
3112 invalid_hash=%block.hash(),
3113 invalid_number=block.number(),
3114 %validation_err,
3115 "Invalid block error on new payload",
3116 );
3117 let latest_valid_hash = self.latest_valid_hash_for_invalid_payload(block.parent_hash())?;
3118
3119 let is_transient = match &validation_err {
3121 InsertBlockValidationError::Consensus(err) => self.consensus.is_transient_error(err),
3122 _ => false,
3123 };
3124 if is_transient {
3125 warn!(
3126 target: "engine::tree",
3127 invalid_hash=%block.hash(),
3128 invalid_number=block.number(),
3129 %validation_err,
3130 "Skipping invalid header cache insert for transient validation error",
3131 );
3132 } else {
3133 self.state.invalid_headers.insert(block.block_with_parent());
3134 }
3135 self.emit_event(EngineApiEvent::BeaconConsensus(ConsensusEngineEvent::InvalidBlock {
3136 block: Box::new(block),
3137 error: validation_err.to_string(),
3138 }));
3139
3140 Ok(PayloadStatus::new(
3141 PayloadStatusEnum::Invalid { validation_error: validation_err.to_string() },
3142 latest_valid_hash,
3143 ))
3144 }
3145
3146 fn on_new_payload_error(
3148 &mut self,
3149 error: NewPayloadError,
3150 payload_num_hash: NumHash,
3151 parent_hash: B256,
3152 ) -> ProviderResult<PayloadStatus> {
3153 error!(target: "engine::tree", payload=?payload_num_hash, %error, "Invalid payload");
3154 let latest_valid_hash =
3157 if error.is_block_hash_mismatch() || error.is_invalid_versioned_hashes() {
3158 None
3162 } else {
3163 self.latest_valid_hash_for_invalid_payload(parent_hash)?
3164 };
3165
3166 let status = PayloadStatusEnum::from(error);
3167 Ok(PayloadStatus::new(status, latest_valid_hash))
3168 }
3169
3170 pub fn find_canonical_header(
3172 &self,
3173 hash: B256,
3174 ) -> Result<Option<SealedHeader<N::BlockHeader>>, ProviderError> {
3175 let mut canonical = self.canonical_in_memory_state.header_by_hash(hash);
3176
3177 if canonical.is_none() {
3178 canonical = self.provider.header(hash)?.map(|header| SealedHeader::new(header, hash));
3179 }
3180
3181 Ok(canonical)
3182 }
3183
3184 fn update_finalized_block(
3186 &self,
3187 finalized_block_hash: B256,
3188 ) -> Result<(), OnForkChoiceUpdated> {
3189 if finalized_block_hash.is_zero() {
3190 return Ok(())
3191 }
3192
3193 match self.find_canonical_header(finalized_block_hash) {
3194 Ok(None) => {
3195 debug!(target: "engine::tree", "Finalized block not found in canonical chain");
3196 return Err(OnForkChoiceUpdated::invalid_state())
3198 }
3199 Ok(Some(finalized)) => {
3200 if Some(finalized.num_hash()) !=
3201 self.canonical_in_memory_state.get_finalized_num_hash()
3202 {
3203 let _ = self.persistence.save_finalized_block_number(finalized.number());
3206 self.canonical_in_memory_state.set_finalized(finalized.clone());
3207 self.metrics.tree.finalized_block_height.set(finalized.number() as f64);
3209 }
3210 }
3211 Err(err) => {
3212 error!(target: "engine::tree", %err, "Failed to fetch finalized block header");
3213 }
3214 }
3215
3216 Ok(())
3217 }
3218
3219 fn update_safe_block(&self, safe_block_hash: B256) -> Result<(), OnForkChoiceUpdated> {
3221 if safe_block_hash.is_zero() {
3222 return Ok(())
3223 }
3224
3225 match self.find_canonical_header(safe_block_hash) {
3226 Ok(None) => {
3227 debug!(target: "engine::tree", "Safe block not found in canonical chain");
3228 return Err(OnForkChoiceUpdated::invalid_state())
3230 }
3231 Ok(Some(safe)) => {
3232 if Some(safe.num_hash()) != self.canonical_in_memory_state.get_safe_num_hash() {
3233 let _ = self.persistence.save_safe_block_number(safe.number());
3236 self.canonical_in_memory_state.set_safe(safe.clone());
3237 self.metrics.tree.safe_block_height.set(safe.number() as f64);
3239 }
3240 }
3241 Err(err) => {
3242 error!(target: "engine::tree", %err, "Failed to fetch safe block header");
3243 }
3244 }
3245
3246 Ok(())
3247 }
3248
3249 fn ensure_consistent_forkchoice_state(
3258 &self,
3259 state: ForkchoiceState,
3260 ) -> Result<(), OnForkChoiceUpdated> {
3261 self.update_finalized_block(state.finalized_block_hash)?;
3267
3268 self.update_safe_block(state.safe_block_hash)
3274 }
3275
3276 fn process_payload_attributes(
3291 &mut self,
3292 attributes: T::PayloadAttributes,
3293 head: &N::BlockHeader,
3294 state: ForkchoiceState,
3295 ) -> OnForkChoiceUpdated {
3296 if let Err(err) =
3297 self.payload_validator.validate_payload_attributes_against_header(&attributes, head)
3298 {
3299 warn!(target: "engine::tree", %err, ?head, "Invalid payload attributes");
3300 return OnForkChoiceUpdated::invalid_payload_attributes()
3301 }
3302
3303 let cache = if self.config.share_execution_cache_with_payload_builder() {
3309 self.payload_validator.cache_for(state.head_block_hash)
3310 } else {
3311 None
3312 };
3313
3314 let state_root_handle = self.payload_validator.payload_state_root_handle_for(
3315 state.head_block_hash,
3316 head,
3317 attributes.timestamp(),
3318 &mut self.state,
3319 );
3320
3321 let pending_payload_id = self.payload_builder.send_new_payload(BuildNewPayload {
3324 parent_hash: state.head_block_hash,
3325 attributes,
3326 cache,
3327 state_root_handle,
3328 });
3329
3330 OnForkChoiceUpdated::updated_with_pending_payload_id(
3342 PayloadStatus::new(PayloadStatusEnum::Valid, Some(state.head_block_hash)),
3343 pending_payload_id,
3344 )
3345 }
3346
3347 pub(crate) fn remove_before(
3354 &mut self,
3355 upper_bound: BlockNumHash,
3356 finalized_hash: Option<B256>,
3357 ) -> ProviderResult<()> {
3358 let num = if let Some(hash) = finalized_hash {
3361 self.provider.block_number(hash)?.map(|number| BlockNumHash { number, hash })
3362 } else {
3363 None
3364 };
3365
3366 self.state.tree_state.remove_until(
3367 upper_bound,
3368 self.persistence_state.last_persisted_block.hash,
3369 num,
3370 );
3371 Ok(())
3372 }
3373
3374 pub fn state_provider_builder(
3379 &self,
3380 hash: B256,
3381 ) -> ProviderResult<Option<StateProviderBuilder<N, P>>>
3382 where
3383 P: BlockReader + StateProviderFactory + StateReader + Clone,
3384 {
3385 if let Some((historical, blocks)) = self.state.tree_state.blocks_by_hash(hash) {
3386 debug!(target: "engine::tree", %hash, %historical, "found canonical state for block in memory, creating provider builder");
3387 return Ok(Some(StateProviderBuilder::new(
3389 self.provider.clone(),
3390 historical,
3391 Some(blocks),
3392 )))
3393 }
3394
3395 if let Some(header) = self.provider.header(hash)? {
3397 debug!(target: "engine::tree", %hash, number = %header.number(), "found canonical state for block in database, creating provider builder");
3398 return Ok(Some(StateProviderBuilder::new(self.provider.clone(), hash, None)))
3401 }
3402
3403 debug!(target: "engine::tree", %hash, "no canonical state found for block");
3404 Ok(None)
3405 }
3406}
3407
3408#[derive(Debug)]
3410enum LoopEvent<T, N>
3411where
3412 N: NodePrimitives,
3413 T: PayloadTypes,
3414{
3415 EngineMessage(FromEngine<EngineApiRequest<T, N>, N::Block>),
3417 PersistenceComplete {
3419 result: PersistenceResult,
3421 start_time: Instant,
3423 },
3424 Disconnected,
3426}
3427
3428#[derive(Clone, Copy, Debug, Eq, PartialEq)]
3434pub enum BlockStatus {
3435 Valid,
3437 Disconnected {
3439 head: BlockNumHash,
3441 missing_ancestor: BlockNumHash,
3443 },
3444}
3445
3446#[derive(Clone, Copy, Debug, Eq, PartialEq)]
3451pub enum InsertPayloadOk {
3452 AlreadySeen(BlockStatus),
3454 Inserted(BlockStatus),
3456}
3457
3458#[derive(Debug, Clone, Copy)]
3460enum PersistTarget {
3461 Threshold,
3463 Head,
3465}
3466
3467#[derive(Debug, Clone, Copy, Default)]
3469pub struct CacheWaitDurations {
3470 pub execution_cache: Duration,
3472 pub sparse_trie: Duration,
3474}
3475
3476pub trait WaitForCaches {
3481 fn wait_for_caches(&self) -> CacheWaitDurations;
3485}