Skip to main content

reth_engine_tree/tree/
state.rs

1//! Functionality related to tree state.
2
3use crate::engine::EngineApiKind;
4use alloy_eips::BlockNumHash;
5use alloy_primitives::{
6    map::{B256Map, B256Set},
7    BlockNumber, B256,
8};
9use reth_chain_state::{EthPrimitives, ExecutedBlock, LazyOverlay};
10use reth_primitives_traits::{AlloyBlockHeader, NodePrimitives, SealedHeader};
11use std::{
12    collections::{btree_map, hash_map, BTreeMap, VecDeque},
13    ops::Bound,
14};
15use tracing::debug;
16
17/// Keeps track of the state of the tree.
18///
19/// ## Invariants
20///
21/// - This only stores blocks that are connected to the canonical chain.
22/// - All executed blocks are valid and have been executed.
23#[derive(Debug, Default)]
24pub struct TreeState<N: NodePrimitives = EthPrimitives> {
25    /// __All__ unique executed blocks by block hash that are connected to the canonical chain.
26    ///
27    /// This includes blocks of all forks.
28    pub(crate) blocks_by_hash: B256Map<ExecutedBlock<N>>,
29    /// Executed blocks grouped by their respective block number.
30    ///
31    /// This maps unique block number to all known blocks for that height.
32    ///
33    /// Note: there can be multiple blocks at the same height due to forks.
34    pub(crate) blocks_by_number: BTreeMap<BlockNumber, Vec<ExecutedBlock<N>>>,
35    /// Map of any parent block hash to its children.
36    pub(crate) parent_to_child: B256Map<B256Set>,
37    /// Currently tracked canonical head of the chain.
38    pub(crate) current_canonical_head: BlockNumHash,
39    /// The engine API variant of this handler
40    pub(crate) engine_kind: EngineApiKind,
41    /// Pre-computed lazy overlay for the canonical head.
42    ///
43    /// This is optimistically prepared after the canonical head changes, so that
44    /// the next payload building on the canonical head can use it immediately
45    /// without recomputing.
46    pub(crate) cached_canonical_overlay: Option<PreparedCanonicalOverlay<N>>,
47}
48
49impl<N: NodePrimitives> TreeState<N> {
50    /// Returns a new, empty tree state that points to the given canonical head.
51    pub fn new(current_canonical_head: BlockNumHash, engine_kind: EngineApiKind) -> Self {
52        Self {
53            blocks_by_hash: B256Map::default(),
54            blocks_by_number: BTreeMap::new(),
55            current_canonical_head,
56            parent_to_child: B256Map::default(),
57            engine_kind,
58            cached_canonical_overlay: None,
59        }
60    }
61
62    /// Resets the state and points to the given canonical head.
63    pub fn reset(&mut self, current_canonical_head: BlockNumHash) {
64        *self = Self::new(current_canonical_head, self.engine_kind);
65    }
66
67    /// Returns the number of executed blocks stored.
68    pub fn block_count(&self) -> usize {
69        self.blocks_by_hash.len()
70    }
71
72    /// Returns the [`ExecutedBlock`] by hash.
73    pub fn executed_block_by_hash(&self, hash: B256) -> Option<&ExecutedBlock<N>> {
74        self.blocks_by_hash.get(&hash)
75    }
76
77    /// Returns `true` if a block with the given hash exists in memory.
78    pub fn contains_hash(&self, hash: &B256) -> bool {
79        self.blocks_by_hash.contains_key(hash)
80    }
81
82    /// Returns the sealed block header by hash.
83    pub fn sealed_header_by_hash(&self, hash: &B256) -> Option<SealedHeader<N::BlockHeader>> {
84        self.blocks_by_hash.get(hash).map(|b| b.sealed_block().sealed_header().clone())
85    }
86
87    /// Returns all available blocks for the given hash that lead back to the canonical chain, from
88    /// newest to oldest, and the parent hash of the oldest returned block. This parent hash is the
89    /// highest persisted block connected to this chain.
90    ///
91    /// Returns `None` if the block for the given hash is not found.
92    pub fn blocks_by_hash(&self, hash: B256) -> Option<(B256, Vec<ExecutedBlock<N>>)> {
93        let block = self.blocks_by_hash.get(&hash).cloned()?;
94        let mut parent_hash = block.recovered_block().parent_hash();
95        let mut blocks = vec![block];
96        while let Some(executed) = self.blocks_by_hash.get(&parent_hash) {
97            parent_hash = executed.recovered_block().parent_hash();
98            blocks.push(executed.clone());
99        }
100
101        Some((parent_hash, blocks))
102    }
103
104    /// Prepares a cached lazy overlay for the current canonical head.
105    ///
106    /// This should be called after the canonical head changes to optimistically
107    /// prepare the overlay for the next payload that will likely build on it.
108    ///
109    /// Returns a clone of the prepared overlay so the caller can spawn a background
110    /// task to trigger computation via [`LazyOverlay::get`] for the cached anchor.
111    /// This ensures the overlay is actually computed before the next payload arrives.
112    pub(crate) fn prepare_canonical_overlay(&mut self) -> Option<PreparedCanonicalOverlay<N>> {
113        let canonical_hash = self.current_canonical_head.hash;
114
115        // Get blocks leading to the canonical head
116        let Some((anchor_hash, blocks)) = self.blocks_by_hash(canonical_hash) else {
117            // Canonical head not in memory (persisted), no overlay needed
118            self.cached_canonical_overlay = None;
119            return None;
120        };
121
122        let num_blocks = blocks.len();
123        let prepared = PreparedCanonicalOverlay {
124            parent_hash: canonical_hash,
125            overlay: LazyOverlay::new(blocks),
126            anchor_hash,
127        };
128        self.cached_canonical_overlay = Some(prepared.clone());
129
130        debug!(
131            target: "engine::tree",
132            %canonical_hash,
133            %anchor_hash,
134            num_blocks,
135            "Prepared cached canonical overlay"
136        );
137
138        Some(prepared)
139    }
140
141    /// Returns the cached overlay if it matches the requested parent hash and anchor.
142    ///
143    /// Both parent hash and anchor hash must match to ensure the overlay is valid.
144    /// This prevents using a stale overlay after persistence has advanced the anchor.
145    pub fn get_cached_overlay(
146        &self,
147        parent_hash: B256,
148        expected_anchor: B256,
149    ) -> Option<&PreparedCanonicalOverlay<N>> {
150        self.cached_canonical_overlay.as_ref().filter(|cached| {
151            cached.parent_hash == parent_hash && cached.anchor_hash == expected_anchor
152        })
153    }
154
155    /// Invalidates the cached overlay.
156    ///
157    /// Should be called when the anchor changes (e.g., after persistence).
158    pub(crate) fn invalidate_cached_overlay(&mut self) {
159        self.cached_canonical_overlay = None;
160    }
161
162    /// Insert executed block into the state.
163    pub fn insert_executed(&mut self, executed: ExecutedBlock<N>) {
164        let hash = executed.recovered_block().hash();
165        let parent_hash = executed.recovered_block().parent_hash();
166        let block_number = executed.recovered_block().number();
167
168        if self.blocks_by_hash.contains_key(&hash) {
169            return;
170        }
171
172        self.blocks_by_hash.insert(hash, executed.clone());
173
174        self.blocks_by_number.entry(block_number).or_default().push(executed);
175
176        self.parent_to_child.entry(parent_hash).or_default().insert(hash);
177    }
178
179    /// Remove single executed block by its hash.
180    ///
181    /// ## Returns
182    ///
183    /// The removed block and the block hashes of its children.
184    fn remove_by_hash(&mut self, hash: B256) -> Option<(ExecutedBlock<N>, B256Set)> {
185        let executed = self.blocks_by_hash.remove(&hash)?;
186
187        // Remove this block from collection of children of its parent block.
188        let parent_entry = self.parent_to_child.entry(executed.recovered_block().parent_hash());
189        if let hash_map::Entry::Occupied(mut entry) = parent_entry {
190            entry.get_mut().remove(&hash);
191
192            if entry.get().is_empty() {
193                entry.remove();
194            }
195        }
196
197        // Remove point to children of this block.
198        let children = self.parent_to_child.remove(&hash).unwrap_or_default();
199
200        // Remove this block from `blocks_by_number`.
201        let block_number_entry = self.blocks_by_number.entry(executed.recovered_block().number());
202        if let btree_map::Entry::Occupied(mut entry) = block_number_entry {
203            // We have to find the index of the block since it exists in a vec
204            if let Some(index) = entry.get().iter().position(|b| b.recovered_block().hash() == hash)
205            {
206                entry.get_mut().swap_remove(index);
207
208                // If there are no blocks left then remove the entry for this block
209                if entry.get().is_empty() {
210                    entry.remove();
211                }
212            }
213        }
214
215        Some((executed, children))
216    }
217
218    /// Returns whether or not the hash is part of the canonical chain.
219    pub fn is_canonical(&self, hash: B256) -> bool {
220        let mut current_block = self.current_canonical_head.hash;
221        if current_block == hash {
222            return true
223        }
224
225        while let Some(executed) = self.blocks_by_hash.get(&current_block) {
226            current_block = executed.recovered_block().parent_hash();
227            if current_block == hash {
228                return true
229            }
230        }
231
232        false
233    }
234
235    /// Removes canonical blocks below the upper bound, only if the last persisted hash is
236    /// part of the canonical chain.
237    pub fn remove_canonical_until(&mut self, upper_bound: BlockNumber, last_persisted_hash: B256) {
238        debug!(target: "engine::tree", ?upper_bound, ?last_persisted_hash, "Removing canonical blocks from the tree");
239
240        // If the last persisted hash is not canonical, then we don't want to remove any canonical
241        // blocks yet.
242        if !self.is_canonical(last_persisted_hash) {
243            return
244        }
245
246        // First, let's walk back the canonical chain and remove canonical blocks lower than the
247        // upper bound
248        let mut current_block = self.current_canonical_head.hash;
249        while let Some(executed) = self.blocks_by_hash.get(&current_block) {
250            current_block = executed.recovered_block().parent_hash();
251            if executed.recovered_block().number() <= upper_bound {
252                let num_hash = executed.recovered_block().num_hash();
253                debug!(target: "engine::tree", ?num_hash, "Attempting to remove block walking back from the head");
254                self.remove_by_hash(executed.recovered_block().hash());
255            }
256        }
257        debug!(target: "engine::tree", ?upper_bound, ?last_persisted_hash, "Removed canonical blocks from the tree");
258    }
259
260    /// Removes all blocks that are below the finalized block, as well as removing non-canonical
261    /// sidechains that fork from below the finalized block.
262    pub fn prune_finalized_sidechains(&mut self, finalized_num_hash: BlockNumHash) {
263        let BlockNumHash { number: finalized_num, hash: finalized_hash } = finalized_num_hash;
264
265        // We remove disconnected sidechains in three steps:
266        // * first, remove everything with a block number __below__ the finalized block.
267        // * next, we populate a vec with parents __at__ the finalized block.
268        // * finally, we iterate through the vec, removing children until the vec is empty
269        // (BFS).
270
271        // We _exclude_ the finalized block because we will be dealing with the blocks __at__
272        // the finalized block later.
273        let blocks_to_remove = self
274            .blocks_by_number
275            .range((Bound::Unbounded, Bound::Excluded(finalized_num)))
276            .flat_map(|(_, blocks)| blocks.iter().map(|b| b.recovered_block().hash()))
277            .collect::<Vec<_>>();
278        for hash in blocks_to_remove {
279            if let Some((removed, _)) = self.remove_by_hash(hash) {
280                debug!(target: "engine::tree", num_hash=?removed.recovered_block().num_hash(), "Removed finalized sidechain block");
281            }
282        }
283
284        // The only block that should remain at the `finalized` number now, is the finalized
285        // block, if it exists.
286        //
287        // For all other blocks, we  first put their children into this vec.
288        // Then, we will iterate over them, removing them, adding their children, etc,
289        // until the vec is empty.
290        let mut blocks_to_remove = self.blocks_by_number.remove(&finalized_num).unwrap_or_default();
291
292        // re-insert the finalized hash if we removed it
293        if let Some(position) =
294            blocks_to_remove.iter().position(|b| b.recovered_block().hash() == finalized_hash)
295        {
296            let finalized_block = blocks_to_remove.swap_remove(position);
297            self.blocks_by_number.insert(finalized_num, vec![finalized_block]);
298        }
299
300        let mut blocks_to_remove = blocks_to_remove
301            .into_iter()
302            .map(|e| e.recovered_block().hash())
303            .collect::<VecDeque<_>>();
304        while let Some(block) = blocks_to_remove.pop_front() {
305            if let Some((removed, children)) = self.remove_by_hash(block) {
306                debug!(target: "engine::tree", num_hash=?removed.recovered_block().num_hash(), "Removed finalized sidechain child block");
307                blocks_to_remove.extend(children);
308            }
309        }
310    }
311
312    /// Remove all blocks up to __and including__ the given block number.
313    ///
314    /// If a finalized hash is provided, the only non-canonical blocks which will be removed are
315    /// those which have a fork point at or below the finalized hash.
316    ///
317    /// Canonical blocks below the upper bound will still be removed.
318    ///
319    /// NOTE: if the finalized block is greater than the upper bound, the only blocks that will be
320    /// removed are canonical blocks and sidechains that fork below the `upper_bound`. This is the
321    /// same behavior as if the `finalized_num` were `Some(upper_bound)`.
322    pub fn remove_until(
323        &mut self,
324        upper_bound: BlockNumHash,
325        last_persisted_hash: B256,
326        finalized_num_hash: Option<BlockNumHash>,
327    ) {
328        debug!(target: "engine::tree", ?upper_bound, ?finalized_num_hash, "Removing blocks from the tree");
329
330        // If the finalized num is ahead of the upper bound, and exists, we need to instead ensure
331        // that the only blocks removed, are canonical blocks less than the upper bound
332        let finalized_num_hash = finalized_num_hash.map(|mut finalized| {
333            if upper_bound.number < finalized.number {
334                finalized = upper_bound;
335                debug!(target: "engine::tree", ?finalized, "Adjusted upper bound");
336            }
337            finalized
338        });
339
340        // We want to do two things:
341        // * remove canonical blocks that are persisted
342        // * remove forks whose root are below the finalized block
343        // We can do this in 2 steps:
344        // * remove all canonical blocks below the upper bound
345        // * fetch the number of the finalized hash, removing any sidechains that are __below__ the
346        // finalized block
347        self.remove_canonical_until(upper_bound.number, last_persisted_hash);
348
349        // Now, we have removed canonical blocks (assuming the upper bound is above the finalized
350        // block) and only have sidechains below the finalized block.
351        if let Some(finalized_num_hash) = finalized_num_hash {
352            self.prune_finalized_sidechains(finalized_num_hash);
353        }
354
355        // Invalidate the cached overlay since blocks were removed and the anchor may have changed
356        self.invalidate_cached_overlay();
357    }
358
359    /// Updates the canonical head to the given block.
360    pub const fn set_canonical_head(&mut self, new_head: BlockNumHash) {
361        self.current_canonical_head = new_head;
362    }
363
364    /// Returns the tracked canonical head.
365    pub const fn canonical_head(&self) -> &BlockNumHash {
366        &self.current_canonical_head
367    }
368
369    /// Returns the block hash of the canonical head.
370    pub const fn canonical_block_hash(&self) -> B256 {
371        self.canonical_head().hash
372    }
373
374    /// Returns the block number of the canonical head.
375    pub const fn canonical_block_number(&self) -> BlockNumber {
376        self.canonical_head().number
377    }
378}
379
380#[cfg(test)]
381impl<N: NodePrimitives> TreeState<N> {
382    /// Determines if the second block is a descendant of the first block.
383    ///
384    /// If the two blocks are the same, this returns `false`.
385    pub fn is_descendant(
386        &self,
387        first: BlockNumHash,
388        second: alloy_eips::eip1898::BlockWithParent,
389    ) -> bool {
390        // If the second block's parent is the first block's hash, then it is a direct child
391        // and we can return early.
392        if second.parent == first.hash {
393            return true
394        }
395
396        // If the second block is lower than, or has the same block number, they are not
397        // descendants.
398        if second.block.number <= first.number {
399            return false
400        }
401
402        // iterate through parents of the second until we reach the number
403        let Some(mut current_block) = self.blocks_by_hash.get(&second.parent) else {
404            // If we can't find its parent in the tree, we can't continue, so return false
405            return false
406        };
407
408        while current_block.recovered_block().number() > first.number + 1 {
409            let Some(block) =
410                self.blocks_by_hash.get(&current_block.recovered_block().parent_hash())
411            else {
412                // If we can't find its parent in the tree, we can't continue, so return false
413                return false
414            };
415
416            current_block = block;
417        }
418
419        // Now the block numbers should be equal, so we compare hashes.
420        current_block.recovered_block().parent_hash() == first.hash
421    }
422}
423
424/// Pre-computed lazy overlay for the canonical head block.
425///
426/// This is prepared **optimistically** when the canonical head changes, allowing
427/// the next payload (which typically builds on the canonical head) to reuse
428/// the pre-computed overlay immediately without re-traversing in-memory blocks.
429///
430/// The overlay captures executed blocks from all in-memory blocks
431/// between the canonical head and the persisted anchor. When a new payload
432/// arrives building on the canonical head, this cached overlay can be used
433/// directly instead of calling `blocks_by_hash` again.
434///
435/// # Invalidation
436///
437/// The cached overlay is invalidated when:
438/// - Persistence completes (anchor changes)
439/// - The canonical head changes to a different block
440#[derive(Debug, Clone)]
441pub struct PreparedCanonicalOverlay<N: NodePrimitives = EthPrimitives> {
442    /// The block hash for which this overlay is prepared as a parent.
443    ///
444    /// When a payload arrives with this parent hash, the overlay can be reused.
445    pub parent_hash: B256,
446    /// The pre-computed lazy overlay containing executed blocks for the canonical segment.
447    ///
448    /// This is computed optimistically after `set_canonical_head` so subsequent payloads don't
449    /// need to walk the in-memory chain again.
450    pub overlay: LazyOverlay<N>,
451    /// The anchor hash (persisted ancestor) this overlay is based on.
452    ///
453    /// Used to verify the overlay is still valid (anchor hasn't changed due to persistence).
454    pub anchor_hash: B256,
455}
456
457#[cfg(test)]
458mod tests {
459    use super::*;
460    use reth_chain_state::test_utils::TestBlockBuilder;
461
462    #[test]
463    fn test_tree_state_normal_descendant() {
464        let mut tree_state = TreeState::new(BlockNumHash::default(), EngineApiKind::Ethereum);
465        let blocks: Vec<_> = TestBlockBuilder::eth().get_executed_blocks(1..4).collect();
466
467        tree_state.insert_executed(blocks[0].clone());
468        assert!(tree_state.is_descendant(
469            blocks[0].recovered_block().num_hash(),
470            blocks[1].recovered_block().block_with_parent()
471        ));
472
473        tree_state.insert_executed(blocks[1].clone());
474
475        assert!(tree_state.is_descendant(
476            blocks[0].recovered_block().num_hash(),
477            blocks[2].recovered_block().block_with_parent()
478        ));
479        assert!(tree_state.is_descendant(
480            blocks[1].recovered_block().num_hash(),
481            blocks[2].recovered_block().block_with_parent()
482        ));
483    }
484
485    #[tokio::test]
486    async fn test_tree_state_insert_executed() {
487        let mut tree_state = TreeState::new(BlockNumHash::default(), EngineApiKind::Ethereum);
488        let blocks: Vec<_> = TestBlockBuilder::eth().get_executed_blocks(1..4).collect();
489
490        tree_state.insert_executed(blocks[0].clone());
491        tree_state.insert_executed(blocks[1].clone());
492
493        assert_eq!(
494            tree_state.parent_to_child.get(&blocks[0].recovered_block().hash()),
495            Some(&B256Set::from_iter([blocks[1].recovered_block().hash()]))
496        );
497
498        assert!(!tree_state.parent_to_child.contains_key(&blocks[1].recovered_block().hash()));
499
500        tree_state.insert_executed(blocks[2].clone());
501
502        assert_eq!(
503            tree_state.parent_to_child.get(&blocks[1].recovered_block().hash()),
504            Some(&B256Set::from_iter([blocks[2].recovered_block().hash()]))
505        );
506        assert!(tree_state.parent_to_child.contains_key(&blocks[1].recovered_block().hash()));
507
508        assert!(!tree_state.parent_to_child.contains_key(&blocks[2].recovered_block().hash()));
509    }
510
511    #[tokio::test]
512    async fn test_tree_state_insert_executed_with_reorg() {
513        let mut tree_state = TreeState::new(BlockNumHash::default(), EngineApiKind::Ethereum);
514        let mut test_block_builder = TestBlockBuilder::eth();
515        let blocks: Vec<_> = test_block_builder.get_executed_blocks(1..6).collect();
516
517        for block in &blocks {
518            tree_state.insert_executed(block.clone());
519        }
520        assert_eq!(tree_state.blocks_by_hash.len(), 5);
521
522        let fork_block_3 = test_block_builder
523            .get_executed_block_with_number(3, blocks[1].recovered_block().hash());
524        let fork_block_4 = test_block_builder
525            .get_executed_block_with_number(4, fork_block_3.recovered_block().hash());
526        let fork_block_5 = test_block_builder
527            .get_executed_block_with_number(5, fork_block_4.recovered_block().hash());
528
529        tree_state.insert_executed(fork_block_3.clone());
530        tree_state.insert_executed(fork_block_4.clone());
531        tree_state.insert_executed(fork_block_5.clone());
532
533        assert_eq!(tree_state.blocks_by_hash.len(), 8);
534        assert_eq!(tree_state.blocks_by_number[&3].len(), 2); // two blocks at height 3 (original and fork)
535        assert_eq!(tree_state.parent_to_child[&blocks[1].recovered_block().hash()].len(), 2); // block 2 should have two children
536
537        // verify that we can insert the same block again without issues
538        tree_state.insert_executed(fork_block_4.clone());
539        assert_eq!(tree_state.blocks_by_hash.len(), 8);
540
541        assert!(tree_state.parent_to_child[&fork_block_3.recovered_block().hash()]
542            .contains(&fork_block_4.recovered_block().hash()));
543        assert!(tree_state.parent_to_child[&fork_block_4.recovered_block().hash()]
544            .contains(&fork_block_5.recovered_block().hash()));
545
546        assert_eq!(tree_state.blocks_by_number[&4].len(), 2);
547        assert_eq!(tree_state.blocks_by_number[&5].len(), 2);
548    }
549
550    #[tokio::test]
551    async fn test_tree_state_remove_before() {
552        let start_num_hash = BlockNumHash::default();
553        let mut tree_state = TreeState::new(start_num_hash, EngineApiKind::Ethereum);
554        let blocks: Vec<_> = TestBlockBuilder::eth().get_executed_blocks(1..6).collect();
555
556        for block in &blocks {
557            tree_state.insert_executed(block.clone());
558        }
559
560        let last = blocks.last().unwrap();
561
562        // set the canonical head
563        tree_state.set_canonical_head(last.recovered_block().num_hash());
564
565        // inclusive bound, so we should remove anything up to and including 2
566        tree_state.remove_until(
567            BlockNumHash::new(2, blocks[1].recovered_block().hash()),
568            start_num_hash.hash,
569            Some(blocks[1].recovered_block().num_hash()),
570        );
571
572        assert!(!tree_state.blocks_by_hash.contains_key(&blocks[0].recovered_block().hash()));
573        assert!(!tree_state.blocks_by_hash.contains_key(&blocks[1].recovered_block().hash()));
574        assert!(!tree_state.blocks_by_number.contains_key(&1));
575        assert!(!tree_state.blocks_by_number.contains_key(&2));
576
577        assert!(tree_state.blocks_by_hash.contains_key(&blocks[2].recovered_block().hash()));
578        assert!(tree_state.blocks_by_hash.contains_key(&blocks[3].recovered_block().hash()));
579        assert!(tree_state.blocks_by_hash.contains_key(&blocks[4].recovered_block().hash()));
580        assert!(tree_state.blocks_by_number.contains_key(&3));
581        assert!(tree_state.blocks_by_number.contains_key(&4));
582        assert!(tree_state.blocks_by_number.contains_key(&5));
583
584        assert!(!tree_state.parent_to_child.contains_key(&blocks[0].recovered_block().hash()));
585        assert!(!tree_state.parent_to_child.contains_key(&blocks[1].recovered_block().hash()));
586        assert!(tree_state.parent_to_child.contains_key(&blocks[2].recovered_block().hash()));
587        assert!(tree_state.parent_to_child.contains_key(&blocks[3].recovered_block().hash()));
588        assert!(!tree_state.parent_to_child.contains_key(&blocks[4].recovered_block().hash()));
589
590        assert_eq!(
591            tree_state.parent_to_child.get(&blocks[2].recovered_block().hash()),
592            Some(&B256Set::from_iter([blocks[3].recovered_block().hash()]))
593        );
594        assert_eq!(
595            tree_state.parent_to_child.get(&blocks[3].recovered_block().hash()),
596            Some(&B256Set::from_iter([blocks[4].recovered_block().hash()]))
597        );
598    }
599
600    #[tokio::test]
601    async fn test_tree_state_remove_before_finalized() {
602        let start_num_hash = BlockNumHash::default();
603        let mut tree_state = TreeState::new(start_num_hash, EngineApiKind::Ethereum);
604        let blocks: Vec<_> = TestBlockBuilder::eth().get_executed_blocks(1..6).collect();
605
606        for block in &blocks {
607            tree_state.insert_executed(block.clone());
608        }
609
610        let last = blocks.last().unwrap();
611
612        // set the canonical head
613        tree_state.set_canonical_head(last.recovered_block().num_hash());
614
615        // we should still remove everything up to and including 2
616        tree_state.remove_until(
617            BlockNumHash::new(2, blocks[1].recovered_block().hash()),
618            start_num_hash.hash,
619            None,
620        );
621
622        assert!(!tree_state.blocks_by_hash.contains_key(&blocks[0].recovered_block().hash()));
623        assert!(!tree_state.blocks_by_hash.contains_key(&blocks[1].recovered_block().hash()));
624        assert!(!tree_state.blocks_by_number.contains_key(&1));
625        assert!(!tree_state.blocks_by_number.contains_key(&2));
626
627        assert!(tree_state.blocks_by_hash.contains_key(&blocks[2].recovered_block().hash()));
628        assert!(tree_state.blocks_by_hash.contains_key(&blocks[3].recovered_block().hash()));
629        assert!(tree_state.blocks_by_hash.contains_key(&blocks[4].recovered_block().hash()));
630        assert!(tree_state.blocks_by_number.contains_key(&3));
631        assert!(tree_state.blocks_by_number.contains_key(&4));
632        assert!(tree_state.blocks_by_number.contains_key(&5));
633
634        assert!(!tree_state.parent_to_child.contains_key(&blocks[0].recovered_block().hash()));
635        assert!(!tree_state.parent_to_child.contains_key(&blocks[1].recovered_block().hash()));
636        assert!(tree_state.parent_to_child.contains_key(&blocks[2].recovered_block().hash()));
637        assert!(tree_state.parent_to_child.contains_key(&blocks[3].recovered_block().hash()));
638        assert!(!tree_state.parent_to_child.contains_key(&blocks[4].recovered_block().hash()));
639
640        assert_eq!(
641            tree_state.parent_to_child.get(&blocks[2].recovered_block().hash()),
642            Some(&B256Set::from_iter([blocks[3].recovered_block().hash()]))
643        );
644        assert_eq!(
645            tree_state.parent_to_child.get(&blocks[3].recovered_block().hash()),
646            Some(&B256Set::from_iter([blocks[4].recovered_block().hash()]))
647        );
648    }
649
650    #[tokio::test]
651    async fn test_tree_state_remove_before_lower_finalized() {
652        let start_num_hash = BlockNumHash::default();
653        let mut tree_state = TreeState::new(start_num_hash, EngineApiKind::Ethereum);
654        let blocks: Vec<_> = TestBlockBuilder::eth().get_executed_blocks(1..6).collect();
655
656        for block in &blocks {
657            tree_state.insert_executed(block.clone());
658        }
659
660        let last = blocks.last().unwrap();
661
662        // set the canonical head
663        tree_state.set_canonical_head(last.recovered_block().num_hash());
664
665        // we have no forks so we should still remove anything up to and including 2
666        tree_state.remove_until(
667            BlockNumHash::new(2, blocks[1].recovered_block().hash()),
668            start_num_hash.hash,
669            Some(blocks[0].recovered_block().num_hash()),
670        );
671
672        assert!(!tree_state.blocks_by_hash.contains_key(&blocks[0].recovered_block().hash()));
673        assert!(!tree_state.blocks_by_hash.contains_key(&blocks[1].recovered_block().hash()));
674        assert!(!tree_state.blocks_by_number.contains_key(&1));
675        assert!(!tree_state.blocks_by_number.contains_key(&2));
676
677        assert!(tree_state.blocks_by_hash.contains_key(&blocks[2].recovered_block().hash()));
678        assert!(tree_state.blocks_by_hash.contains_key(&blocks[3].recovered_block().hash()));
679        assert!(tree_state.blocks_by_hash.contains_key(&blocks[4].recovered_block().hash()));
680        assert!(tree_state.blocks_by_number.contains_key(&3));
681        assert!(tree_state.blocks_by_number.contains_key(&4));
682        assert!(tree_state.blocks_by_number.contains_key(&5));
683
684        assert!(!tree_state.parent_to_child.contains_key(&blocks[0].recovered_block().hash()));
685        assert!(!tree_state.parent_to_child.contains_key(&blocks[1].recovered_block().hash()));
686        assert!(tree_state.parent_to_child.contains_key(&blocks[2].recovered_block().hash()));
687        assert!(tree_state.parent_to_child.contains_key(&blocks[3].recovered_block().hash()));
688        assert!(!tree_state.parent_to_child.contains_key(&blocks[4].recovered_block().hash()));
689
690        assert_eq!(
691            tree_state.parent_to_child.get(&blocks[2].recovered_block().hash()),
692            Some(&B256Set::from_iter([blocks[3].recovered_block().hash()]))
693        );
694        assert_eq!(
695            tree_state.parent_to_child.get(&blocks[3].recovered_block().hash()),
696            Some(&B256Set::from_iter([blocks[4].recovered_block().hash()]))
697        );
698    }
699}