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