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