mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 19:09:54 +00:00
refactor(tree): block buffer (#5879)
This commit is contained in:
@ -1,42 +1,38 @@
|
|||||||
|
use crate::metrics::BlockBufferMetrics;
|
||||||
use lru::LruCache;
|
use lru::LruCache;
|
||||||
use reth_primitives::{BlockHash, BlockNumHash, BlockNumber, SealedBlockWithSenders};
|
use reth_primitives::{BlockHash, BlockNumber, SealedBlockWithSenders};
|
||||||
use std::{
|
use std::{
|
||||||
collections::{btree_map::Entry, hash_map, BTreeMap, HashMap, HashSet},
|
collections::{btree_map, hash_map, BTreeMap, HashMap, HashSet},
|
||||||
num::NonZeroUsize,
|
num::NonZeroUsize,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::metrics::BlockBufferMetrics;
|
/// Contains the tree of pending blocks that cannot be executed due to missing parent.
|
||||||
/// Type that contains blocks by number and hash.
|
/// It allows to store unconnected blocks for potential future inclusion.
|
||||||
pub type BufferedBlocks = BTreeMap<BlockNumber, HashMap<BlockHash, SealedBlockWithSenders>>;
|
|
||||||
|
|
||||||
/// Contains the Tree of pending blocks that are not executed but buffered
|
|
||||||
/// It allows us to store unconnected blocks for potential inclusion.
|
|
||||||
///
|
///
|
||||||
/// It has three main functionality:
|
/// The buffer has three main functionalities:
|
||||||
/// * [BlockBuffer::insert_block] for inserting blocks inside the buffer.
|
/// * [BlockBuffer::insert_block] for inserting blocks inside the buffer.
|
||||||
/// * [BlockBuffer::remove_with_children] for connecting blocks if the parent gets received and
|
/// * [BlockBuffer::remove_block_with_children] for connecting blocks if the parent gets received
|
||||||
/// inserted.
|
/// and inserted.
|
||||||
/// * [BlockBuffer::clean_old_blocks] to clear old blocks that are below finalized line.
|
/// * [BlockBuffer::remove_old_blocks] to remove old blocks that precede the finalized number.
|
||||||
///
|
///
|
||||||
/// Note: Buffer is limited by number of blocks that it can contain and eviction of the block
|
/// Note: Buffer is limited by number of blocks that it can contain and eviction of the block
|
||||||
/// is done by last recently used block.
|
/// is done by last recently used block.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct BlockBuffer {
|
pub struct BlockBuffer {
|
||||||
/// Blocks ordered by block number inside the BTreeMap.
|
/// All blocks in the buffer stored by their block hash.
|
||||||
///
|
pub(crate) blocks: HashMap<BlockHash, SealedBlockWithSenders>,
|
||||||
/// Note: BTreeMap is used so that we can remove the finalized old blocks
|
/// Map of any parent block hash (even the ones not currently in the buffer)
|
||||||
/// from the buffer
|
/// to the buffered children.
|
||||||
pub(crate) blocks: BufferedBlocks,
|
/// Allows connecting buffered blocks by parent.
|
||||||
/// Needed for removal of the blocks. and to connect the potential unconnected block
|
pub(crate) parent_to_child: HashMap<BlockHash, HashSet<BlockHash>>,
|
||||||
/// to the connected one.
|
/// BTreeMap tracking the earliest blocks by block number.
|
||||||
pub(crate) parent_to_child: HashMap<BlockHash, HashSet<BlockNumHash>>,
|
/// Used for removal of old blocks that precede finalization.
|
||||||
/// Helper map for fetching the block num from the block hash.
|
pub(crate) earliest_blocks: BTreeMap<BlockNumber, HashSet<BlockHash>>,
|
||||||
pub(crate) hash_to_num: HashMap<BlockHash, BlockNumber>,
|
|
||||||
/// LRU used for tracing oldest inserted blocks that are going to be
|
/// LRU used for tracing oldest inserted blocks that are going to be
|
||||||
/// first in line for evicting if `max_blocks` limit is hit.
|
/// first in line for evicting if `max_blocks` limit is hit.
|
||||||
///
|
///
|
||||||
/// Used as counter of amount of blocks inside buffer.
|
/// Used as counter of amount of blocks inside buffer.
|
||||||
pub(crate) lru: LruCache<BlockNumHash, ()>,
|
pub(crate) lru: LruCache<BlockHash, ()>,
|
||||||
/// Various metrics for the block buffer.
|
/// Various metrics for the block buffer.
|
||||||
pub(crate) metrics: BlockBufferMetrics,
|
pub(crate) metrics: BlockBufferMetrics,
|
||||||
}
|
}
|
||||||
@ -47,30 +43,47 @@ impl BlockBuffer {
|
|||||||
Self {
|
Self {
|
||||||
blocks: Default::default(),
|
blocks: Default::default(),
|
||||||
parent_to_child: Default::default(),
|
parent_to_child: Default::default(),
|
||||||
hash_to_num: Default::default(),
|
earliest_blocks: Default::default(),
|
||||||
lru: LruCache::new(NonZeroUsize::new(limit).unwrap()),
|
lru: LruCache::new(NonZeroUsize::new(limit).unwrap()),
|
||||||
metrics: Default::default(),
|
metrics: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return reference to buffered blocks
|
||||||
|
pub fn blocks(&self) -> &HashMap<BlockHash, SealedBlockWithSenders> {
|
||||||
|
&self.blocks
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return reference to the requested block.
|
||||||
|
pub fn block(&self, hash: &BlockHash) -> Option<&SealedBlockWithSenders> {
|
||||||
|
self.blocks.get(hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a reference to the lowest ancestor of the given block in the buffer.
|
||||||
|
pub fn lowest_ancestor(&self, hash: &BlockHash) -> Option<&SealedBlockWithSenders> {
|
||||||
|
let mut current_block = self.blocks.get(hash)?;
|
||||||
|
while let Some(parent) = self.blocks.get(¤t_block.parent_hash) {
|
||||||
|
current_block = parent;
|
||||||
|
}
|
||||||
|
Some(current_block)
|
||||||
|
}
|
||||||
|
|
||||||
/// Insert a correct block inside the buffer.
|
/// Insert a correct block inside the buffer.
|
||||||
pub fn insert_block(&mut self, block: SealedBlockWithSenders) {
|
pub fn insert_block(&mut self, block: SealedBlockWithSenders) {
|
||||||
let num_hash = block.num_hash();
|
let hash = block.hash;
|
||||||
|
|
||||||
self.parent_to_child.entry(block.parent_hash).or_default().insert(block.num_hash());
|
self.parent_to_child.entry(block.parent_hash).or_default().insert(hash);
|
||||||
self.hash_to_num.insert(block.hash, block.number);
|
self.earliest_blocks.entry(block.number).or_default().insert(hash);
|
||||||
self.blocks.entry(block.number).or_default().insert(block.hash, block);
|
self.blocks.insert(hash, block);
|
||||||
|
|
||||||
if let Some((evicted_num_hash, _)) =
|
if let Some((evicted_hash, _)) = self.lru.push(hash, ()).filter(|(b, _)| *b != hash) {
|
||||||
self.lru.push(num_hash, ()).filter(|(b, _)| *b != num_hash)
|
|
||||||
{
|
|
||||||
// evict the block if limit is hit
|
// evict the block if limit is hit
|
||||||
if let Some(evicted_block) = self.remove_from_blocks(&evicted_num_hash) {
|
if let Some(evicted_block) = self.remove_block(&evicted_hash) {
|
||||||
// evict the block if limit is hit
|
// evict the block if limit is hit
|
||||||
self.remove_from_parent(evicted_block.parent_hash, &evicted_num_hash);
|
self.remove_from_parent(evicted_block.parent_hash, &evicted_hash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.metrics.blocks.set(self.len() as f64);
|
self.metrics.blocks.set(self.blocks.len() as f64);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes the given block from the buffer and also all the children of the block.
|
/// Removes the given block from the buffer and also all the children of the block.
|
||||||
@ -79,93 +92,59 @@ impl BlockBuffer {
|
|||||||
///
|
///
|
||||||
/// Note: that order of returned blocks is important and the blocks with lower block number
|
/// Note: that order of returned blocks is important and the blocks with lower block number
|
||||||
/// in the chain will come first so that they can be executed in the correct order.
|
/// in the chain will come first so that they can be executed in the correct order.
|
||||||
pub fn remove_with_children(&mut self, parent: BlockNumHash) -> Vec<SealedBlockWithSenders> {
|
pub fn remove_block_with_children(
|
||||||
|
&mut self,
|
||||||
|
parent_hash: &BlockHash,
|
||||||
|
) -> Vec<SealedBlockWithSenders> {
|
||||||
// remove parent block if present
|
// remove parent block if present
|
||||||
let mut taken = Vec::new();
|
let mut removed = Vec::new();
|
||||||
if let Some(block) = self.remove_from_blocks(&parent) {
|
if let Some(block) = self.remove_block(parent_hash) {
|
||||||
taken.push(block);
|
removed.push(block);
|
||||||
}
|
}
|
||||||
|
|
||||||
taken.extend(self.remove_children(vec![parent]));
|
removed.extend(self.remove_children(vec![*parent_hash]));
|
||||||
self.metrics.blocks.set(self.len() as f64);
|
self.metrics.blocks.set(self.blocks.len() as f64);
|
||||||
taken
|
removed
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clean up the old blocks from the buffer as blocks before finalization are not needed
|
/// Discard all blocks that precede finalized block number from the buffer.
|
||||||
/// anymore. We can discard them from the buffer.
|
pub fn remove_old_blocks(&mut self, finalized_number: BlockNumber) {
|
||||||
pub fn clean_old_blocks(&mut self, finalized_number: BlockNumber) {
|
let mut block_hashes_to_remove = Vec::new();
|
||||||
let mut remove_parent_children = Vec::new();
|
|
||||||
|
|
||||||
// discard all blocks that are before the finalized number.
|
// discard all blocks that are before the finalized number.
|
||||||
while let Some(entry) = self.blocks.first_entry() {
|
while let Some(entry) = self.earliest_blocks.first_entry() {
|
||||||
if *entry.key() > finalized_number {
|
if *entry.key() > finalized_number {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
let blocks = entry.remove();
|
let block_hashes = entry.remove();
|
||||||
remove_parent_children.extend(
|
block_hashes_to_remove.extend(block_hashes);
|
||||||
blocks.into_iter().map(|(hash, block)| BlockNumHash::new(block.number, hash)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// remove from lru
|
|
||||||
for block in remove_parent_children.iter() {
|
|
||||||
self.lru.pop(block);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.remove_children(remove_parent_children);
|
// remove from other collections.
|
||||||
self.metrics.blocks.set(self.len() as f64);
|
for block_hash in &block_hashes_to_remove {
|
||||||
}
|
// It's fine to call
|
||||||
|
self.remove_block(block_hash);
|
||||||
/// Return reference to buffered blocks
|
|
||||||
pub fn blocks(&self) -> &BufferedBlocks {
|
|
||||||
&self.blocks
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return reference to the asked block.
|
|
||||||
pub fn block(&self, block: BlockNumHash) -> Option<&SealedBlockWithSenders> {
|
|
||||||
self.blocks.get(&block.number)?.get(&block.hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return reference to the asked block by hash.
|
|
||||||
pub fn block_by_hash(&self, hash: &BlockHash) -> Option<&SealedBlockWithSenders> {
|
|
||||||
let num = self.hash_to_num.get(hash)?;
|
|
||||||
self.blocks.get(num)?.get(hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return a reference to the lowest ancestor of the given block in the buffer.
|
|
||||||
pub fn lowest_ancestor(&self, hash: &BlockHash) -> Option<&SealedBlockWithSenders> {
|
|
||||||
let mut current_block = self.block_by_hash(hash)?;
|
|
||||||
while let Some(block) = self
|
|
||||||
.blocks
|
|
||||||
.get(&(current_block.number - 1))
|
|
||||||
.and_then(|blocks| blocks.get(¤t_block.parent_hash))
|
|
||||||
{
|
|
||||||
current_block = block;
|
|
||||||
}
|
}
|
||||||
Some(current_block)
|
|
||||||
|
self.remove_children(block_hashes_to_remove);
|
||||||
|
self.metrics.blocks.set(self.blocks.len() as f64);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return number of blocks inside buffer.
|
/// Remove block entry
|
||||||
pub fn len(&self) -> usize {
|
fn remove_from_earliest_blocks(&mut self, number: BlockNumber, hash: &BlockHash) {
|
||||||
self.lru.len()
|
if let btree_map::Entry::Occupied(mut entry) = self.earliest_blocks.entry(number) {
|
||||||
|
entry.get_mut().remove(hash);
|
||||||
|
if entry.get().is_empty() {
|
||||||
|
entry.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return if buffer is empty.
|
/// Remove from parent child connection. This method does not remove children.
|
||||||
pub fn is_empty(&self) -> bool {
|
fn remove_from_parent(&mut self, parent_hash: BlockHash, hash: &BlockHash) {
|
||||||
self.lru.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remove from the hash to num map.
|
|
||||||
fn remove_from_hash_to_num(&mut self, hash: &BlockHash) {
|
|
||||||
self.hash_to_num.remove(hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remove from parent child connection. Dont touch childrens.
|
|
||||||
fn remove_from_parent(&mut self, parent: BlockHash, block: &BlockNumHash) {
|
|
||||||
self.remove_from_hash_to_num(&parent);
|
|
||||||
|
|
||||||
// remove from parent to child connection, but only for this block parent.
|
// remove from parent to child connection, but only for this block parent.
|
||||||
if let hash_map::Entry::Occupied(mut entry) = self.parent_to_child.entry(parent) {
|
if let hash_map::Entry::Occupied(mut entry) = self.parent_to_child.entry(parent_hash) {
|
||||||
entry.get_mut().remove(block);
|
entry.get_mut().remove(hash);
|
||||||
// if set is empty remove block entry.
|
// if set is empty remove block entry.
|
||||||
if entry.get().is_empty() {
|
if entry.get().is_empty() {
|
||||||
entry.remove();
|
entry.remove();
|
||||||
@ -173,40 +152,37 @@ impl BlockBuffer {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove block from `self.blocks`, This will also remove block from `self.lru`.
|
/// Removes block from inner collections.
|
||||||
///
|
/// This method will only remove the block if it's present inside `self.blocks`.
|
||||||
/// Note: This function will not remove block from the `self.parent_to_child` connection.
|
/// The block might be missing from other collections, the method will only ensure that it has
|
||||||
fn remove_from_blocks(&mut self, block: &BlockNumHash) -> Option<SealedBlockWithSenders> {
|
/// been removed.
|
||||||
self.remove_from_hash_to_num(&block.hash);
|
fn remove_block(&mut self, hash: &BlockHash) -> Option<SealedBlockWithSenders> {
|
||||||
|
if let Some(block) = self.blocks.remove(hash) {
|
||||||
if let Entry::Occupied(mut entry) = self.blocks.entry(block.number) {
|
self.remove_from_earliest_blocks(block.number, hash);
|
||||||
let ret = entry.get_mut().remove(&block.hash);
|
self.remove_from_parent(block.parent_hash, hash);
|
||||||
// if set is empty remove block entry.
|
self.lru.pop(hash);
|
||||||
if entry.get().is_empty() {
|
Some(block)
|
||||||
entry.remove();
|
} else {
|
||||||
}
|
None
|
||||||
self.lru.pop(block);
|
}
|
||||||
return ret
|
|
||||||
};
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove all children and their descendants for the given blocks and return them.
|
/// Remove all children and their descendants for the given blocks and return them.
|
||||||
fn remove_children(&mut self, parent_blocks: Vec<BlockNumHash>) -> Vec<SealedBlockWithSenders> {
|
fn remove_children(&mut self, parent_hashes: Vec<BlockHash>) -> Vec<SealedBlockWithSenders> {
|
||||||
// remove all parent child connection and all the child children blocks that are connected
|
// remove all parent child connection and all the child children blocks that are connected
|
||||||
// to the discarded parent blocks.
|
// to the discarded parent blocks.
|
||||||
let mut remove_parent_children = parent_blocks;
|
let mut remove_parent_children = parent_hashes;
|
||||||
let mut removed_blocks = Vec::new();
|
let mut removed_blocks = Vec::new();
|
||||||
while let Some(parent_num_hash) = remove_parent_children.pop() {
|
while let Some(parent_hash) = remove_parent_children.pop() {
|
||||||
// get this child blocks children and add them to the remove list.
|
// get this child blocks children and add them to the remove list.
|
||||||
if let Some(parent_childrens) = self.parent_to_child.remove(&parent_num_hash.hash) {
|
if let Some(parent_children) = self.parent_to_child.remove(&parent_hash) {
|
||||||
// remove child from buffer
|
// remove child from buffer
|
||||||
for child in parent_childrens.iter() {
|
for child_hash in parent_children.iter() {
|
||||||
if let Some(block) = self.remove_from_blocks(child) {
|
if let Some(block) = self.remove_block(child_hash) {
|
||||||
removed_blocks.push(block);
|
removed_blocks.push(block);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
remove_parent_children.extend(parent_childrens);
|
remove_parent_children.extend(parent_children);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
removed_blocks
|
removed_blocks
|
||||||
@ -223,11 +199,41 @@ mod tests {
|
|||||||
use reth_primitives::{BlockHash, BlockNumHash, SealedBlockWithSenders};
|
use reth_primitives::{BlockHash, BlockNumHash, SealedBlockWithSenders};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
/// Create random block with specified number and parent hash.
|
||||||
fn create_block<R: Rng>(rng: &mut R, number: u64, parent: BlockHash) -> SealedBlockWithSenders {
|
fn create_block<R: Rng>(rng: &mut R, number: u64, parent: BlockHash) -> SealedBlockWithSenders {
|
||||||
let block = random_block(rng, number, Some(parent), None, None);
|
let block = random_block(rng, number, Some(parent), None, None);
|
||||||
block.seal_with_senders().unwrap()
|
block.seal_with_senders().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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.parent_to_child.iter().fold(0, |acc, (_, hashes)| acc + hashes.len()),
|
||||||
|
expected
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
buffer.earliest_blocks.iter().fold(0, |acc, (_, hashes)| acc + hashes.len()),
|
||||||
|
expected
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assert that the block was removed from all buffer collections.
|
||||||
|
fn assert_block_removal(buffer: &BlockBuffer, block: &SealedBlockWithSenders) {
|
||||||
|
assert!(buffer.blocks.get(&block.hash).is_none());
|
||||||
|
assert!(buffer
|
||||||
|
.parent_to_child
|
||||||
|
.get(&block.parent_hash)
|
||||||
|
.and_then(|p| p.get(&block.hash))
|
||||||
|
.is_none());
|
||||||
|
assert!(buffer
|
||||||
|
.earliest_blocks
|
||||||
|
.get(&block.number)
|
||||||
|
.and_then(|hashes| hashes.get(&block.hash))
|
||||||
|
.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn simple_insertion() {
|
fn simple_insertion() {
|
||||||
let mut rng = generators::rng();
|
let mut rng = generators::rng();
|
||||||
@ -236,17 +242,16 @@ mod tests {
|
|||||||
let mut buffer = BlockBuffer::new(3);
|
let mut buffer = BlockBuffer::new(3);
|
||||||
|
|
||||||
buffer.insert_block(block1.clone());
|
buffer.insert_block(block1.clone());
|
||||||
assert_eq!(buffer.len(), 1);
|
assert_buffer_lengths(&buffer, 1);
|
||||||
assert_eq!(buffer.block(block1.num_hash()), Some(&block1));
|
assert_eq!(buffer.block(&block1.hash), Some(&block1));
|
||||||
assert_eq!(buffer.block_by_hash(&block1.hash), Some(&block1));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn take_all_chain_of_childrens() {
|
fn take_entire_chain_of_children() {
|
||||||
let mut rng = generators::rng();
|
let mut rng = generators::rng();
|
||||||
|
|
||||||
let main_parent = BlockNumHash::new(9, rng.gen());
|
let main_parent_hash = rng.gen();
|
||||||
let block1 = create_block(&mut rng, 10, main_parent.hash);
|
let block1 = create_block(&mut rng, 10, main_parent_hash);
|
||||||
let block2 = create_block(&mut rng, 11, block1.hash);
|
let block2 = create_block(&mut rng, 11, block1.hash);
|
||||||
let block3 = create_block(&mut rng, 12, block2.hash);
|
let block3 = create_block(&mut rng, 12, block2.hash);
|
||||||
let parent4 = rng.gen();
|
let parent4 = rng.gen();
|
||||||
@ -259,20 +264,57 @@ mod tests {
|
|||||||
buffer.insert_block(block3.clone());
|
buffer.insert_block(block3.clone());
|
||||||
buffer.insert_block(block4.clone());
|
buffer.insert_block(block4.clone());
|
||||||
|
|
||||||
assert_eq!(buffer.len(), 4);
|
assert_buffer_lengths(&buffer, 4);
|
||||||
assert_eq!(buffer.block_by_hash(&block4.hash), Some(&block4));
|
assert_eq!(buffer.block(&block4.hash), Some(&block4));
|
||||||
assert_eq!(buffer.block_by_hash(&block2.hash), Some(&block2));
|
assert_eq!(buffer.block(&block2.hash), Some(&block2));
|
||||||
assert_eq!(buffer.block_by_hash(&main_parent.hash), None);
|
assert_eq!(buffer.block(&main_parent_hash), None);
|
||||||
|
|
||||||
assert_eq!(buffer.lowest_ancestor(&block4.hash), Some(&block4));
|
assert_eq!(buffer.lowest_ancestor(&block4.hash), Some(&block4));
|
||||||
assert_eq!(buffer.lowest_ancestor(&block3.hash), Some(&block1));
|
assert_eq!(buffer.lowest_ancestor(&block3.hash), Some(&block1));
|
||||||
assert_eq!(buffer.lowest_ancestor(&block1.hash), Some(&block1));
|
assert_eq!(buffer.lowest_ancestor(&block1.hash), Some(&block1));
|
||||||
assert_eq!(buffer.remove_with_children(main_parent), vec![block1, block2, block3]);
|
assert_eq!(
|
||||||
assert_eq!(buffer.len(), 1);
|
buffer.remove_block_with_children(&main_parent_hash),
|
||||||
|
vec![block1, block2, block3]
|
||||||
|
);
|
||||||
|
assert_buffer_lengths(&buffer, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn take_all_multi_level_childrens() {
|
fn take_all_multi_level_children() {
|
||||||
|
let mut rng = generators::rng();
|
||||||
|
|
||||||
|
let main_parent_hash = rng.gen();
|
||||||
|
let block1 = create_block(&mut rng, 10, main_parent_hash);
|
||||||
|
let block2 = create_block(&mut rng, 11, block1.hash);
|
||||||
|
let block3 = create_block(&mut rng, 11, block1.hash);
|
||||||
|
let block4 = create_block(&mut rng, 12, block2.hash);
|
||||||
|
|
||||||
|
let mut buffer = BlockBuffer::new(5);
|
||||||
|
|
||||||
|
buffer.insert_block(block1.clone());
|
||||||
|
buffer.insert_block(block2.clone());
|
||||||
|
buffer.insert_block(block3.clone());
|
||||||
|
buffer.insert_block(block4.clone());
|
||||||
|
|
||||||
|
assert_buffer_lengths(&buffer, 4);
|
||||||
|
assert_eq!(
|
||||||
|
buffer
|
||||||
|
.remove_block_with_children(&main_parent_hash)
|
||||||
|
.into_iter()
|
||||||
|
.map(|b| (b.hash, b))
|
||||||
|
.collect::<HashMap<_, _>>(),
|
||||||
|
HashMap::from([
|
||||||
|
(block1.hash, block1),
|
||||||
|
(block2.hash, block2),
|
||||||
|
(block3.hash, block3),
|
||||||
|
(block4.hash, block4)
|
||||||
|
])
|
||||||
|
);
|
||||||
|
assert_buffer_lengths(&buffer, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn take_block_with_children() {
|
||||||
let mut rng = generators::rng();
|
let mut rng = generators::rng();
|
||||||
|
|
||||||
let main_parent = BlockNumHash::new(9, rng.gen());
|
let main_parent = BlockNumHash::new(9, rng.gen());
|
||||||
@ -288,10 +330,10 @@ mod tests {
|
|||||||
buffer.insert_block(block3.clone());
|
buffer.insert_block(block3.clone());
|
||||||
buffer.insert_block(block4.clone());
|
buffer.insert_block(block4.clone());
|
||||||
|
|
||||||
assert_eq!(buffer.len(), 4);
|
assert_buffer_lengths(&buffer, 4);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
buffer
|
buffer
|
||||||
.remove_with_children(main_parent)
|
.remove_block_with_children(&block1.hash)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|b| (b.hash, b))
|
.map(|b| (b.hash, b))
|
||||||
.collect::<HashMap<_, _>>(),
|
.collect::<HashMap<_, _>>(),
|
||||||
@ -302,45 +344,11 @@ mod tests {
|
|||||||
(block4.hash, block4)
|
(block4.hash, block4)
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
assert_eq!(buffer.len(), 0);
|
assert_buffer_lengths(&buffer, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn take_self_with_childs() {
|
fn remove_chain_of_children() {
|
||||||
let mut rng = generators::rng();
|
|
||||||
|
|
||||||
let main_parent = BlockNumHash::new(9, rng.gen());
|
|
||||||
let block1 = create_block(&mut rng, 10, main_parent.hash);
|
|
||||||
let block2 = create_block(&mut rng, 11, block1.hash);
|
|
||||||
let block3 = create_block(&mut rng, 11, block1.hash);
|
|
||||||
let block4 = create_block(&mut rng, 12, block2.hash);
|
|
||||||
|
|
||||||
let mut buffer = BlockBuffer::new(5);
|
|
||||||
|
|
||||||
buffer.insert_block(block1.clone());
|
|
||||||
buffer.insert_block(block2.clone());
|
|
||||||
buffer.insert_block(block3.clone());
|
|
||||||
buffer.insert_block(block4.clone());
|
|
||||||
|
|
||||||
assert_eq!(buffer.len(), 4);
|
|
||||||
assert_eq!(
|
|
||||||
buffer
|
|
||||||
.remove_with_children(block1.num_hash())
|
|
||||||
.into_iter()
|
|
||||||
.map(|b| (b.hash, b))
|
|
||||||
.collect::<HashMap<_, _>>(),
|
|
||||||
HashMap::from([
|
|
||||||
(block1.hash, block1),
|
|
||||||
(block2.hash, block2),
|
|
||||||
(block3.hash, block3),
|
|
||||||
(block4.hash, block4)
|
|
||||||
])
|
|
||||||
);
|
|
||||||
assert_eq!(buffer.len(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn clean_chain_of_children() {
|
|
||||||
let mut rng = generators::rng();
|
let mut rng = generators::rng();
|
||||||
|
|
||||||
let main_parent = BlockNumHash::new(9, rng.gen());
|
let main_parent = BlockNumHash::new(9, rng.gen());
|
||||||
@ -357,13 +365,13 @@ mod tests {
|
|||||||
buffer.insert_block(block3);
|
buffer.insert_block(block3);
|
||||||
buffer.insert_block(block4);
|
buffer.insert_block(block4);
|
||||||
|
|
||||||
assert_eq!(buffer.len(), 4);
|
assert_buffer_lengths(&buffer, 4);
|
||||||
buffer.clean_old_blocks(block1.number);
|
buffer.remove_old_blocks(block1.number);
|
||||||
assert_eq!(buffer.len(), 1);
|
assert_buffer_lengths(&buffer, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn clean_all_multi_level_childrens() {
|
fn remove_all_multi_level_children() {
|
||||||
let mut rng = generators::rng();
|
let mut rng = generators::rng();
|
||||||
|
|
||||||
let main_parent = BlockNumHash::new(9, rng.gen());
|
let main_parent = BlockNumHash::new(9, rng.gen());
|
||||||
@ -379,13 +387,13 @@ mod tests {
|
|||||||
buffer.insert_block(block3);
|
buffer.insert_block(block3);
|
||||||
buffer.insert_block(block4);
|
buffer.insert_block(block4);
|
||||||
|
|
||||||
assert_eq!(buffer.len(), 4);
|
assert_buffer_lengths(&buffer, 4);
|
||||||
buffer.clean_old_blocks(block1.number);
|
buffer.remove_old_blocks(block1.number);
|
||||||
assert_eq!(buffer.len(), 0);
|
assert_buffer_lengths(&buffer, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn clean_multi_chains() {
|
fn remove_multi_chains() {
|
||||||
let mut rng = generators::rng();
|
let mut rng = generators::rng();
|
||||||
|
|
||||||
let main_parent = BlockNumHash::new(9, rng.gen());
|
let main_parent = BlockNumHash::new(9, rng.gen());
|
||||||
@ -423,19 +431,9 @@ mod tests {
|
|||||||
assert_eq!(buffer.lowest_ancestor(&block1a.hash), Some(&block1a));
|
assert_eq!(buffer.lowest_ancestor(&block1a.hash), Some(&block1a));
|
||||||
assert_eq!(buffer.lowest_ancestor(&block1.hash), Some(&block1));
|
assert_eq!(buffer.lowest_ancestor(&block1.hash), Some(&block1));
|
||||||
|
|
||||||
assert_eq!(buffer.len(), 7);
|
assert_buffer_lengths(&buffer, 7);
|
||||||
buffer.clean_old_blocks(10);
|
buffer.remove_old_blocks(10);
|
||||||
assert_eq!(buffer.len(), 2);
|
assert_buffer_lengths(&buffer, 2);
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_block_existance(buffer: &BlockBuffer, block: &SealedBlockWithSenders) {
|
|
||||||
assert!(buffer.blocks.get(&block.number).and_then(|t| t.get(&block.hash)).is_none());
|
|
||||||
assert!(buffer
|
|
||||||
.parent_to_child
|
|
||||||
.get(&block.parent_hash)
|
|
||||||
.and_then(|p| p.get(&block.num_hash()))
|
|
||||||
.is_none());
|
|
||||||
assert!(buffer.hash_to_num.get(&block.hash).is_none());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -465,14 +463,14 @@ mod tests {
|
|||||||
assert_eq!(buffer.lowest_ancestor(&block4.hash), Some(&block4));
|
assert_eq!(buffer.lowest_ancestor(&block4.hash), Some(&block4));
|
||||||
|
|
||||||
// block1 gets evicted
|
// block1 gets evicted
|
||||||
assert_block_existance(&buffer, &block1);
|
assert_block_removal(&buffer, &block1);
|
||||||
|
|
||||||
// check lowest ancestor results post eviction
|
// check lowest ancestor results post eviction
|
||||||
assert_eq!(buffer.lowest_ancestor(&block3.hash), Some(&block2));
|
assert_eq!(buffer.lowest_ancestor(&block3.hash), Some(&block2));
|
||||||
assert_eq!(buffer.lowest_ancestor(&block2.hash), Some(&block2));
|
assert_eq!(buffer.lowest_ancestor(&block2.hash), Some(&block2));
|
||||||
assert_eq!(buffer.lowest_ancestor(&block1.hash), None);
|
assert_eq!(buffer.lowest_ancestor(&block1.hash), None);
|
||||||
|
|
||||||
assert_eq!(buffer.len(), 3);
|
assert_buffer_lengths(&buffer, 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -494,8 +492,8 @@ mod tests {
|
|||||||
buffer.insert_block(block4);
|
buffer.insert_block(block4);
|
||||||
|
|
||||||
// block3 gets evicted
|
// block3 gets evicted
|
||||||
assert_block_existance(&buffer, &block1);
|
assert_block_removal(&buffer, &block1);
|
||||||
|
|
||||||
assert_eq!(buffer.len(), 3);
|
assert_buffer_lengths(&buffer, 3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -172,7 +172,7 @@ impl<DB: Database, EF: ExecutorFactory> BlockchainTree<DB, EF> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check if block is disconnected
|
// check if block is disconnected
|
||||||
if let Some(block) = self.state.buffered_blocks.block(block) {
|
if let Some(block) = self.state.buffered_blocks.block(&block.hash) {
|
||||||
return Ok(Some(BlockStatus::Disconnected {
|
return Ok(Some(BlockStatus::Disconnected {
|
||||||
missing_ancestor: block.parent_num_hash(),
|
missing_ancestor: block.parent_num_hash(),
|
||||||
}));
|
}));
|
||||||
@ -328,7 +328,7 @@ impl<DB: Database, EF: ExecutorFactory> BlockchainTree<DB, EF> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if there is a parent inside the buffer, validate against it.
|
// if there is a parent inside the buffer, validate against it.
|
||||||
if let Some(buffered_parent) = self.state.buffered_blocks.block(parent) {
|
if let Some(buffered_parent) = self.state.buffered_blocks.block(&parent.hash) {
|
||||||
self.externals
|
self.externals
|
||||||
.consensus
|
.consensus
|
||||||
.validate_header_against_parent(&block, buffered_parent)
|
.validate_header_against_parent(&block, buffered_parent)
|
||||||
@ -804,7 +804,7 @@ impl<DB: Database, EF: ExecutorFactory> BlockchainTree<DB, EF> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// clean block buffer.
|
// clean block buffer.
|
||||||
self.state.buffered_blocks.clean_old_blocks(finalized_block);
|
self.state.buffered_blocks.remove_old_blocks(finalized_block);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reads the last `N` canonical hashes from the database and updates the block indices of the
|
/// Reads the last `N` canonical hashes from the database and updates the block indices of the
|
||||||
@ -890,7 +890,7 @@ impl<DB: Database, EF: ExecutorFactory> BlockchainTree<DB, EF> {
|
|||||||
trace!(target: "blockchain_tree", ?new_block, "try_connect_buffered_blocks");
|
trace!(target: "blockchain_tree", ?new_block, "try_connect_buffered_blocks");
|
||||||
|
|
||||||
// first remove all the children of the new block from the buffer
|
// first remove all the children of the new block from the buffer
|
||||||
let include_blocks = self.state.buffered_blocks.remove_with_children(new_block);
|
let include_blocks = self.state.buffered_blocks.remove_block_with_children(&new_block.hash);
|
||||||
// then try to reinsert them into the tree
|
// then try to reinsert them into the tree
|
||||||
for block in include_blocks.into_iter() {
|
for block in include_blocks.into_iter() {
|
||||||
// dont fail on error, just ignore the block.
|
// dont fail on error, just ignore the block.
|
||||||
@ -1282,7 +1282,6 @@ impl<DB: Database, EF: ExecutorFactory> BlockchainTree<DB, EF> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::block_buffer::BufferedBlocks;
|
|
||||||
use assert_matches::assert_matches;
|
use assert_matches::assert_matches;
|
||||||
use linked_hash_set::LinkedHashSet;
|
use linked_hash_set::LinkedHashSet;
|
||||||
use reth_db::{tables, test_utils::TempDatabase, transaction::DbTxMut, DatabaseEnv};
|
use reth_db::{tables, test_utils::TempDatabase, transaction::DbTxMut, DatabaseEnv};
|
||||||
@ -1368,7 +1367,7 @@ mod tests {
|
|||||||
/// Pending blocks
|
/// Pending blocks
|
||||||
pending_blocks: Option<(BlockNumber, HashSet<BlockHash>)>,
|
pending_blocks: Option<(BlockNumber, HashSet<BlockHash>)>,
|
||||||
/// Buffered blocks
|
/// Buffered blocks
|
||||||
buffered_blocks: Option<BufferedBlocks>,
|
buffered_blocks: Option<HashMap<BlockHash, SealedBlockWithSenders>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TreeTester {
|
impl TreeTester {
|
||||||
@ -1376,10 +1375,12 @@ mod tests {
|
|||||||
self.chain_num = Some(chain_num);
|
self.chain_num = Some(chain_num);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn with_block_to_chain(mut self, block_to_chain: HashMap<BlockHash, BlockChainId>) -> Self {
|
fn with_block_to_chain(mut self, block_to_chain: HashMap<BlockHash, BlockChainId>) -> Self {
|
||||||
self.block_to_chain = Some(block_to_chain);
|
self.block_to_chain = Some(block_to_chain);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn with_fork_to_child(
|
fn with_fork_to_child(
|
||||||
mut self,
|
mut self,
|
||||||
fork_to_child: HashMap<BlockHash, HashSet<BlockHash>>,
|
fork_to_child: HashMap<BlockHash, HashSet<BlockHash>>,
|
||||||
@ -1388,7 +1389,10 @@ mod tests {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn with_buffered_blocks(mut self, buffered_blocks: BufferedBlocks) -> Self {
|
fn with_buffered_blocks(
|
||||||
|
mut self,
|
||||||
|
buffered_blocks: HashMap<BlockHash, SealedBlockWithSenders>,
|
||||||
|
) -> Self {
|
||||||
self.buffered_blocks = Some(buffered_blocks);
|
self.buffered_blocks = Some(buffered_blocks);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@ -1659,10 +1663,7 @@ mod tests {
|
|||||||
// |
|
// |
|
||||||
|
|
||||||
TreeTester::default()
|
TreeTester::default()
|
||||||
.with_buffered_blocks(BTreeMap::from([(
|
.with_buffered_blocks(HashMap::from([(block2.hash(), block2.clone())]))
|
||||||
block2.number,
|
|
||||||
HashMap::from([(block2.hash(), block2.clone())]),
|
|
||||||
)]))
|
|
||||||
.assert(&tree);
|
.assert(&tree);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -1959,10 +1960,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
TreeTester::default()
|
TreeTester::default()
|
||||||
.with_buffered_blocks(BTreeMap::from([(
|
.with_buffered_blocks(HashMap::from([(block2b.hash(), block2b.clone())]))
|
||||||
block2b.number,
|
|
||||||
HashMap::from([(block2b.hash(), block2b.clone())]),
|
|
||||||
)]))
|
|
||||||
.assert(&tree);
|
.assert(&tree);
|
||||||
|
|
||||||
// update canonical block to b2, this would make b2a be removed
|
// update canonical block to b2, this would make b2a be removed
|
||||||
@ -1979,10 +1977,10 @@ mod tests {
|
|||||||
// |
|
// |
|
||||||
TreeTester::default()
|
TreeTester::default()
|
||||||
.with_chain_num(0)
|
.with_chain_num(0)
|
||||||
.with_block_to_chain(HashMap::from([]))
|
.with_block_to_chain(HashMap::default())
|
||||||
.with_fork_to_child(HashMap::from([]))
|
.with_fork_to_child(HashMap::default())
|
||||||
.with_pending_blocks((block2.number + 1, HashSet::from([])))
|
.with_pending_blocks((block2.number + 1, HashSet::default()))
|
||||||
.with_buffered_blocks(BTreeMap::from([]))
|
.with_buffered_blocks(HashMap::default())
|
||||||
.assert(&tree);
|
.assert(&tree);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -99,7 +99,7 @@ impl TreeState {
|
|||||||
|
|
||||||
/// Checks the block buffer for the given block.
|
/// Checks the block buffer for the given block.
|
||||||
pub(crate) fn get_buffered_block(&self, hash: &BlockHash) -> Option<&SealedBlockWithSenders> {
|
pub(crate) fn get_buffered_block(&self, hash: &BlockHash) -> Option<&SealedBlockWithSenders> {
|
||||||
self.buffered_blocks.block_by_hash(hash)
|
self.buffered_blocks.block(hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the lowest ancestor for the given block in the block buffer.
|
/// Gets the lowest ancestor for the given block in the block buffer.
|
||||||
|
|||||||
Reference in New Issue
Block a user