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