1use crate::engine::EngineApiKind;
4use alloy_eips::BlockNumHash;
5use alloy_primitives::{
6 map::{HashMap, HashSet},
7 BlockNumber, B256,
8};
9use reth_chain_state::{EthPrimitives, ExecutedBlock};
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#[derive(Debug, Default)]
24pub struct TreeState<N: NodePrimitives = EthPrimitives> {
25 pub(crate) blocks_by_hash: HashMap<B256, ExecutedBlock<N>>,
29 pub(crate) blocks_by_number: BTreeMap<BlockNumber, Vec<ExecutedBlock<N>>>,
35 pub(crate) parent_to_child: HashMap<B256, HashSet<B256>>,
37 pub(crate) current_canonical_head: BlockNumHash,
39 pub(crate) engine_kind: EngineApiKind,
41}
42
43impl<N: NodePrimitives> TreeState<N> {
44 pub(crate) fn new(current_canonical_head: BlockNumHash, engine_kind: EngineApiKind) -> Self {
46 Self {
47 blocks_by_hash: HashMap::default(),
48 blocks_by_number: BTreeMap::new(),
49 current_canonical_head,
50 parent_to_child: HashMap::default(),
51 engine_kind,
52 }
53 }
54
55 pub(crate) fn reset(&mut self, current_canonical_head: BlockNumHash) {
57 *self = Self::new(current_canonical_head, self.engine_kind);
58 }
59
60 pub(crate) fn block_count(&self) -> usize {
62 self.blocks_by_hash.len()
63 }
64
65 pub(crate) fn executed_block_by_hash(&self, hash: B256) -> Option<&ExecutedBlock<N>> {
67 self.blocks_by_hash.get(&hash)
68 }
69
70 pub(crate) fn sealed_header_by_hash(
72 &self,
73 hash: &B256,
74 ) -> Option<SealedHeader<N::BlockHeader>> {
75 self.blocks_by_hash.get(hash).map(|b| b.sealed_block().sealed_header().clone())
76 }
77
78 pub(crate) fn blocks_by_hash(&self, hash: B256) -> Option<(B256, Vec<ExecutedBlock<N>>)> {
84 let block = self.blocks_by_hash.get(&hash).cloned()?;
85 let mut parent_hash = block.recovered_block().parent_hash();
86 let mut blocks = vec![block];
87 while let Some(executed) = self.blocks_by_hash.get(&parent_hash) {
88 parent_hash = executed.recovered_block().parent_hash();
89 blocks.push(executed.clone());
90 }
91
92 Some((parent_hash, blocks))
93 }
94
95 pub(crate) fn insert_executed(&mut self, executed: ExecutedBlock<N>) {
97 let hash = executed.recovered_block().hash();
98 let parent_hash = executed.recovered_block().parent_hash();
99 let block_number = executed.recovered_block().number();
100
101 if self.blocks_by_hash.contains_key(&hash) {
102 return;
103 }
104
105 self.blocks_by_hash.insert(hash, executed.clone());
106
107 self.blocks_by_number.entry(block_number).or_default().push(executed);
108
109 self.parent_to_child.entry(parent_hash).or_default().insert(hash);
110 }
111
112 fn remove_by_hash(&mut self, hash: B256) -> Option<(ExecutedBlock<N>, HashSet<B256>)> {
118 let executed = self.blocks_by_hash.remove(&hash)?;
119
120 let parent_entry = self.parent_to_child.entry(executed.recovered_block().parent_hash());
122 if let hash_map::Entry::Occupied(mut entry) = parent_entry {
123 entry.get_mut().remove(&hash);
124
125 if entry.get().is_empty() {
126 entry.remove();
127 }
128 }
129
130 let children = self.parent_to_child.remove(&hash).unwrap_or_default();
132
133 let block_number_entry = self.blocks_by_number.entry(executed.recovered_block().number());
135 if let btree_map::Entry::Occupied(mut entry) = block_number_entry {
136 if let Some(index) = entry.get().iter().position(|b| b.recovered_block().hash() == hash)
138 {
139 entry.get_mut().swap_remove(index);
140
141 if entry.get().is_empty() {
143 entry.remove();
144 }
145 }
146 }
147
148 Some((executed, children))
149 }
150
151 pub(crate) fn is_canonical(&self, hash: B256) -> bool {
153 let mut current_block = self.current_canonical_head.hash;
154 if current_block == hash {
155 return true
156 }
157
158 while let Some(executed) = self.blocks_by_hash.get(¤t_block) {
159 current_block = executed.recovered_block().parent_hash();
160 if current_block == hash {
161 return true
162 }
163 }
164
165 false
166 }
167
168 pub(crate) fn remove_canonical_until(
171 &mut self,
172 upper_bound: BlockNumber,
173 last_persisted_hash: B256,
174 ) {
175 debug!(target: "engine::tree", ?upper_bound, ?last_persisted_hash, "Removing canonical blocks from the tree");
176
177 if !self.is_canonical(last_persisted_hash) {
180 return
181 }
182
183 let mut current_block = self.current_canonical_head.hash;
186 while let Some(executed) = self.blocks_by_hash.get(¤t_block) {
187 current_block = executed.recovered_block().parent_hash();
188 if executed.recovered_block().number() <= upper_bound {
189 let num_hash = executed.recovered_block().num_hash();
190 debug!(target: "engine::tree", ?num_hash, "Attempting to remove block walking back from the head");
191 self.remove_by_hash(executed.recovered_block().hash());
192 }
193 }
194 debug!(target: "engine::tree", ?upper_bound, ?last_persisted_hash, "Removed canonical blocks from the tree");
195 }
196
197 pub(crate) fn prune_finalized_sidechains(&mut self, finalized_num_hash: BlockNumHash) {
200 let BlockNumHash { number: finalized_num, hash: finalized_hash } = finalized_num_hash;
201
202 let blocks_to_remove = self
211 .blocks_by_number
212 .range((Bound::Unbounded, Bound::Excluded(finalized_num)))
213 .flat_map(|(_, blocks)| blocks.iter().map(|b| b.recovered_block().hash()))
214 .collect::<Vec<_>>();
215 for hash in blocks_to_remove {
216 if let Some((removed, _)) = self.remove_by_hash(hash) {
217 debug!(target: "engine::tree", num_hash=?removed.recovered_block().num_hash(), "Removed finalized sidechain block");
218 }
219 }
220
221 let mut blocks_to_remove = self.blocks_by_number.remove(&finalized_num).unwrap_or_default();
228
229 if let Some(position) =
231 blocks_to_remove.iter().position(|b| b.recovered_block().hash() == finalized_hash)
232 {
233 let finalized_block = blocks_to_remove.swap_remove(position);
234 self.blocks_by_number.insert(finalized_num, vec![finalized_block]);
235 }
236
237 let mut blocks_to_remove = blocks_to_remove
238 .into_iter()
239 .map(|e| e.recovered_block().hash())
240 .collect::<VecDeque<_>>();
241 while let Some(block) = blocks_to_remove.pop_front() {
242 if let Some((removed, children)) = self.remove_by_hash(block) {
243 debug!(target: "engine::tree", num_hash=?removed.recovered_block().num_hash(), "Removed finalized sidechain child block");
244 blocks_to_remove.extend(children);
245 }
246 }
247 }
248
249 pub(crate) fn remove_until(
260 &mut self,
261 upper_bound: BlockNumHash,
262 last_persisted_hash: B256,
263 finalized_num_hash: Option<BlockNumHash>,
264 ) {
265 debug!(target: "engine::tree", ?upper_bound, ?finalized_num_hash, "Removing blocks from the tree");
266
267 let finalized_num_hash = finalized_num_hash.map(|mut finalized| {
270 if upper_bound.number < finalized.number {
271 finalized = upper_bound;
272 debug!(target: "engine::tree", ?finalized, "Adjusted upper bound");
273 }
274 finalized
275 });
276
277 self.remove_canonical_until(upper_bound.number, last_persisted_hash);
285
286 if let Some(finalized_num_hash) = finalized_num_hash {
289 self.prune_finalized_sidechains(finalized_num_hash);
290 }
291 }
292
293 pub(crate) const fn set_canonical_head(&mut self, new_head: BlockNumHash) {
295 self.current_canonical_head = new_head;
296 }
297
298 pub(crate) const fn canonical_head(&self) -> &BlockNumHash {
300 &self.current_canonical_head
301 }
302
303 pub(crate) const fn canonical_block_hash(&self) -> B256 {
305 self.canonical_head().hash
306 }
307
308 pub(crate) const fn canonical_block_number(&self) -> BlockNumber {
310 self.canonical_head().number
311 }
312}
313
314#[cfg(test)]
315impl<N: NodePrimitives> TreeState<N> {
316 pub(crate) fn is_descendant(
320 &self,
321 first: BlockNumHash,
322 second: alloy_eips::eip1898::BlockWithParent,
323 ) -> bool {
324 if second.parent == first.hash {
327 return true
328 }
329
330 if second.block.number <= first.number {
333 return false
334 }
335
336 let Some(mut current_block) = self.blocks_by_hash.get(&second.parent) else {
338 return false
340 };
341
342 while current_block.recovered_block().number() > first.number + 1 {
343 let Some(block) =
344 self.blocks_by_hash.get(¤t_block.recovered_block().parent_hash())
345 else {
346 return false
348 };
349
350 current_block = block;
351 }
352
353 current_block.recovered_block().parent_hash() == first.hash
355 }
356}
357
358#[cfg(test)]
359mod tests {
360 use super::*;
361 use reth_chain_state::test_utils::TestBlockBuilder;
362
363 #[test]
364 fn test_tree_state_normal_descendant() {
365 let mut tree_state = TreeState::new(BlockNumHash::default(), EngineApiKind::Ethereum);
366 let blocks: Vec<_> = TestBlockBuilder::eth().get_executed_blocks(1..4).collect();
367
368 tree_state.insert_executed(blocks[0].clone());
369 assert!(tree_state.is_descendant(
370 blocks[0].recovered_block().num_hash(),
371 blocks[1].recovered_block().block_with_parent()
372 ));
373
374 tree_state.insert_executed(blocks[1].clone());
375
376 assert!(tree_state.is_descendant(
377 blocks[0].recovered_block().num_hash(),
378 blocks[2].recovered_block().block_with_parent()
379 ));
380 assert!(tree_state.is_descendant(
381 blocks[1].recovered_block().num_hash(),
382 blocks[2].recovered_block().block_with_parent()
383 ));
384 }
385
386 #[tokio::test]
387 async fn test_tree_state_insert_executed() {
388 let mut tree_state = TreeState::new(BlockNumHash::default(), EngineApiKind::Ethereum);
389 let blocks: Vec<_> = TestBlockBuilder::eth().get_executed_blocks(1..4).collect();
390
391 tree_state.insert_executed(blocks[0].clone());
392 tree_state.insert_executed(blocks[1].clone());
393
394 assert_eq!(
395 tree_state.parent_to_child.get(&blocks[0].recovered_block().hash()),
396 Some(&HashSet::from_iter([blocks[1].recovered_block().hash()]))
397 );
398
399 assert!(!tree_state.parent_to_child.contains_key(&blocks[1].recovered_block().hash()));
400
401 tree_state.insert_executed(blocks[2].clone());
402
403 assert_eq!(
404 tree_state.parent_to_child.get(&blocks[1].recovered_block().hash()),
405 Some(&HashSet::from_iter([blocks[2].recovered_block().hash()]))
406 );
407 assert!(tree_state.parent_to_child.contains_key(&blocks[1].recovered_block().hash()));
408
409 assert!(!tree_state.parent_to_child.contains_key(&blocks[2].recovered_block().hash()));
410 }
411
412 #[tokio::test]
413 async fn test_tree_state_insert_executed_with_reorg() {
414 let mut tree_state = TreeState::new(BlockNumHash::default(), EngineApiKind::Ethereum);
415 let mut test_block_builder = TestBlockBuilder::eth();
416 let blocks: Vec<_> = test_block_builder.get_executed_blocks(1..6).collect();
417
418 for block in &blocks {
419 tree_state.insert_executed(block.clone());
420 }
421 assert_eq!(tree_state.blocks_by_hash.len(), 5);
422
423 let fork_block_3 = test_block_builder
424 .get_executed_block_with_number(3, blocks[1].recovered_block().hash());
425 let fork_block_4 = test_block_builder
426 .get_executed_block_with_number(4, fork_block_3.recovered_block().hash());
427 let fork_block_5 = test_block_builder
428 .get_executed_block_with_number(5, fork_block_4.recovered_block().hash());
429
430 tree_state.insert_executed(fork_block_3.clone());
431 tree_state.insert_executed(fork_block_4.clone());
432 tree_state.insert_executed(fork_block_5.clone());
433
434 assert_eq!(tree_state.blocks_by_hash.len(), 8);
435 assert_eq!(tree_state.blocks_by_number[&3].len(), 2); assert_eq!(tree_state.parent_to_child[&blocks[1].recovered_block().hash()].len(), 2); tree_state.insert_executed(fork_block_4.clone());
440 assert_eq!(tree_state.blocks_by_hash.len(), 8);
441
442 assert!(tree_state.parent_to_child[&fork_block_3.recovered_block().hash()]
443 .contains(&fork_block_4.recovered_block().hash()));
444 assert!(tree_state.parent_to_child[&fork_block_4.recovered_block().hash()]
445 .contains(&fork_block_5.recovered_block().hash()));
446
447 assert_eq!(tree_state.blocks_by_number[&4].len(), 2);
448 assert_eq!(tree_state.blocks_by_number[&5].len(), 2);
449 }
450
451 #[tokio::test]
452 async fn test_tree_state_remove_before() {
453 let start_num_hash = BlockNumHash::default();
454 let mut tree_state = TreeState::new(start_num_hash, EngineApiKind::Ethereum);
455 let blocks: Vec<_> = TestBlockBuilder::eth().get_executed_blocks(1..6).collect();
456
457 for block in &blocks {
458 tree_state.insert_executed(block.clone());
459 }
460
461 let last = blocks.last().unwrap();
462
463 tree_state.set_canonical_head(last.recovered_block().num_hash());
465
466 tree_state.remove_until(
468 BlockNumHash::new(2, blocks[1].recovered_block().hash()),
469 start_num_hash.hash,
470 Some(blocks[1].recovered_block().num_hash()),
471 );
472
473 assert!(!tree_state.blocks_by_hash.contains_key(&blocks[0].recovered_block().hash()));
474 assert!(!tree_state.blocks_by_hash.contains_key(&blocks[1].recovered_block().hash()));
475 assert!(!tree_state.blocks_by_number.contains_key(&1));
476 assert!(!tree_state.blocks_by_number.contains_key(&2));
477
478 assert!(tree_state.blocks_by_hash.contains_key(&blocks[2].recovered_block().hash()));
479 assert!(tree_state.blocks_by_hash.contains_key(&blocks[3].recovered_block().hash()));
480 assert!(tree_state.blocks_by_hash.contains_key(&blocks[4].recovered_block().hash()));
481 assert!(tree_state.blocks_by_number.contains_key(&3));
482 assert!(tree_state.blocks_by_number.contains_key(&4));
483 assert!(tree_state.blocks_by_number.contains_key(&5));
484
485 assert!(!tree_state.parent_to_child.contains_key(&blocks[0].recovered_block().hash()));
486 assert!(!tree_state.parent_to_child.contains_key(&blocks[1].recovered_block().hash()));
487 assert!(tree_state.parent_to_child.contains_key(&blocks[2].recovered_block().hash()));
488 assert!(tree_state.parent_to_child.contains_key(&blocks[3].recovered_block().hash()));
489 assert!(!tree_state.parent_to_child.contains_key(&blocks[4].recovered_block().hash()));
490
491 assert_eq!(
492 tree_state.parent_to_child.get(&blocks[2].recovered_block().hash()),
493 Some(&HashSet::from_iter([blocks[3].recovered_block().hash()]))
494 );
495 assert_eq!(
496 tree_state.parent_to_child.get(&blocks[3].recovered_block().hash()),
497 Some(&HashSet::from_iter([blocks[4].recovered_block().hash()]))
498 );
499 }
500
501 #[tokio::test]
502 async fn test_tree_state_remove_before_finalized() {
503 let start_num_hash = BlockNumHash::default();
504 let mut tree_state = TreeState::new(start_num_hash, EngineApiKind::Ethereum);
505 let blocks: Vec<_> = TestBlockBuilder::eth().get_executed_blocks(1..6).collect();
506
507 for block in &blocks {
508 tree_state.insert_executed(block.clone());
509 }
510
511 let last = blocks.last().unwrap();
512
513 tree_state.set_canonical_head(last.recovered_block().num_hash());
515
516 tree_state.remove_until(
518 BlockNumHash::new(2, blocks[1].recovered_block().hash()),
519 start_num_hash.hash,
520 None,
521 );
522
523 assert!(!tree_state.blocks_by_hash.contains_key(&blocks[0].recovered_block().hash()));
524 assert!(!tree_state.blocks_by_hash.contains_key(&blocks[1].recovered_block().hash()));
525 assert!(!tree_state.blocks_by_number.contains_key(&1));
526 assert!(!tree_state.blocks_by_number.contains_key(&2));
527
528 assert!(tree_state.blocks_by_hash.contains_key(&blocks[2].recovered_block().hash()));
529 assert!(tree_state.blocks_by_hash.contains_key(&blocks[3].recovered_block().hash()));
530 assert!(tree_state.blocks_by_hash.contains_key(&blocks[4].recovered_block().hash()));
531 assert!(tree_state.blocks_by_number.contains_key(&3));
532 assert!(tree_state.blocks_by_number.contains_key(&4));
533 assert!(tree_state.blocks_by_number.contains_key(&5));
534
535 assert!(!tree_state.parent_to_child.contains_key(&blocks[0].recovered_block().hash()));
536 assert!(!tree_state.parent_to_child.contains_key(&blocks[1].recovered_block().hash()));
537 assert!(tree_state.parent_to_child.contains_key(&blocks[2].recovered_block().hash()));
538 assert!(tree_state.parent_to_child.contains_key(&blocks[3].recovered_block().hash()));
539 assert!(!tree_state.parent_to_child.contains_key(&blocks[4].recovered_block().hash()));
540
541 assert_eq!(
542 tree_state.parent_to_child.get(&blocks[2].recovered_block().hash()),
543 Some(&HashSet::from_iter([blocks[3].recovered_block().hash()]))
544 );
545 assert_eq!(
546 tree_state.parent_to_child.get(&blocks[3].recovered_block().hash()),
547 Some(&HashSet::from_iter([blocks[4].recovered_block().hash()]))
548 );
549 }
550
551 #[tokio::test]
552 async fn test_tree_state_remove_before_lower_finalized() {
553 let start_num_hash = BlockNumHash::default();
554 let mut tree_state = TreeState::new(start_num_hash, EngineApiKind::Ethereum);
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 tree_state.set_canonical_head(last.recovered_block().num_hash());
565
566 tree_state.remove_until(
568 BlockNumHash::new(2, blocks[1].recovered_block().hash()),
569 start_num_hash.hash,
570 Some(blocks[0].recovered_block().num_hash()),
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(&HashSet::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(&HashSet::from_iter([blocks[4].recovered_block().hash()]))
598 );
599 }
600}