diff --git a/crates/engine/tree/src/tree/block_buffer.rs b/crates/engine/tree/src/tree/block_buffer.rs index 4a0fede28..8c421430a 100644 --- a/crates/engine/tree/src/tree/block_buffer.rs +++ b/crates/engine/tree/src/tree/block_buffer.rs @@ -2,8 +2,7 @@ use crate::tree::metrics::BlockBufferMetrics; use alloy_consensus::BlockHeader; use alloy_primitives::{BlockHash, BlockNumber}; use reth_primitives_traits::{Block, RecoveredBlock}; -use schnellru::{ByLength, LruMap}; -use std::collections::{BTreeMap, HashMap, HashSet}; +use std::collections::{BTreeMap, HashMap, HashSet, VecDeque}; /// Contains the tree of pending blocks that cannot be executed due to missing parent. /// It allows to store unconnected blocks for potential future inclusion. @@ -27,11 +26,11 @@ pub struct BlockBuffer { /// `BTreeMap` tracking the earliest blocks by block number. /// Used for removal of old blocks that precede finalization. pub(crate) earliest_blocks: BTreeMap>, - /// LRU used for tracing oldest inserted blocks that are going to be - /// first in line for evicting if `max_blocks` limit is hit. - /// - /// Used as counter of amount of blocks inside buffer. - pub(crate) lru: LruMap, + /// FIFO queue tracking block insertion order for eviction. + /// When the buffer reaches its capacity limit, the oldest block is evicted first. + pub(crate) block_queue: VecDeque, + /// Maximum number of blocks that can be stored in the buffer + pub(crate) max_blocks: usize, /// Various metrics for the block buffer. pub(crate) metrics: BlockBufferMetrics, } @@ -43,7 +42,8 @@ impl BlockBuffer { blocks: Default::default(), parent_to_child: Default::default(), earliest_blocks: Default::default(), - lru: LruMap::new(ByLength::new(limit)), + block_queue: VecDeque::default(), + max_blocks: limit as usize, metrics: Default::default(), } } @@ -70,28 +70,19 @@ impl BlockBuffer { self.earliest_blocks.entry(block.number()).or_default().insert(hash); self.blocks.insert(hash, block); - if let Some(evicted_hash) = self.insert_hash_and_get_evicted(hash) { - // evict the block if limit is hit - if let Some(evicted_block) = self.remove_block(&evicted_hash) { - // evict the block if limit is hit - self.remove_from_parent(evicted_block.parent_hash(), &evicted_hash); + // Add block to FIFO queue and handle eviction if needed + if self.block_queue.len() >= self.max_blocks { + // Evict oldest block if limit is hit + if let Some(evicted_hash) = self.block_queue.pop_front() { + if let Some(evicted_block) = self.remove_block(&evicted_hash) { + self.remove_from_parent(evicted_block.parent_hash(), &evicted_hash); + } } } + self.block_queue.push_back(hash); self.metrics.blocks.set(self.blocks.len() as f64); } - /// Inserts the hash and returns the oldest evicted hash if any. - fn insert_hash_and_get_evicted(&mut self, entry: BlockHash) -> Option { - let new = self.lru.peek(&entry).is_none(); - let evicted = if new && self.lru.limiter().max_length() as usize <= self.lru.len() { - self.lru.pop_oldest().map(|(k, ())| k) - } else { - None - }; - self.lru.get_or_insert(entry, || ()); - evicted - } - /// Removes the given block from the buffer and also all the children of the block. /// /// This is used to get all the blocks that are dependent on the block that is included. @@ -164,7 +155,7 @@ impl BlockBuffer { let block = self.blocks.remove(hash)?; self.remove_from_earliest_blocks(block.number(), hash); self.remove_from_parent(block.parent_hash(), hash); - self.lru.remove(hash); + self.block_queue.retain(|h| h != hash); Some(block) } @@ -213,7 +204,7 @@ mod tests { /// Assert that all buffer collections have the same data length. fn assert_buffer_lengths(buffer: &BlockBuffer, expected: usize) { assert_eq!(buffer.blocks.len(), expected); - assert_eq!(buffer.lru.len(), expected); + assert_eq!(buffer.block_queue.len(), expected); assert_eq!( buffer.parent_to_child.iter().fold(0, |acc, (_, hashes)| acc + hashes.len()), expected