Skip to main content

reth_engine_tree/tree/
block_buffer.rs

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