Skip to main content

reth_engine_tree/tree/
block_buffer.rs

1use crate::tree::metrics::BlockBufferMetrics;
2use alloy_consensus::BlockHeader;
3use alloy_primitives::{BlockHash, BlockNumber};
4use indexmap::IndexSet;
5use reth_network_p2p::full_block::SealedBlockWithAccessList;
6use reth_primitives_traits::{Block, SealedBlock};
7use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
8
9/// Contains the tree of pending blocks that cannot be executed due to missing parent.
10/// It allows to store unconnected blocks for potential future inclusion.
11///
12/// The buffer has three main functionalities:
13/// * [`BlockBuffer::insert_block`] for inserting blocks inside the buffer.
14/// * [`BlockBuffer::remove_block_with_children`] for connecting blocks if the parent gets received
15///   and inserted.
16/// * [`BlockBuffer::remove_old_blocks`] to remove old blocks that precede the finalized number.
17///
18/// Note: Buffer is limited by number of blocks that it can contain and eviction of the block
19/// is done in FIFO order (oldest inserted block is evicted first).
20#[derive(Debug)]
21pub struct BlockBuffer<B: Block> {
22    /// All blocks in the buffer stored by their block hash.
23    pub(crate) blocks: HashMap<BlockHash, SealedBlockWithAccessList<B>>,
24    /// Map of any parent block hash (even the ones not currently in the buffer)
25    /// to the buffered children.
26    /// Allows connecting buffered blocks by parent.
27    pub(crate) parent_to_child: HashMap<BlockHash, IndexSet<BlockHash>>,
28    /// `BTreeMap` tracking the earliest blocks by block number.
29    /// Used for removal of old blocks that precede finalization.
30    pub(crate) earliest_blocks: BTreeMap<BlockNumber, HashSet<BlockHash>>,
31    /// FIFO queue tracking block insertion order for eviction.
32    /// When the buffer reaches its capacity limit, the oldest block is evicted first.
33    pub(crate) block_queue: VecDeque<BlockHash>,
34    /// Maximum number of blocks that can be stored in the buffer
35    pub(crate) max_blocks: usize,
36    /// Various metrics for the block buffer.
37    pub(crate) metrics: BlockBufferMetrics,
38}
39
40impl<B: Block> BlockBuffer<B> {
41    /// Create new buffer with max limit of blocks
42    pub fn new(limit: u32) -> Self {
43        Self {
44            blocks: Default::default(),
45            parent_to_child: Default::default(),
46            earliest_blocks: Default::default(),
47            block_queue: VecDeque::default(),
48            max_blocks: limit as usize,
49            metrics: Default::default(),
50        }
51    }
52
53    /// Return reference to the requested block.
54    pub fn block(&self, hash: &BlockHash) -> Option<&SealedBlock<B>> {
55        self.blocks.get(hash).map(|block| &**block)
56    }
57
58    /// Return a reference to the lowest ancestor of the given block in the buffer.
59    pub fn lowest_ancestor(&self, hash: &BlockHash) -> Option<&SealedBlock<B>> {
60        let mut current_block = self.blocks.get(hash)?;
61        while let Some(parent) = self.blocks.get(&current_block.parent_hash()) {
62            current_block = parent;
63        }
64        Some(current_block)
65    }
66
67    /// Insert a correct block inside the buffer.
68    pub fn insert_block(&mut self, block: SealedBlockWithAccessList<B>) {
69        let hash = block.hash();
70
71        match self.blocks.entry(hash) {
72            std::collections::hash_map::Entry::Occupied(mut entry) => {
73                // a duplicate that includes access list data is preferred over one without
74                if entry.get().data().is_none() && block.data().is_some() {
75                    entry.insert(block);
76                }
77                return
78            }
79            std::collections::hash_map::Entry::Vacant(entry) => {
80                self.parent_to_child.entry(block.parent_hash()).or_default().insert(hash);
81                self.earliest_blocks.entry(block.number()).or_default().insert(hash);
82                entry.insert(block);
83            }
84        };
85
86        // Add block to FIFO queue and handle eviction if needed
87        if self.block_queue.len() >= self.max_blocks {
88            // Evict oldest block if limit is hit
89            if let Some(evicted_hash) = self.block_queue.pop_front() {
90                self.remove_block(&evicted_hash);
91            }
92        }
93        self.block_queue.push_back(hash);
94        self.metrics.blocks.set(self.blocks.len() as f64);
95    }
96
97    /// Removes the given block from the buffer and also all the children of the block.
98    ///
99    /// This is used to get all the blocks that are dependent on the block that is included.
100    ///
101    /// Note: that order of returned blocks is important and the blocks with lower block number
102    /// in the chain will come first so that they can be executed in the correct order.
103    pub fn remove_block_with_children(
104        &mut self,
105        parent_hash: &BlockHash,
106    ) -> Vec<SealedBlockWithAccessList<B>> {
107        let removed = self
108            .remove_block(parent_hash)
109            .into_iter()
110            .chain(self.remove_children(vec![*parent_hash]))
111            .collect();
112        self.metrics.blocks.set(self.blocks.len() as f64);
113        removed
114    }
115
116    /// Discard all blocks that precede block number from the buffer.
117    pub fn remove_old_blocks(&mut self, block_number: BlockNumber) {
118        let mut block_hashes_to_remove = Vec::new();
119
120        // discard all blocks that are before the finalized number.
121        while let Some(entry) = self.earliest_blocks.first_entry() {
122            if *entry.key() > block_number {
123                break
124            }
125            let block_hashes = entry.remove();
126            block_hashes_to_remove.extend(block_hashes);
127        }
128
129        // remove from other collections.
130        for block_hash in &block_hashes_to_remove {
131            // It's fine to call
132            self.remove_block(block_hash);
133        }
134
135        self.remove_children(block_hashes_to_remove);
136        self.metrics.blocks.set(self.blocks.len() as f64);
137    }
138
139    /// Remove block entry
140    fn remove_from_earliest_blocks(&mut self, number: BlockNumber, hash: &BlockHash) {
141        if let Some(entry) = self.earliest_blocks.get_mut(&number) {
142            entry.remove(hash);
143            if entry.is_empty() {
144                self.earliest_blocks.remove(&number);
145            }
146        }
147    }
148
149    /// Remove from parent child connection. This method does not remove children.
150    fn remove_from_parent(&mut self, parent_hash: BlockHash, hash: &BlockHash) {
151        // remove from parent to child connection, but only for this block parent.
152        if let Some(entry) = self.parent_to_child.get_mut(&parent_hash) {
153            entry.swap_remove(hash);
154            // if set is empty remove block entry.
155            if entry.is_empty() {
156                self.parent_to_child.remove(&parent_hash);
157            }
158        }
159    }
160
161    /// Removes block from inner collections.
162    /// This method will only remove the block if it's present inside `self.blocks`.
163    /// The block might be missing from other collections, the method will only ensure that it has
164    /// been removed.
165    fn remove_block(&mut self, hash: &BlockHash) -> Option<SealedBlockWithAccessList<B>> {
166        let block = self.blocks.remove(hash)?;
167        self.remove_from_earliest_blocks(block.number(), hash);
168        self.remove_from_parent(block.parent_hash(), hash);
169        self.block_queue.retain(|h| h != hash);
170        Some(block)
171    }
172
173    /// Remove all children and their descendants for the given blocks and return them.
174    fn remove_children(
175        &mut self,
176        parent_hashes: Vec<BlockHash>,
177    ) -> Vec<SealedBlockWithAccessList<B>> {
178        // remove all parent child connection and all the child children blocks that are connected
179        // to the discarded parent blocks.
180        let mut remove_parent_children = parent_hashes;
181        let mut removed_blocks = Vec::new();
182        while let Some(parent_hash) = remove_parent_children.pop() {
183            // get this child blocks children and add them to the remove list.
184            if let Some(parent_children) = self.parent_to_child.remove(&parent_hash) {
185                // remove child from buffer
186                for child_hash in &parent_children {
187                    if let Some(block) = self.remove_block(child_hash) {
188                        removed_blocks.push(block);
189                    }
190                }
191                remove_parent_children.extend(parent_children);
192            }
193        }
194        removed_blocks
195    }
196}
197
198#[cfg(test)]
199mod tests {
200    use super::*;
201    use alloy_eip7928::bal::RawBal;
202    use alloy_eips::BlockNumHash;
203    use alloy_primitives::{BlockHash, Bytes};
204    use reth_testing_utils::generators::{self, random_block, BlockParams, Rng};
205    use std::collections::HashMap;
206
207    /// Create random block with specified number and parent hash.
208    fn create_block<R: Rng>(
209        rng: &mut R,
210        number: u64,
211        parent: BlockHash,
212    ) -> SealedBlock<reth_ethereum_primitives::Block> {
213        random_block(rng, number, BlockParams { parent: Some(parent), ..Default::default() })
214    }
215
216    /// Assert that all buffer collections have the same data length.
217    fn assert_buffer_lengths<B: Block>(buffer: &BlockBuffer<B>, expected: usize) {
218        assert_eq!(buffer.blocks.len(), expected);
219        assert_eq!(buffer.block_queue.len(), expected);
220        assert_eq!(
221            buffer.parent_to_child.iter().fold(0, |acc, (_, hashes)| acc + hashes.len()),
222            expected
223        );
224        assert_eq!(
225            buffer.earliest_blocks.iter().fold(0, |acc, (_, hashes)| acc + hashes.len()),
226            expected
227        );
228    }
229
230    /// Assert that the block was removed from all buffer collections.
231    fn assert_block_removal<B: Block>(
232        buffer: &BlockBuffer<B>,
233        block: &SealedBlock<reth_ethereum_primitives::Block>,
234    ) {
235        assert!(!buffer.blocks.contains_key(&block.hash()));
236        assert!(buffer
237            .parent_to_child
238            .get(&block.parent_hash)
239            .and_then(|p| p.get(&block.hash()))
240            .is_none());
241        assert!(buffer
242            .earliest_blocks
243            .get(&block.number)
244            .and_then(|hashes| hashes.get(&block.hash()))
245            .is_none());
246    }
247
248    #[test]
249    fn simple_insertion() {
250        let mut rng = generators::rng();
251        let parent = rng.random();
252        let block1 = create_block(&mut rng, 10, parent);
253        let mut buffer = BlockBuffer::new(3);
254
255        buffer.insert_block(block1.clone().into());
256        assert_buffer_lengths(&buffer, 1);
257        assert_eq!(buffer.block(&block1.hash()), Some(&block1));
258    }
259
260    /// Creates a raw access list holding an empty RLP list.
261    fn raw_bal() -> RawBal {
262        RawBal::from(Bytes::from_static(&[alloy_rlp::EMPTY_LIST_CODE]))
263    }
264
265    #[test]
266    fn preserves_access_list_for_buffered_blocks() {
267        let mut rng = generators::rng();
268
269        let access_list = raw_bal();
270        let parent = rng.random();
271        let block = create_block(&mut rng, 10, parent);
272
273        let mut buffer = BlockBuffer::new(1);
274        buffer
275            .insert_block(SealedBlockWithAccessList::new(block.clone(), Some(access_list.clone())));
276
277        let blocks = buffer.remove_block_with_children(&parent);
278        assert_eq!(blocks.len(), 1);
279        assert_eq!(&*blocks[0], &block);
280        assert_eq!(blocks[0].data().as_ref(), Some(&access_list));
281    }
282
283    #[test]
284    fn updates_buffered_duplicate_with_access_list() {
285        let mut rng = generators::rng();
286
287        let access_list = raw_bal();
288        let parent = rng.random();
289        let block = create_block(&mut rng, 10, parent);
290
291        let mut buffer = BlockBuffer::new(1);
292        buffer.insert_block(block.clone().into());
293        buffer
294            .insert_block(SealedBlockWithAccessList::new(block.clone(), Some(access_list.clone())));
295
296        let blocks = buffer.remove_block_with_children(&parent);
297        assert_eq!(blocks.len(), 1);
298        assert_eq!(&*blocks[0], &block);
299        assert_eq!(blocks[0].data().as_ref(), Some(&access_list));
300    }
301
302    #[test]
303    fn take_entire_chain_of_children() {
304        let mut rng = generators::rng();
305
306        let main_parent_hash = rng.random();
307        let block1 = create_block(&mut rng, 10, main_parent_hash);
308        let block2 = create_block(&mut rng, 11, block1.hash());
309        let block3 = create_block(&mut rng, 12, block2.hash());
310        let parent4 = rng.random();
311        let block4 = create_block(&mut rng, 14, parent4);
312
313        let mut buffer = BlockBuffer::new(5);
314
315        buffer.insert_block(block1.clone().into());
316        buffer.insert_block(block2.clone().into());
317        buffer.insert_block(block3.clone().into());
318        buffer.insert_block(block4.clone().into());
319
320        assert_buffer_lengths(&buffer, 4);
321        assert_eq!(buffer.block(&block4.hash()), Some(&block4));
322        assert_eq!(buffer.block(&block2.hash()), Some(&block2));
323        assert_eq!(buffer.block(&main_parent_hash), None);
324
325        assert_eq!(buffer.lowest_ancestor(&block4.hash()), Some(&block4));
326        assert_eq!(buffer.lowest_ancestor(&block3.hash()), Some(&block1));
327        assert_eq!(buffer.lowest_ancestor(&block1.hash()), Some(&block1));
328        assert_eq!(
329            buffer
330                .remove_block_with_children(&main_parent_hash)
331                .into_iter()
332                .map(|b| b.split().0)
333                .collect::<Vec<_>>(),
334            vec![block1, block2, block3]
335        );
336        assert_buffer_lengths(&buffer, 1);
337    }
338
339    #[test]
340    fn take_all_multi_level_children() {
341        let mut rng = generators::rng();
342
343        let main_parent_hash = rng.random();
344        let block1 = create_block(&mut rng, 10, main_parent_hash);
345        let block2 = create_block(&mut rng, 11, block1.hash());
346        let block3 = create_block(&mut rng, 11, block1.hash());
347        let block4 = create_block(&mut rng, 12, block2.hash());
348
349        let mut buffer = BlockBuffer::new(5);
350
351        buffer.insert_block(block1.clone().into());
352        buffer.insert_block(block2.clone().into());
353        buffer.insert_block(block3.clone().into());
354        buffer.insert_block(block4.clone().into());
355
356        assert_buffer_lengths(&buffer, 4);
357        assert_eq!(
358            buffer
359                .remove_block_with_children(&main_parent_hash)
360                .into_iter()
361                .map(|b| (b.hash(), b.split().0))
362                .collect::<HashMap<_, _>>(),
363            HashMap::from([
364                (block1.hash(), block1),
365                (block2.hash(), block2),
366                (block3.hash(), block3),
367                (block4.hash(), block4)
368            ])
369        );
370        assert_buffer_lengths(&buffer, 0);
371    }
372
373    #[test]
374    fn take_block_with_children() {
375        let mut rng = generators::rng();
376
377        let main_parent = BlockNumHash::new(9, rng.random());
378        let block1 = create_block(&mut rng, 10, main_parent.hash);
379        let block2 = create_block(&mut rng, 11, block1.hash());
380        let block3 = create_block(&mut rng, 11, block1.hash());
381        let block4 = create_block(&mut rng, 12, block2.hash());
382
383        let mut buffer = BlockBuffer::new(5);
384
385        buffer.insert_block(block1.clone().into());
386        buffer.insert_block(block2.clone().into());
387        buffer.insert_block(block3.clone().into());
388        buffer.insert_block(block4.clone().into());
389
390        assert_buffer_lengths(&buffer, 4);
391        assert_eq!(
392            buffer
393                .remove_block_with_children(&block1.hash())
394                .into_iter()
395                .map(|b| (b.hash(), b.split().0))
396                .collect::<HashMap<_, _>>(),
397            HashMap::from([
398                (block1.hash(), block1),
399                (block2.hash(), block2),
400                (block3.hash(), block3),
401                (block4.hash(), block4)
402            ])
403        );
404        assert_buffer_lengths(&buffer, 0);
405    }
406
407    #[test]
408    fn remove_chain_of_children() {
409        let mut rng = generators::rng();
410
411        let main_parent = BlockNumHash::new(9, rng.random());
412        let block1 = create_block(&mut rng, 10, main_parent.hash);
413        let block2 = create_block(&mut rng, 11, block1.hash());
414        let block3 = create_block(&mut rng, 12, block2.hash());
415        let parent4 = rng.random();
416        let block4 = create_block(&mut rng, 14, parent4);
417
418        let mut buffer = BlockBuffer::new(5);
419
420        buffer.insert_block(block1.clone().into());
421        buffer.insert_block(block2.into());
422        buffer.insert_block(block3.into());
423        buffer.insert_block(block4.into());
424
425        assert_buffer_lengths(&buffer, 4);
426        buffer.remove_old_blocks(block1.number);
427        assert_buffer_lengths(&buffer, 1);
428    }
429
430    #[test]
431    fn remove_all_multi_level_children() {
432        let mut rng = generators::rng();
433
434        let main_parent = BlockNumHash::new(9, rng.random());
435        let block1 = create_block(&mut rng, 10, main_parent.hash);
436        let block2 = create_block(&mut rng, 11, block1.hash());
437        let block3 = create_block(&mut rng, 11, block1.hash());
438        let block4 = create_block(&mut rng, 12, block2.hash());
439
440        let mut buffer = BlockBuffer::new(5);
441
442        buffer.insert_block(block1.clone().into());
443        buffer.insert_block(block2.into());
444        buffer.insert_block(block3.into());
445        buffer.insert_block(block4.into());
446
447        assert_buffer_lengths(&buffer, 4);
448        buffer.remove_old_blocks(block1.number);
449        assert_buffer_lengths(&buffer, 0);
450    }
451
452    #[test]
453    fn remove_multi_chains() {
454        let mut rng = generators::rng();
455
456        let main_parent = BlockNumHash::new(9, rng.random());
457        let block1 = create_block(&mut rng, 10, main_parent.hash);
458        let block1a = create_block(&mut rng, 10, main_parent.hash);
459        let block2 = create_block(&mut rng, 11, block1.hash());
460        let block2a = create_block(&mut rng, 11, block1.hash());
461        let random_parent1 = rng.random();
462        let random_block1 = create_block(&mut rng, 10, random_parent1);
463        let random_parent2 = rng.random();
464        let random_block2 = create_block(&mut rng, 11, random_parent2);
465        let random_parent3 = rng.random();
466        let random_block3 = create_block(&mut rng, 12, random_parent3);
467
468        let mut buffer = BlockBuffer::new(10);
469
470        buffer.insert_block(block1.clone().into());
471        buffer.insert_block(block1a.clone().into());
472        buffer.insert_block(block2.clone().into());
473        buffer.insert_block(block2a.clone().into());
474        buffer.insert_block(random_block1.clone().into());
475        buffer.insert_block(random_block2.clone().into());
476        buffer.insert_block(random_block3.clone().into());
477
478        // check that random blocks are their own ancestor, and that chains have proper ancestors
479        assert_eq!(buffer.lowest_ancestor(&random_block1.hash()), Some(&random_block1));
480        assert_eq!(buffer.lowest_ancestor(&random_block2.hash()), Some(&random_block2));
481        assert_eq!(buffer.lowest_ancestor(&random_block3.hash()), Some(&random_block3));
482
483        // descendants have ancestors
484        assert_eq!(buffer.lowest_ancestor(&block2a.hash()), Some(&block1));
485        assert_eq!(buffer.lowest_ancestor(&block2.hash()), Some(&block1));
486
487        // roots are themselves
488        assert_eq!(buffer.lowest_ancestor(&block1a.hash()), Some(&block1a));
489        assert_eq!(buffer.lowest_ancestor(&block1.hash()), Some(&block1));
490
491        assert_buffer_lengths(&buffer, 7);
492        buffer.remove_old_blocks(10);
493        assert_buffer_lengths(&buffer, 2);
494    }
495
496    #[test]
497    fn evict_with_gap() {
498        let mut rng = generators::rng();
499
500        let main_parent = BlockNumHash::new(9, rng.random());
501        let block1 = create_block(&mut rng, 10, main_parent.hash);
502        let block2 = create_block(&mut rng, 11, block1.hash());
503        let block3 = create_block(&mut rng, 12, block2.hash());
504        let parent4 = rng.random();
505        let block4 = create_block(&mut rng, 13, parent4);
506
507        let mut buffer = BlockBuffer::new(3);
508
509        buffer.insert_block(block1.clone().into());
510        buffer.insert_block(block2.clone().into());
511        buffer.insert_block(block3.clone().into());
512
513        // pre-eviction block1 is the root
514        assert_eq!(buffer.lowest_ancestor(&block3.hash()), Some(&block1));
515        assert_eq!(buffer.lowest_ancestor(&block2.hash()), Some(&block1));
516        assert_eq!(buffer.lowest_ancestor(&block1.hash()), Some(&block1));
517
518        buffer.insert_block(block4.clone().into());
519
520        assert_eq!(buffer.lowest_ancestor(&block4.hash()), Some(&block4));
521
522        // block1 gets evicted
523        assert_block_removal(&buffer, &block1);
524
525        // check lowest ancestor results post eviction
526        assert_eq!(buffer.lowest_ancestor(&block3.hash()), Some(&block2));
527        assert_eq!(buffer.lowest_ancestor(&block2.hash()), Some(&block2));
528        assert_eq!(buffer.lowest_ancestor(&block1.hash()), None);
529
530        assert_buffer_lengths(&buffer, 3);
531    }
532
533    #[test]
534    fn simple_eviction() {
535        let mut rng = generators::rng();
536
537        let main_parent = BlockNumHash::new(9, rng.random());
538        let block1 = create_block(&mut rng, 10, main_parent.hash);
539        let block2 = create_block(&mut rng, 11, block1.hash());
540        let block3 = create_block(&mut rng, 12, block2.hash());
541        let parent4 = rng.random();
542        let block4 = create_block(&mut rng, 13, parent4);
543
544        let mut buffer = BlockBuffer::new(3);
545
546        buffer.insert_block(block1.clone().into());
547        buffer.insert_block(block2.into());
548        buffer.insert_block(block3.into());
549        buffer.insert_block(block4.into());
550
551        // block3 gets evicted
552        assert_block_removal(&buffer, &block1);
553
554        assert_buffer_lengths(&buffer, 3);
555    }
556
557    #[test]
558    fn eviction_parent_child_cleanup() {
559        let mut rng = generators::rng();
560
561        let main_parent = BlockNumHash::new(9, rng.random());
562        let block1 = create_block(&mut rng, 10, main_parent.hash);
563        let block2 = create_block(&mut rng, 11, block1.hash());
564        // Unrelated block to trigger eviction
565        let unrelated_parent = rng.random();
566        let unrelated_block = create_block(&mut rng, 12, unrelated_parent);
567
568        // Capacity 2 so third insert evicts the oldest (block1)
569        let mut buffer = BlockBuffer::new(2);
570
571        buffer.insert_block(block1.clone().into());
572        buffer.insert_block(block2.clone().into());
573
574        // Pre-eviction: parent_to_child contains main_parent -> {block1}, block1 -> {block2}
575        assert!(buffer
576            .parent_to_child
577            .get(&main_parent.hash)
578            .and_then(|s| s.get(&block1.hash()))
579            .is_some());
580        assert!(buffer
581            .parent_to_child
582            .get(&block1.hash())
583            .and_then(|s| s.get(&block2.hash()))
584            .is_some());
585
586        // Insert unrelated block to evict block1
587        buffer.insert_block(unrelated_block.into());
588
589        // Evicted block1 should be fully removed from collections
590        assert_block_removal(&buffer, &block1);
591
592        // Cleanup: parent_to_child must no longer have (main_parent -> block1)
593        assert!(buffer
594            .parent_to_child
595            .get(&main_parent.hash)
596            .and_then(|s| s.get(&block1.hash()))
597            .is_none());
598
599        // But the mapping (block1 -> block2) must remain so descendants can still be tracked
600        assert!(buffer
601            .parent_to_child
602            .get(&block1.hash())
603            .and_then(|s| s.get(&block2.hash()))
604            .is_some());
605
606        // And lowest ancestor for block2 becomes itself after its parent is evicted
607        assert_eq!(buffer.lowest_ancestor(&block2.hash()), Some(&block2));
608    }
609}