diff --git a/Cargo.lock b/Cargo.lock index 22f123e02..0e3c08dc5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6517,44 +6517,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "reth-blockchain-tree" -version = "1.1.5" -dependencies = [ - "alloy-consensus", - "alloy-eips", - "alloy-genesis", - "alloy-primitives", - "aquamarine", - "assert_matches", - "linked_hash_set", - "metrics", - "parking_lot", - "reth-blockchain-tree-api", - "reth-chainspec", - "reth-consensus", - "reth-db", - "reth-db-api", - "reth-evm", - "reth-evm-ethereum", - "reth-execution-errors", - "reth-execution-types", - "reth-metrics", - "reth-network", - "reth-node-types", - "reth-primitives", - "reth-provider", - "reth-revm", - "reth-stages-api", - "reth-storage-errors", - "reth-testing-utils", - "reth-trie", - "reth-trie-db", - "reth-trie-parallel", - "tokio", - "tracing", -] - [[package]] name = "reth-blockchain-tree-api" version = "1.1.5" diff --git a/Cargo.toml b/Cargo.toml index 47d802c5a..890b79a14 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,6 @@ members = [ "bin/reth-bench/", "bin/reth/", "crates/blockchain-tree-api/", - "crates/blockchain-tree/", "crates/chain-state/", "crates/chainspec/", "crates/cli/cli/", @@ -304,7 +303,6 @@ op-reth = { path = "crates/optimism/bin" } reth = { path = "bin/reth" } reth-basic-payload-builder = { path = "crates/payload/basic" } reth-bench = { path = "bin/reth-bench" } -reth-blockchain-tree = { path = "crates/blockchain-tree" } reth-blockchain-tree-api = { path = "crates/blockchain-tree-api" } reth-chain-state = { path = "crates/chain-state" } reth-chainspec = { path = "crates/chainspec", default-features = false } diff --git a/crates/blockchain-tree/Cargo.toml b/crates/blockchain-tree/Cargo.toml deleted file mode 100644 index 1c42a292a..000000000 --- a/crates/blockchain-tree/Cargo.toml +++ /dev/null @@ -1,89 +0,0 @@ -[package] -name = "reth-blockchain-tree" -version.workspace = true -edition.workspace = true -rust-version.workspace = true -license.workspace = true -homepage.workspace = true -repository.workspace = true - -[lints] -workspace = true - -[dependencies] -# reth -reth-blockchain-tree-api.workspace = true -reth-primitives.workspace = true -reth-storage-errors.workspace = true -reth-execution-errors.workspace = true -reth-db.workspace = true -reth-db-api.workspace = true -reth-evm.workspace = true -reth-revm.workspace = true -reth-provider.workspace = true -reth-execution-types.workspace = true -reth-stages-api.workspace = true -reth-trie = { workspace = true, features = ["metrics"] } -reth-trie-db = { workspace = true, features = ["metrics"] } -reth-trie-parallel.workspace = true -reth-network.workspace = true -reth-consensus.workspace = true -reth-node-types.workspace = true - -# ethereum -alloy-consensus.workspace = true -alloy-primitives.workspace = true -alloy-eips.workspace = true - -# common -parking_lot.workspace = true -tracing.workspace = true -tokio = { workspace = true, features = ["macros", "sync"] } - -# metrics -reth-metrics = { workspace = true, features = ["common"] } -metrics.workspace = true - -# misc -aquamarine.workspace = true -linked_hash_set.workspace = true - -[dev-dependencies] -reth-chainspec.workspace = true -reth-db = { workspace = true, features = ["test-utils"] } -reth-primitives = { workspace = true, features = ["test-utils"] } -reth-provider = { workspace = true, features = ["test-utils"] } -reth-evm = { workspace = true, features = ["test-utils"] } -reth-consensus = { workspace = true, features = ["test-utils"] } -reth-testing-utils.workspace = true -reth-revm.workspace = true -reth-evm-ethereum.workspace = true -reth-execution-types.workspace = true -parking_lot.workspace = true -assert_matches.workspace = true -alloy-genesis.workspace = true -alloy-consensus.workspace = true - -[features] -test-utils = [ - "reth-chainspec/test-utils", - "reth-consensus/test-utils", - "reth-evm/test-utils", - "reth-network/test-utils", - "reth-primitives/test-utils", - "reth-revm/test-utils", - "reth-stages-api/test-utils", - "reth-db/test-utils", - "reth-db-api/test-utils", - "reth-provider/test-utils", - "reth-trie-db/test-utils", - "reth-trie/test-utils", - "reth-trie-parallel/test-utils" -] -optimism = [ - "reth-primitives/optimism", - "reth-provider/optimism", - "reth-execution-types/optimism", - "reth-db/optimism", - "reth-db-api/optimism" -] diff --git a/crates/blockchain-tree/docs/mermaid/tree.mmd b/crates/blockchain-tree/docs/mermaid/tree.mmd deleted file mode 100644 index c9b41b857..000000000 --- a/crates/blockchain-tree/docs/mermaid/tree.mmd +++ /dev/null @@ -1,21 +0,0 @@ -flowchart BT - subgraph canonical chain - CanonState:::state - block0canon:::canon -->block1canon:::canon -->block2canon:::canon -->block3canon:::canon --> - block4canon:::canon --> block5canon:::canon - end - block5canon --> block6pending1:::pending - block5canon --> block6pending2:::pending - subgraph sidechain2 - S2State:::state - block3canon --> block4s2:::sidechain --> block5s2:::sidechain - end - subgraph sidechain1 - S1State:::state - block2canon --> block3s1:::sidechain --> block4s1:::sidechain --> block5s1:::sidechain --> - block6s1:::sidechain - end - classDef state fill:#1882C4 - classDef canon fill:#8AC926 - classDef pending fill:#FFCA3A - classDef sidechain fill:#FF595E diff --git a/crates/blockchain-tree/src/block_buffer.rs b/crates/blockchain-tree/src/block_buffer.rs deleted file mode 100644 index 994ed82cf..000000000 --- a/crates/blockchain-tree/src/block_buffer.rs +++ /dev/null @@ -1,494 +0,0 @@ -use crate::metrics::BlockBufferMetrics; -use alloy_consensus::BlockHeader; -use alloy_primitives::{BlockHash, BlockNumber}; -use reth_network::cache::LruCache; -use reth_node_types::Block; -use reth_primitives::SealedBlockWithSenders; -use std::collections::{BTreeMap, HashMap, HashSet}; - -/// Contains the tree of pending blocks that cannot be executed due to missing parent. -/// It allows to store unconnected blocks for potential future inclusion. -/// -/// The buffer has three main functionalities: -/// * [`BlockBuffer::insert_block`] for inserting blocks inside the buffer. -/// * [`BlockBuffer::remove_block_with_children`] for connecting blocks if the parent gets received -/// and inserted. -/// * [`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 -/// is done by last recently used block. -#[derive(Debug)] -pub struct BlockBuffer { - /// All blocks in the buffer stored by their block hash. - pub(crate) blocks: HashMap>, - /// Map of any parent block hash (even the ones not currently in the buffer) - /// to the buffered children. - /// Allows connecting buffered blocks by parent. - pub(crate) parent_to_child: HashMap>, - /// `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: LruCache, - /// Various metrics for the block buffer. - pub(crate) metrics: BlockBufferMetrics, -} - -impl BlockBuffer { - /// Create new buffer with max limit of blocks - pub fn new(limit: u32) -> Self { - Self { - blocks: Default::default(), - parent_to_child: Default::default(), - earliest_blocks: Default::default(), - lru: LruCache::new(limit), - metrics: Default::default(), - } - } - - /// Return reference to buffered blocks - pub const fn blocks(&self) -> &HashMap> { - &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. - pub fn insert_block(&mut self, block: SealedBlockWithSenders) { - let hash = block.hash(); - - self.parent_to_child.entry(block.parent_hash()).or_default().insert(hash); - self.earliest_blocks.entry(block.number()).or_default().insert(hash); - self.blocks.insert(hash, block); - - if let (_, Some(evicted_hash)) = self.lru.insert_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); - } - } - self.metrics.blocks.set(self.blocks.len() as f64); - } - - /// 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. - /// - /// 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. - pub fn remove_block_with_children( - &mut self, - parent_hash: &BlockHash, - ) -> Vec> { - let removed = self - .remove_block(parent_hash) - .into_iter() - .chain(self.remove_children(vec![*parent_hash])) - .collect(); - self.metrics.blocks.set(self.blocks.len() as f64); - removed - } - - /// Discard all blocks that precede block number from the buffer. - pub fn remove_old_blocks(&mut self, block_number: BlockNumber) { - let mut block_hashes_to_remove = Vec::new(); - - // discard all blocks that are before the finalized number. - while let Some(entry) = self.earliest_blocks.first_entry() { - if *entry.key() > block_number { - break - } - let block_hashes = entry.remove(); - block_hashes_to_remove.extend(block_hashes); - } - - // remove from other collections. - for block_hash in &block_hashes_to_remove { - // It's fine to call - self.remove_block(block_hash); - } - - self.remove_children(block_hashes_to_remove); - self.metrics.blocks.set(self.blocks.len() as f64); - } - - /// Remove block entry - fn remove_from_earliest_blocks(&mut self, number: BlockNumber, hash: &BlockHash) { - if let Some(entry) = self.earliest_blocks.get_mut(&number) { - entry.remove(hash); - if entry.is_empty() { - self.earliest_blocks.remove(&number); - } - } - } - - /// Remove from parent child connection. This method does not remove children. - fn remove_from_parent(&mut self, parent_hash: BlockHash, hash: &BlockHash) { - // remove from parent to child connection, but only for this block parent. - if let Some(entry) = self.parent_to_child.get_mut(&parent_hash) { - entry.remove(hash); - // if set is empty remove block entry. - if entry.is_empty() { - self.parent_to_child.remove(&parent_hash); - } - } - } - - /// Removes block from inner collections. - /// This method will only remove the block if it's present inside `self.blocks`. - /// The block might be missing from other collections, the method will only ensure that it has - /// been removed. - fn remove_block(&mut self, hash: &BlockHash) -> Option> { - 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); - Some(block) - } - - /// Remove all children and their descendants for the given blocks and return them. - fn remove_children(&mut self, parent_hashes: Vec) -> Vec> { - // remove all parent child connection and all the child children blocks that are connected - // to the discarded parent blocks. - let mut remove_parent_children = parent_hashes; - let mut removed_blocks = Vec::new(); - while let Some(parent_hash) = remove_parent_children.pop() { - // get this child blocks children and add them to the remove list. - if let Some(parent_children) = self.parent_to_child.remove(&parent_hash) { - // remove child from buffer - for child_hash in &parent_children { - if let Some(block) = self.remove_block(child_hash) { - removed_blocks.push(block); - } - } - remove_parent_children.extend(parent_children); - } - } - removed_blocks - } -} - -#[cfg(test)] -mod tests { - use crate::BlockBuffer; - use alloy_eips::BlockNumHash; - use alloy_primitives::BlockHash; - use reth_primitives::SealedBlockWithSenders; - use reth_testing_utils::generators::{self, random_block, BlockParams, Rng}; - use std::collections::HashMap; - - /// Create random block with specified number and parent hash. - fn create_block(rng: &mut R, number: u64, parent: BlockHash) -> SealedBlockWithSenders { - let block = - random_block(rng, number, BlockParams { parent: Some(parent), ..Default::default() }); - 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.contains_key(&block.hash())); - 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] - fn simple_insertion() { - let mut rng = generators::rng(); - let parent = rng.gen(); - let block1 = create_block(&mut rng, 10, parent); - let mut buffer = BlockBuffer::new(3); - - buffer.insert_block(block1.clone()); - assert_buffer_lengths(&buffer, 1); - assert_eq!(buffer.block(&block1.hash()), Some(&block1)); - } - - #[test] - fn take_entire_chain_of_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, 12, block2.hash()); - let parent4 = rng.gen(); - let block4 = create_block(&mut rng, 14, parent4); - - 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.block(&block4.hash()), Some(&block4)); - assert_eq!(buffer.block(&block2.hash()), Some(&block2)); - assert_eq!(buffer.block(&main_parent_hash), None); - - assert_eq!(buffer.lowest_ancestor(&block4.hash()), Some(&block4)); - assert_eq!(buffer.lowest_ancestor(&block3.hash()), Some(&block1)); - assert_eq!(buffer.lowest_ancestor(&block1.hash()), Some(&block1)); - assert_eq!( - buffer.remove_block_with_children(&main_parent_hash), - vec![block1, block2, block3] - ); - assert_buffer_lengths(&buffer, 1); - } - - #[test] - 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::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 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_buffer_lengths(&buffer, 4); - assert_eq!( - buffer - .remove_block_with_children(&block1.hash()) - .into_iter() - .map(|b| (b.hash(), b)) - .collect::>(), - HashMap::from([ - (block1.hash(), block1), - (block2.hash(), block2), - (block3.hash(), block3), - (block4.hash(), block4) - ]) - ); - assert_buffer_lengths(&buffer, 0); - } - - #[test] - 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, 12, block2.hash()); - let parent4 = rng.gen(); - let block4 = create_block(&mut rng, 14, parent4); - - let mut buffer = BlockBuffer::new(5); - - buffer.insert_block(block1.clone()); - buffer.insert_block(block2); - buffer.insert_block(block3); - buffer.insert_block(block4); - - assert_buffer_lengths(&buffer, 4); - buffer.remove_old_blocks(block1.number); - assert_buffer_lengths(&buffer, 1); - } - - #[test] - fn remove_all_multi_level_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); - buffer.insert_block(block3); - buffer.insert_block(block4); - - assert_buffer_lengths(&buffer, 4); - buffer.remove_old_blocks(block1.number); - assert_buffer_lengths(&buffer, 0); - } - - #[test] - fn remove_multi_chains() { - let mut rng = generators::rng(); - - let main_parent = BlockNumHash::new(9, rng.gen()); - let block1 = create_block(&mut rng, 10, main_parent.hash); - let block1a = create_block(&mut rng, 10, main_parent.hash); - let block2 = create_block(&mut rng, 11, block1.hash()); - let block2a = create_block(&mut rng, 11, block1.hash()); - let random_parent1 = rng.gen(); - let random_block1 = create_block(&mut rng, 10, random_parent1); - let random_parent2 = rng.gen(); - let random_block2 = create_block(&mut rng, 11, random_parent2); - let random_parent3 = rng.gen(); - let random_block3 = create_block(&mut rng, 12, random_parent3); - - let mut buffer = BlockBuffer::new(10); - - buffer.insert_block(block1.clone()); - buffer.insert_block(block1a.clone()); - buffer.insert_block(block2.clone()); - buffer.insert_block(block2a.clone()); - buffer.insert_block(random_block1.clone()); - buffer.insert_block(random_block2.clone()); - buffer.insert_block(random_block3.clone()); - - // check that random blocks are their own ancestor, and that chains have proper ancestors - assert_eq!(buffer.lowest_ancestor(&random_block1.hash()), Some(&random_block1)); - assert_eq!(buffer.lowest_ancestor(&random_block2.hash()), Some(&random_block2)); - assert_eq!(buffer.lowest_ancestor(&random_block3.hash()), Some(&random_block3)); - - // descendants have ancestors - assert_eq!(buffer.lowest_ancestor(&block2a.hash()), Some(&block1)); - assert_eq!(buffer.lowest_ancestor(&block2.hash()), Some(&block1)); - - // roots are themselves - assert_eq!(buffer.lowest_ancestor(&block1a.hash()), Some(&block1a)); - assert_eq!(buffer.lowest_ancestor(&block1.hash()), Some(&block1)); - - assert_buffer_lengths(&buffer, 7); - buffer.remove_old_blocks(10); - assert_buffer_lengths(&buffer, 2); - } - - #[test] - fn evict_with_gap() { - 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, 12, block2.hash()); - let parent4 = rng.gen(); - let block4 = create_block(&mut rng, 13, parent4); - - let mut buffer = BlockBuffer::new(3); - - buffer.insert_block(block1.clone()); - buffer.insert_block(block2.clone()); - buffer.insert_block(block3.clone()); - - // pre-eviction block1 is the root - assert_eq!(buffer.lowest_ancestor(&block3.hash()), Some(&block1)); - assert_eq!(buffer.lowest_ancestor(&block2.hash()), Some(&block1)); - assert_eq!(buffer.lowest_ancestor(&block1.hash()), Some(&block1)); - - buffer.insert_block(block4.clone()); - - assert_eq!(buffer.lowest_ancestor(&block4.hash()), Some(&block4)); - - // block1 gets evicted - assert_block_removal(&buffer, &block1); - - // check lowest ancestor results post eviction - assert_eq!(buffer.lowest_ancestor(&block3.hash()), Some(&block2)); - assert_eq!(buffer.lowest_ancestor(&block2.hash()), Some(&block2)); - assert_eq!(buffer.lowest_ancestor(&block1.hash()), None); - - assert_buffer_lengths(&buffer, 3); - } - - #[test] - fn simple_eviction() { - 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, 12, block2.hash()); - let parent4 = rng.gen(); - let block4 = create_block(&mut rng, 13, parent4); - - let mut buffer = BlockBuffer::new(3); - - buffer.insert_block(block1.clone()); - buffer.insert_block(block2); - buffer.insert_block(block3); - buffer.insert_block(block4); - - // block3 gets evicted - assert_block_removal(&buffer, &block1); - - assert_buffer_lengths(&buffer, 3); - } -} diff --git a/crates/blockchain-tree/src/block_indices.rs b/crates/blockchain-tree/src/block_indices.rs deleted file mode 100644 index 26a676f4d..000000000 --- a/crates/blockchain-tree/src/block_indices.rs +++ /dev/null @@ -1,620 +0,0 @@ -//! Implementation of [`BlockIndices`] related to [`super::BlockchainTree`] - -use super::state::SidechainId; -use crate::canonical_chain::CanonicalChain; -use alloy_eips::BlockNumHash; -use alloy_primitives::{BlockHash, BlockNumber}; -use linked_hash_set::LinkedHashSet; -use reth_execution_types::Chain; -use reth_primitives::SealedBlockWithSenders; -use std::collections::{btree_map, hash_map, BTreeMap, BTreeSet, HashMap, HashSet}; - -/// Internal indices of the blocks and chains. -/// -/// This is main connection between blocks, chains and canonical chain. -/// -/// It contains a list of canonical block hashes, forks to child blocks, and a mapping of block hash -/// to chain ID. -#[derive(Debug, Clone)] -pub struct BlockIndices { - /// Last finalized block. - last_finalized_block: BlockNumber, - /// Non-finalized canonical chain. Contains N number (depends on `finalization_depth`) of - /// blocks. These blocks are found in `fork_to_child` but not inside `blocks_to_chain` or - /// `number_to_block` as those are sidechain specific indices. - canonical_chain: CanonicalChain, - /// Index needed when discarding the chain, so we can remove connected chains from tree. - /// - /// This maintains insertion order for all child blocks, so - /// [`BlockIndices::pending_block_num_hash`] returns always the same block: the first child - /// block we inserted. - /// - /// NOTE: It contains just blocks that are forks as a key and not all blocks. - fork_to_child: HashMap>, - /// Utility index for Block number to block hash(s). - /// - /// This maps all blocks with same block number to their hash. - /// - /// Can be used for RPC fetch block(s) in chain by its number. - /// - /// Note: This is a bijection: at all times `blocks_to_chain` and this map contain the block - /// hashes. - block_number_to_block_hashes: BTreeMap>, - /// Block hashes to the sidechain IDs they belong to. - blocks_to_chain: HashMap, -} - -impl BlockIndices { - /// Create new block indices structure - pub fn new( - last_finalized_block: BlockNumber, - canonical_chain: BTreeMap, - ) -> Self { - Self { - last_finalized_block, - canonical_chain: CanonicalChain::new(canonical_chain), - fork_to_child: Default::default(), - blocks_to_chain: Default::default(), - block_number_to_block_hashes: Default::default(), - } - } - - /// Return fork to child indices - pub const fn fork_to_child(&self) -> &HashMap> { - &self.fork_to_child - } - - /// Return block to sidechain id - #[allow(dead_code)] - pub(crate) const fn blocks_to_chain(&self) -> &HashMap { - &self.blocks_to_chain - } - - /// Returns the hash and number of the pending block. - /// - /// It is possible that multiple child blocks for the canonical tip exist. - /// This will always return the _first_ child we recorded for the canonical tip. - pub(crate) fn pending_block_num_hash(&self) -> Option { - let canonical_tip = self.canonical_tip(); - let hash = self.fork_to_child.get(&canonical_tip.hash)?.front().copied()?; - Some(BlockNumHash { number: canonical_tip.number + 1, hash }) - } - - /// Returns all pending block hashes. - /// - /// Pending blocks are considered blocks that are extending the canonical tip by one block - /// number and have their parent hash set to the canonical tip. - pub fn pending_blocks(&self) -> (BlockNumber, Vec) { - let canonical_tip = self.canonical_tip(); - let pending_blocks = self - .fork_to_child - .get(&canonical_tip.hash) - .cloned() - .unwrap_or_default() - .into_iter() - .collect(); - (canonical_tip.number + 1, pending_blocks) - } - - /// Last finalized block - pub const fn last_finalized_block(&self) -> BlockNumber { - self.last_finalized_block - } - - /// Insert non fork block. - pub(crate) fn insert_non_fork_block( - &mut self, - block_number: BlockNumber, - block_hash: BlockHash, - chain_id: SidechainId, - ) { - self.block_number_to_block_hashes.entry(block_number).or_default().insert(block_hash); - self.blocks_to_chain.insert(block_hash, chain_id); - } - - /// Insert block to chain and fork child indices of the new chain - pub(crate) fn insert_chain(&mut self, chain_id: SidechainId, chain: &Chain) { - for (number, block) in chain.blocks() { - // add block -> chain_id index - self.blocks_to_chain.insert(block.hash(), chain_id); - // add number -> block - self.block_number_to_block_hashes.entry(*number).or_default().insert(block.hash()); - } - let first = chain.first(); - // add parent block -> block index - self.fork_to_child.entry(first.parent_hash).or_default().insert_if_absent(first.hash()); - } - - /// Get the [`SidechainId`] for the given block hash if it exists. - pub(crate) fn get_side_chain_id(&self, block: &BlockHash) -> Option { - self.blocks_to_chain.get(block).copied() - } - - /// Update all block hashes. iterate over present and new list of canonical hashes and compare - /// them. Remove all mismatches, disconnect them and return all chains that needs to be - /// removed. - pub(crate) fn update_block_hashes( - &mut self, - hashes: BTreeMap, - ) -> (BTreeSet, Vec) { - // set new canonical hashes. - self.canonical_chain.replace(hashes.clone()); - - let mut new_hashes = hashes.into_iter(); - let mut old_hashes = self.canonical_chain().clone().into_iter(); - - let mut removed = Vec::new(); - let mut added = Vec::new(); - - let mut new_hash = new_hashes.next(); - let mut old_hash = old_hashes.next(); - - loop { - let Some(old_block_value) = old_hash else { - // end of old_hashes canonical chain. New chain has more blocks than old chain. - while let Some(new) = new_hash { - // add new blocks to added list. - added.push(new.into()); - new_hash = new_hashes.next(); - } - break - }; - let Some(new_block_value) = new_hash else { - // Old canonical chain had more block than new chain. - // remove all present block. - // this is mostly not going to happen as reorg should make new chain in Tree. - while let Some(rem) = old_hash { - removed.push(rem); - old_hash = old_hashes.next(); - } - break - }; - // compare old and new canonical block number - match new_block_value.0.cmp(&old_block_value.0) { - std::cmp::Ordering::Less => { - // new chain has more past blocks than old chain - added.push(new_block_value.into()); - new_hash = new_hashes.next(); - } - std::cmp::Ordering::Equal => { - if new_block_value.1 != old_block_value.1 { - // remove block hash as it is different - removed.push(old_block_value); - added.push(new_block_value.into()) - } - new_hash = new_hashes.next(); - old_hash = old_hashes.next(); - } - std::cmp::Ordering::Greater => { - // old chain has more past blocks than new chain - removed.push(old_block_value); - old_hash = old_hashes.next() - } - } - } - - // remove children of removed blocks - ( - removed.into_iter().fold(BTreeSet::new(), |mut fold, (number, hash)| { - fold.extend(self.remove_block(number, hash)); - fold - }), - added, - ) - } - - /// Remove chain from indices and return dependent chains that need to be removed. - /// Does the cleaning of the tree and removing blocks from the chain. - pub(crate) fn remove_chain(&mut self, chain: &Chain) -> BTreeSet { - chain - .blocks() - .iter() - .flat_map(|(block_number, block)| { - let block_hash = block.hash(); - self.remove_block(*block_number, block_hash) - }) - .collect() - } - - /// Remove Blocks from indices. - fn remove_block( - &mut self, - block_number: BlockNumber, - block_hash: BlockHash, - ) -> BTreeSet { - // rm number -> block - if let btree_map::Entry::Occupied(mut entry) = - self.block_number_to_block_hashes.entry(block_number) - { - let set = entry.get_mut(); - set.remove(&block_hash); - // remove set if empty - if set.is_empty() { - entry.remove(); - } - } - - // rm block -> chain_id - self.blocks_to_chain.remove(&block_hash); - - // rm fork -> child - let removed_fork = self.fork_to_child.remove(&block_hash); - removed_fork - .map(|fork_blocks| { - fork_blocks - .into_iter() - .filter_map(|fork_child| self.blocks_to_chain.remove(&fork_child)) - .collect() - }) - .unwrap_or_default() - } - - /// Remove all blocks from canonical list and insert new blocks to it. - /// - /// It is assumed that blocks are interconnected and that they connect to canonical chain - pub fn canonicalize_blocks(&mut self, blocks: &BTreeMap) { - if blocks.is_empty() { - return - } - - // Remove all blocks from canonical chain - let first_number = *blocks.first_key_value().unwrap().0; - - // this will remove all blocks numbers that are going to be replaced. - self.canonical_chain.retain(|&number, _| number < first_number); - - // remove them from block to chain_id index - blocks.iter().map(|(_, b)| (b.number, b.hash(), b.parent_hash)).for_each( - |(number, hash, parent_hash)| { - // rm block -> chain_id - self.blocks_to_chain.remove(&hash); - - // rm number -> block - if let btree_map::Entry::Occupied(mut entry) = - self.block_number_to_block_hashes.entry(number) - { - let set = entry.get_mut(); - set.remove(&hash); - // remove set if empty - if set.is_empty() { - entry.remove(); - } - } - // rm fork block -> hash - if let hash_map::Entry::Occupied(mut entry) = self.fork_to_child.entry(parent_hash) - { - let set = entry.get_mut(); - set.remove(&hash); - // remove set if empty - if set.is_empty() { - entry.remove(); - } - } - }, - ); - - // insert new canonical - self.canonical_chain.extend(blocks.iter().map(|(number, block)| (*number, block.hash()))) - } - - /// this is function that is going to remove N number of last canonical hashes. - /// - /// NOTE: This is not safe standalone, as it will not disconnect - /// blocks that depend on unwinded canonical chain. And should be - /// used when canonical chain is reinserted inside Tree. - pub(crate) fn unwind_canonical_chain(&mut self, unwind_to: BlockNumber) { - // this will remove all blocks numbers that are going to be replaced. - self.canonical_chain.retain(|num, _| *num <= unwind_to); - } - - /// Used for finalization of block. - /// - /// Return list of chains for removal that depend on finalized canonical chain. - pub(crate) fn finalize_canonical_blocks( - &mut self, - finalized_block: BlockNumber, - num_of_additional_canonical_hashes_to_retain: u64, - ) -> BTreeSet { - // get finalized chains. blocks between [self.last_finalized,finalized_block). - // Dont remove finalized_block, as sidechain can point to it. - let finalized_blocks: Vec = self - .canonical_chain - .iter() - .filter(|(number, _)| *number >= self.last_finalized_block && *number < finalized_block) - .map(|(_, hash)| hash) - .collect(); - - // remove unneeded canonical hashes. - let remove_until = - finalized_block.saturating_sub(num_of_additional_canonical_hashes_to_retain); - self.canonical_chain.retain(|&number, _| number >= remove_until); - - let mut lose_chains = BTreeSet::new(); - - for block_hash in finalized_blocks { - // there is a fork block. - if let Some(fork_blocks) = self.fork_to_child.remove(&block_hash) { - lose_chains = fork_blocks.into_iter().fold(lose_chains, |mut fold, fork_child| { - if let Some(lose_chain) = self.blocks_to_chain.remove(&fork_child) { - fold.insert(lose_chain); - } - fold - }); - } - } - - // set last finalized block. - self.last_finalized_block = finalized_block; - - lose_chains - } - - /// Returns the block hash of the canonical block with the given number. - #[inline] - pub fn canonical_hash(&self, block_number: &BlockNumber) -> Option { - self.canonical_chain.canonical_hash(block_number) - } - - /// Returns the block number of the canonical block with the given hash. - #[inline] - pub fn canonical_number(&self, block_hash: &BlockHash) -> Option { - self.canonical_chain.canonical_number(block_hash) - } - - /// get canonical tip - #[inline] - pub fn canonical_tip(&self) -> BlockNumHash { - self.canonical_chain.tip() - } - - /// Canonical chain needed for execution of EVM. It should contain last 256 block hashes. - #[inline] - pub(crate) const fn canonical_chain(&self) -> &CanonicalChain { - &self.canonical_chain - } -} - -#[cfg(test)] -mod tests { - use super::*; - use alloy_consensus::Header; - use alloy_primitives::B256; - use reth_primitives::{SealedBlock, SealedHeader}; - - #[test] - fn pending_block_num_hash_returns_none_if_no_fork() { - // Create a new canonical chain with a single block (represented by its number and hash). - let canonical_chain = BTreeMap::from([(0, B256::from_slice(&[1; 32]))]); - - let block_indices = BlockIndices::new(0, canonical_chain); - - // No fork to child blocks, so there is no pending block. - assert_eq!(block_indices.pending_block_num_hash(), None); - } - - #[test] - fn pending_block_num_hash_works() { - // Create a canonical chain with multiple blocks at heights 1, 2, and 3. - let canonical_chain = BTreeMap::from([ - (1, B256::from_slice(&[1; 32])), - (2, B256::from_slice(&[2; 32])), - (3, B256::from_slice(&[3; 32])), - ]); - - let mut block_indices = BlockIndices::new(3, canonical_chain); - - // Define the hash of the parent block (the block at height 3 in the canonical chain). - let parent_hash = B256::from_slice(&[3; 32]); - - // Define the hashes of two child blocks that extend the canonical chain. - let child_hash_1 = B256::from_slice(&[2; 32]); - let child_hash_2 = B256::from_slice(&[3; 32]); - - // Create a set to store both child block hashes. - let mut child_set = LinkedHashSet::new(); - child_set.insert(child_hash_1); - child_set.insert(child_hash_2); - - // Associate the parent block hash with its children in the fork_to_child mapping. - block_indices.fork_to_child.insert(parent_hash, child_set); - - // Pending block should be the first child block. - assert_eq!( - block_indices.pending_block_num_hash(), - Some(BlockNumHash { number: 4, hash: child_hash_1 }) - ); - } - - #[test] - fn pending_blocks_returns_empty_if_no_fork() { - // Create a canonical chain with a single block at height 10. - let canonical_chain = BTreeMap::from([(10, B256::from_slice(&[1; 32]))]); - let block_indices = BlockIndices::new(0, canonical_chain); - - // No child blocks are associated with the canonical tip. - assert_eq!(block_indices.pending_blocks(), (11, Vec::new())); - } - - #[test] - fn pending_blocks_returns_multiple_children() { - // Define the hash of the parent block (the block at height 5 in the canonical chain). - let parent_hash = B256::from_slice(&[3; 32]); - - // Create a canonical chain with a block at height 5. - let canonical_chain = BTreeMap::from([(5, parent_hash)]); - let mut block_indices = BlockIndices::new(0, canonical_chain); - - // Define the hashes of two child blocks. - let child_hash_1 = B256::from_slice(&[4; 32]); - let child_hash_2 = B256::from_slice(&[5; 32]); - - // Create a set to store both child block hashes. - let mut child_set = LinkedHashSet::new(); - child_set.insert(child_hash_1); - child_set.insert(child_hash_2); - - // Associate the parent block hash with its children. - block_indices.fork_to_child.insert(parent_hash, child_set); - - // Pending blocks should be the two child blocks. - assert_eq!(block_indices.pending_blocks(), (6, vec![child_hash_1, child_hash_2])); - } - - #[test] - fn pending_blocks_with_multiple_forked_chains() { - // Define hashes for parent blocks and child blocks. - let parent_hash_1 = B256::from_slice(&[6; 32]); - let parent_hash_2 = B256::from_slice(&[7; 32]); - - // Create a canonical chain with blocks at heights 1 and 2. - let canonical_chain = BTreeMap::from([(1, parent_hash_1), (2, parent_hash_2)]); - - let mut block_indices = BlockIndices::new(2, canonical_chain); - - // Define hashes for child blocks. - let child_hash_1 = B256::from_slice(&[8; 32]); - let child_hash_2 = B256::from_slice(&[9; 32]); - - // Create sets to store child blocks for each parent block. - let mut child_set_1 = LinkedHashSet::new(); - let mut child_set_2 = LinkedHashSet::new(); - child_set_1.insert(child_hash_1); - child_set_2.insert(child_hash_2); - - // Associate parent block hashes with their child blocks. - block_indices.fork_to_child.insert(parent_hash_1, child_set_1); - block_indices.fork_to_child.insert(parent_hash_2, child_set_2); - - // Check that the pending blocks are only those extending the canonical tip. - assert_eq!(block_indices.pending_blocks(), (3, vec![child_hash_2])); - } - - #[test] - fn insert_non_fork_block_adds_block_correctly() { - // Create a new BlockIndices instance with an empty state. - let mut block_indices = BlockIndices::new(0, BTreeMap::new()); - - // Define test parameters. - let block_number = 1; - let block_hash = B256::from_slice(&[1; 32]); - let chain_id = SidechainId::from(42); - - // Insert the block into the BlockIndices instance. - block_indices.insert_non_fork_block(block_number, block_hash, chain_id); - - // Check that the block number to block hashes mapping includes the new block hash. - assert_eq!( - block_indices.block_number_to_block_hashes.get(&block_number), - Some(&HashSet::from([block_hash])) - ); - - // Check that the block hash to chain ID mapping includes the new entry. - assert_eq!(block_indices.blocks_to_chain.get(&block_hash), Some(&chain_id)); - } - - #[test] - fn insert_non_fork_block_combined_tests() { - // Create a new BlockIndices instance with an empty state. - let mut block_indices = BlockIndices::new(0, BTreeMap::new()); - - // Define test parameters. - let block_number_1 = 2; - let block_hash_1 = B256::from_slice(&[1; 32]); - let block_hash_2 = B256::from_slice(&[2; 32]); - let chain_id_1 = SidechainId::from(84); - - let block_number_2 = 4; - let block_hash_3 = B256::from_slice(&[3; 32]); - let chain_id_2 = SidechainId::from(200); - - // Insert multiple hashes for the same block number. - block_indices.insert_non_fork_block(block_number_1, block_hash_1, chain_id_1); - block_indices.insert_non_fork_block(block_number_1, block_hash_2, chain_id_1); - - // Insert blocks with different numbers. - block_indices.insert_non_fork_block(block_number_2, block_hash_3, chain_id_2); - - // Block number 1 should have two block hashes associated with it. - let mut expected_hashes_for_block_1 = HashSet::default(); - expected_hashes_for_block_1.insert(block_hash_1); - expected_hashes_for_block_1.insert(block_hash_2); - assert_eq!( - block_indices.block_number_to_block_hashes.get(&block_number_1), - Some(&expected_hashes_for_block_1) - ); - - // Check that the block hashes for block_number_1 are associated with the correct chain ID. - assert_eq!(block_indices.blocks_to_chain.get(&block_hash_1), Some(&chain_id_1)); - assert_eq!(block_indices.blocks_to_chain.get(&block_hash_2), Some(&chain_id_1)); - - // Block number 2 should have a single block hash associated with it. - assert_eq!( - block_indices.block_number_to_block_hashes.get(&block_number_2), - Some(&HashSet::from([block_hash_3])) - ); - - // Block hash 3 should be associated with the correct chain ID. - assert_eq!(block_indices.blocks_to_chain.get(&block_hash_3), Some(&chain_id_2)); - } - - #[test] - fn insert_chain_validates_insertion() { - // Create a new BlockIndices instance with an empty state. - let mut block_indices = BlockIndices::new(0, BTreeMap::new()); - - // Define test parameters. - let chain_id = SidechainId::from(42); - - // Define some example blocks and their hashes. - let block_hash_1 = B256::from_slice(&[1; 32]); - let block_hash_2 = B256::from_slice(&[2; 32]); - let parent_hash = B256::from_slice(&[0; 32]); - - // Define blocks with their numbers and parent hashes. - let block_1 = SealedBlockWithSenders { - block: SealedBlock::new( - SealedHeader::new( - Header { parent_hash, number: 1, ..Default::default() }, - block_hash_1, - ), - Default::default(), - ), - ..Default::default() - }; - let block_2 = SealedBlockWithSenders { - block: SealedBlock::new( - SealedHeader::new( - Header { parent_hash: block_hash_1, number: 2, ..Default::default() }, - block_hash_2, - ), - Default::default(), - ), - ..Default::default() - }; - - // Define a chain containing the blocks. - let chain = Chain::new(vec![block_1, block_2], Default::default(), Default::default()); - - // Insert the chain into the BlockIndices. - block_indices.insert_chain(chain_id, &chain); - - // Check that the blocks are correctly mapped to the chain ID. - assert_eq!(block_indices.blocks_to_chain.get(&block_hash_1), Some(&chain_id)); - assert_eq!(block_indices.blocks_to_chain.get(&block_hash_2), Some(&chain_id)); - - // Check that block numbers map to their respective hashes. - let mut expected_hashes_1 = HashSet::default(); - expected_hashes_1.insert(block_hash_1); - assert_eq!(block_indices.block_number_to_block_hashes.get(&1), Some(&expected_hashes_1)); - - let mut expected_hashes_2 = HashSet::default(); - expected_hashes_2.insert(block_hash_2); - assert_eq!(block_indices.block_number_to_block_hashes.get(&2), Some(&expected_hashes_2)); - - // Check that the fork_to_child mapping contains the correct parent-child relationship. - // We take the first block of the chain. - let mut expected_children = LinkedHashSet::new(); - expected_children.insert(block_hash_1); - assert_eq!(block_indices.fork_to_child.get(&parent_hash), Some(&expected_children)); - } -} diff --git a/crates/blockchain-tree/src/blockchain_tree.rs b/crates/blockchain-tree/src/blockchain_tree.rs deleted file mode 100644 index 465f779e6..000000000 --- a/crates/blockchain-tree/src/blockchain_tree.rs +++ /dev/null @@ -1,2442 +0,0 @@ -//! Implementation of [`BlockchainTree`] - -use crate::{ - externals::TreeNodeTypes, - metrics::{MakeCanonicalAction, MakeCanonicalDurationsRecorder, TreeMetrics}, - state::{SidechainId, TreeState}, - AppendableChain, BlockIndices, BlockchainTreeConfig, ExecutionData, TreeExternals, -}; -use alloy_eips::{BlockNumHash, ForkBlock}; -use alloy_primitives::{BlockHash, BlockNumber, B256, U256}; -use reth_blockchain_tree_api::{ - error::{BlockchainTreeError, CanonicalError, InsertBlockError, InsertBlockErrorKind}, - BlockAttachment, BlockStatus, BlockValidationKind, CanonicalOutcome, InsertPayloadOk, -}; -use reth_consensus::{Consensus, ConsensusError}; -use reth_evm::execute::BlockExecutorProvider; -use reth_execution_errors::{BlockExecutionError, BlockValidationError}; -use reth_execution_types::{Chain, ExecutionOutcome}; -use reth_node_types::NodeTypesWithDB; -use reth_primitives::{ - EthereumHardfork, GotExpected, Hardforks, Receipt, SealedBlock, SealedBlockWithSenders, - SealedHeader, StaticFileSegment, -}; -use reth_provider::{ - BlockExecutionWriter, BlockNumReader, BlockWriter, CanonStateNotification, - CanonStateNotificationSender, CanonStateNotifications, ChainSpecProvider, ChainSplit, - ChainSplitTarget, DBProvider, DisplayBlocksChain, HashedPostStateProvider, HeaderProvider, - ProviderError, StaticFileProviderFactory, StorageLocation, -}; -use reth_stages_api::{MetricEvent, MetricEventsSender}; -use reth_storage_errors::provider::{ProviderResult, RootMismatch}; -use reth_trie::{hashed_cursor::HashedPostStateCursorFactory, StateRoot}; -use reth_trie_db::{DatabaseHashedCursorFactory, DatabaseStateRoot}; -use std::{ - collections::{btree_map::Entry, BTreeMap, HashSet}, - sync::Arc, -}; -use tracing::{debug, error, info, instrument, trace, warn}; - -#[cfg_attr(doc, aquamarine::aquamarine)] -/// A Tree of chains. -/// -/// The flowchart represents all the states a block can have inside the tree. -/// -/// - Green blocks belong to the canonical chain and are saved inside the database. -/// - Pending blocks and sidechains are found in-memory inside [`BlockchainTree`]. -/// -/// Both pending chains and sidechains have the same mechanisms, the only difference is when they -/// get committed to the database. -/// -/// For pending, it is an append operation, but for sidechains they need to move the current -/// canonical blocks to the tree (by removing them from the database), and commit the sidechain -/// blocks to the database to become the canonical chain (reorg). -/// -/// `include_mmd!("docs/mermaid/tree.mmd`") -/// -/// # Main functions -/// * [`BlockchainTree::insert_block`]: Connect a block to a chain, execute it, and if valid, insert -/// the block into the tree. -/// * [`BlockchainTree::finalize_block`]: Remove chains that branch off of the now finalized block. -/// * [`BlockchainTree::make_canonical`]: Check if we have the hash of a block that is the current -/// canonical head and commit it to db. -#[derive(Debug)] -pub struct BlockchainTree { - /// The state of the tree - /// - /// Tracks all the chains, the block indices, and the block buffer. - state: TreeState, - /// External components (the database, consensus engine etc.) - externals: TreeExternals, - /// Tree configuration - config: BlockchainTreeConfig, - /// Broadcast channel for canon state changes notifications. - canon_state_notification_sender: CanonStateNotificationSender, - /// Metrics for sync stages. - sync_metrics_tx: Option, - /// Metrics for the blockchain tree. - metrics: TreeMetrics, -} - -impl BlockchainTree { - /// Subscribe to new blocks events. - /// - /// Note: Only canonical blocks are emitted by the tree. - pub fn subscribe_canon_state(&self) -> CanonStateNotifications { - self.canon_state_notification_sender.subscribe() - } - - /// Returns a clone of the sender for the canonical state notifications. - pub fn canon_state_notification_sender(&self) -> CanonStateNotificationSender { - self.canon_state_notification_sender.clone() - } -} - -impl BlockchainTree -where - N: TreeNodeTypes, - E: BlockExecutorProvider, -{ - /// Builds the blockchain tree for the node. - /// - /// This method configures the blockchain tree, which is a critical component of the node, - /// responsible for managing the blockchain state, including blocks, transactions, and receipts. - /// It integrates with the consensus mechanism and the EVM for executing transactions. - /// - /// # Parameters - /// - `externals`: External components required by the blockchain tree: - /// - `provider_factory`: A factory for creating various blockchain-related providers, such - /// as for accessing the database or static files. - /// - `consensus`: The consensus configuration, which defines how the node reaches agreement - /// on the blockchain state with other nodes. - /// - `evm_config`: The EVM (Ethereum Virtual Machine) configuration, which affects how - /// smart contracts and transactions are executed. Proper validation of this configuration - /// is crucial for the correct execution of transactions. - /// - `tree_config`: Configuration for the blockchain tree, including any parameters that affect - /// its structure or performance. - pub fn new( - externals: TreeExternals, - config: BlockchainTreeConfig, - ) -> ProviderResult { - let max_reorg_depth = config.max_reorg_depth() as usize; - // The size of the broadcast is twice the maximum reorg depth, because at maximum reorg - // depth at least N blocks must be sent at once. - let (canon_state_notification_sender, _receiver) = - tokio::sync::broadcast::channel(max_reorg_depth * 2); - - let last_canonical_hashes = - externals.fetch_latest_canonical_hashes(config.num_of_canonical_hashes() as usize)?; - - // If we haven't written the finalized block, assume it's zero - let last_finalized_block_number = - externals.fetch_latest_finalized_block_number()?.unwrap_or_default(); - - Ok(Self { - externals, - state: TreeState::new( - last_finalized_block_number, - last_canonical_hashes, - config.max_unconnected_blocks(), - ), - config, - canon_state_notification_sender, - sync_metrics_tx: None, - metrics: Default::default(), - }) - } - - /// Replaces the canon state notification sender. - /// - /// Caution: this will close any existing subscriptions to the previous sender. - #[doc(hidden)] - pub fn with_canon_state_notification_sender( - mut self, - canon_state_notification_sender: CanonStateNotificationSender, - ) -> Self { - self.canon_state_notification_sender = canon_state_notification_sender; - self - } - - /// Set the sync metric events sender. - /// - /// A transmitter for sending synchronization metrics. This is used for monitoring the node's - /// synchronization process with the blockchain network. - pub fn with_sync_metrics_tx(mut self, metrics_tx: MetricEventsSender) -> Self { - self.sync_metrics_tx = Some(metrics_tx); - self - } - - /// Check if the block is known to blockchain tree or database and return its status. - /// - /// Function will check: - /// * if block is inside database returns [`BlockStatus::Valid`]. - /// * if block is inside buffer returns [`BlockStatus::Disconnected`]. - /// * if block is part of the canonical returns [`BlockStatus::Valid`]. - /// - /// Returns an error if - /// - an error occurred while reading from the database. - /// - the block is already finalized - pub(crate) fn is_block_known( - &self, - block: BlockNumHash, - ) -> Result, InsertBlockErrorKind> { - // check if block is canonical - if self.is_block_hash_canonical(&block.hash)? { - return Ok(Some(BlockStatus::Valid(BlockAttachment::Canonical))); - } - - let last_finalized_block = self.block_indices().last_finalized_block(); - // check db if block is finalized. - if block.number <= last_finalized_block { - // check if block is inside database - if self.externals.provider_factory.provider()?.block_number(block.hash)?.is_some() { - return Ok(Some(BlockStatus::Valid(BlockAttachment::Canonical))); - } - - return Err(BlockchainTreeError::PendingBlockIsFinalized { - last_finalized: last_finalized_block, - } - .into()) - } - - // is block inside chain - if let Some(attachment) = self.is_block_inside_sidechain(&block) { - return Ok(Some(BlockStatus::Valid(attachment))); - } - - // check if block is disconnected - if let Some(block) = self.state.buffered_blocks.block(&block.hash) { - return Ok(Some(BlockStatus::Disconnected { - head: self.state.block_indices.canonical_tip(), - missing_ancestor: block.parent_num_hash(), - })) - } - - Ok(None) - } - - /// Expose internal indices of the `BlockchainTree`. - #[inline] - pub const fn block_indices(&self) -> &BlockIndices { - self.state.block_indices() - } - - /// Returns the block with matching hash from any side-chain. - /// - /// Caution: This will not return blocks from the canonical chain. - #[inline] - pub fn sidechain_block_by_hash(&self, block_hash: BlockHash) -> Option<&SealedBlock> { - self.state.block_by_hash(block_hash) - } - - /// Returns the block with matching hash from any side-chain. - /// - /// Caution: This will not return blocks from the canonical chain. - #[inline] - pub fn block_with_senders_by_hash( - &self, - block_hash: BlockHash, - ) -> Option<&SealedBlockWithSenders> { - self.state.block_with_senders_by_hash(block_hash) - } - - /// Returns the block's receipts with matching hash from any side-chain. - /// - /// Caution: This will not return blocks from the canonical chain. - pub fn receipts_by_block_hash(&self, block_hash: BlockHash) -> Option> { - self.state.receipts_by_block_hash(block_hash) - } - - /// Returns the block that's considered the `Pending` block, if it exists. - pub fn pending_block(&self) -> Option<&SealedBlock> { - let b = self.block_indices().pending_block_num_hash()?; - self.sidechain_block_by_hash(b.hash) - } - - /// Return items needed to execute on the pending state. - /// This includes: - /// * `BlockHash` of canonical block that chain connects to. Needed for creating database - /// provider for the rest of the state. - /// * `BundleState` changes that happened at the asked `block_hash` - /// * `BTreeMap` list of past pending and canonical hashes, That are - /// needed for evm `BLOCKHASH` opcode. - /// Return none if: - /// * block unknown. - /// * `chain_id` not present in state. - /// * there are no parent hashes stored. - pub fn post_state_data(&self, block_hash: BlockHash) -> Option { - trace!(target: "blockchain_tree", ?block_hash, "Searching for post state data"); - - let canonical_chain = self.state.block_indices.canonical_chain(); - - // if it is part of the chain - if let Some(chain_id) = self.block_indices().get_side_chain_id(&block_hash) { - trace!(target: "blockchain_tree", ?block_hash, "Constructing post state data based on non-canonical chain"); - // get block state - let Some(chain) = self.state.chains.get(&chain_id) else { - debug!(target: "blockchain_tree", ?chain_id, "Chain with ID not present"); - return None; - }; - let block_number = chain.block_number(block_hash)?; - let execution_outcome = chain.execution_outcome_at_block(block_number)?; - - // get parent hashes - let mut parent_block_hashes = self.all_chain_hashes(chain_id); - let Some((first_pending_block_number, _)) = parent_block_hashes.first_key_value() - else { - debug!(target: "blockchain_tree", ?chain_id, "No block hashes stored"); - return None; - }; - let canonical_chain = canonical_chain - .iter() - .filter(|&(key, _)| &key < first_pending_block_number) - .collect::>(); - parent_block_hashes.extend(canonical_chain); - - // get canonical fork. - let canonical_fork = self.canonical_fork(chain_id)?; - return Some(ExecutionData { execution_outcome, parent_block_hashes, canonical_fork }); - } - - // check if there is canonical block - if let Some(canonical_number) = canonical_chain.canonical_number(&block_hash) { - trace!(target: "blockchain_tree", %block_hash, "Constructing post state data based on canonical chain"); - return Some(ExecutionData { - canonical_fork: ForkBlock { number: canonical_number, hash: block_hash }, - execution_outcome: ExecutionOutcome::default(), - parent_block_hashes: canonical_chain.inner().clone(), - }); - } - - None - } - - /// Try inserting a validated [Self::validate_block] block inside the tree. - /// - /// If the block's parent block is unknown, this returns [`BlockStatus::Disconnected`] and the - /// block will be buffered until the parent block is inserted and then attached to sidechain - #[instrument(level = "trace", skip_all, fields(block = ?block.num_hash()), target = "blockchain_tree", ret)] - fn try_insert_validated_block( - &mut self, - block: SealedBlockWithSenders, - block_validation_kind: BlockValidationKind, - ) -> Result { - debug_assert!(self.validate_block(&block).is_ok(), "Block must be validated"); - - let parent = block.parent_num_hash(); - - // check if block parent can be found in any side chain. - if let Some(chain_id) = self.block_indices().get_side_chain_id(&parent.hash) { - // found parent in side tree, try to insert there - return self.try_insert_block_into_side_chain(block, chain_id, block_validation_kind); - } - - // if not found, check if the parent can be found inside canonical chain. - if self.is_block_hash_canonical(&parent.hash)? { - return self.try_append_canonical_chain(block.clone(), block_validation_kind); - } - - // this is another check to ensure that if the block points to a canonical block its block - // is valid - if let Some(canonical_parent_number) = - self.block_indices().canonical_number(&block.parent_hash) - { - // we found the parent block in canonical chain - if canonical_parent_number != parent.number { - return Err(ConsensusError::ParentBlockNumberMismatch { - parent_block_number: canonical_parent_number, - block_number: block.number, - } - .into()) - } - } - - // if there is a parent inside the buffer, validate against it. - if let Some(buffered_parent) = self.state.buffered_blocks.block(&parent.hash) { - self.externals.consensus.validate_header_against_parent(&block, buffered_parent)?; - } - - // insert block inside unconnected block buffer. Delaying its execution. - self.state.buffered_blocks.insert_block(block.clone()); - - let block_hash = block.hash(); - // find the lowest ancestor of the block in the buffer to return as the missing parent - // this shouldn't return None because that only happens if the block was evicted, which - // shouldn't happen right after insertion - let lowest_ancestor = self - .state - .buffered_blocks - .lowest_ancestor(&block_hash) - .ok_or(BlockchainTreeError::BlockBufferingFailed { block_hash })?; - - Ok(BlockStatus::Disconnected { - head: self.state.block_indices.canonical_tip(), - missing_ancestor: lowest_ancestor.parent_num_hash(), - }) - } - - /// This tries to append the given block to the canonical chain. - /// - /// WARNING: this expects that the block extends the canonical chain: The block's parent is - /// part of the canonical chain (e.g. the block's parent is the latest canonical hash). See also - /// [Self::is_block_hash_canonical]. - #[instrument(level = "trace", skip_all, target = "blockchain_tree")] - fn try_append_canonical_chain( - &mut self, - block: SealedBlockWithSenders, - block_validation_kind: BlockValidationKind, - ) -> Result { - let parent = block.parent_num_hash(); - let block_num_hash = block.num_hash(); - debug!(target: "blockchain_tree", head = ?block_num_hash.hash, ?parent, "Appending block to canonical chain"); - - let provider = self.externals.provider_factory.provider()?; - - // Validate that the block is post merge - let parent_td = provider - .header_td(&block.parent_hash)? - .ok_or_else(|| BlockchainTreeError::CanonicalChain { block_hash: block.parent_hash })?; - - if !self - .externals - .provider_factory - .chain_spec() - .fork(EthereumHardfork::Paris) - .active_at_ttd(parent_td, U256::ZERO) - { - return Err(BlockExecutionError::Validation(BlockValidationError::BlockPreMerge { - hash: block.hash(), - }) - .into()) - } - - let parent_header = provider - .header(&block.parent_hash)? - .ok_or_else(|| BlockchainTreeError::CanonicalChain { block_hash: block.parent_hash })?; - - let parent_sealed_header = SealedHeader::new(parent_header, block.parent_hash); - - let canonical_chain = self.state.block_indices.canonical_chain(); - - let block_attachment = if block.parent_hash == canonical_chain.tip().hash { - BlockAttachment::Canonical - } else { - BlockAttachment::HistoricalFork - }; - - let chain = AppendableChain::new_canonical_fork( - block, - &parent_sealed_header, - canonical_chain.inner(), - parent, - &self.externals, - block_attachment, - block_validation_kind, - )?; - - self.insert_chain(chain); - self.try_connect_buffered_blocks(block_num_hash); - - Ok(BlockStatus::Valid(block_attachment)) - } - - /// Try inserting a block into the given side chain. - /// - /// WARNING: This expects a valid side chain id, see [BlockIndices::get_side_chain_id] - #[instrument(level = "trace", skip_all, target = "blockchain_tree")] - fn try_insert_block_into_side_chain( - &mut self, - block: SealedBlockWithSenders, - chain_id: SidechainId, - block_validation_kind: BlockValidationKind, - ) -> Result { - let block_num_hash = block.num_hash(); - debug!(target: "blockchain_tree", ?block_num_hash, ?chain_id, "Inserting block into side chain"); - // Create a new sidechain by forking the given chain, or append the block if the parent - // block is the top of the given chain. - let block_hashes = self.all_chain_hashes(chain_id); - - // get canonical fork. - let canonical_fork = self.canonical_fork(chain_id).ok_or_else(|| { - BlockchainTreeError::BlockSideChainIdConsistency { chain_id: chain_id.into() } - })?; - - // get chain that block needs to join to. - let parent_chain = self.state.chains.get_mut(&chain_id).ok_or_else(|| { - BlockchainTreeError::BlockSideChainIdConsistency { chain_id: chain_id.into() } - })?; - - let chain_tip = parent_chain.tip().hash(); - let canonical_chain = self.state.block_indices.canonical_chain(); - - // append the block if it is continuing the side chain. - let block_attachment = if chain_tip == block.parent_hash { - // check if the chain extends the currently tracked canonical head - let block_attachment = if canonical_fork.hash == canonical_chain.tip().hash { - BlockAttachment::Canonical - } else { - BlockAttachment::HistoricalFork - }; - - let block_hash = block.hash(); - let block_number = block.number; - debug!(target: "blockchain_tree", ?block_hash, ?block_number, "Appending block to side chain"); - parent_chain.append_block( - block, - block_hashes, - canonical_chain.inner(), - &self.externals, - canonical_fork, - block_attachment, - block_validation_kind, - )?; - - self.state.block_indices.insert_non_fork_block(block_number, block_hash, chain_id); - block_attachment - } else { - debug!(target: "blockchain_tree", ?canonical_fork, "Starting new fork from side chain"); - // the block starts a new fork - let chain = parent_chain.new_chain_fork( - block, - block_hashes, - canonical_chain.inner(), - canonical_fork, - &self.externals, - block_validation_kind, - )?; - self.insert_chain(chain); - BlockAttachment::HistoricalFork - }; - - // After we inserted the block, we try to connect any buffered blocks - self.try_connect_buffered_blocks(block_num_hash); - - Ok(BlockStatus::Valid(block_attachment)) - } - - /// Get all block hashes from a sidechain that are not part of the canonical chain. - /// This is a one time operation per block. - /// - /// # Note - /// - /// This is not cached in order to save memory. - fn all_chain_hashes(&self, chain_id: SidechainId) -> BTreeMap { - let mut chain_id = chain_id; - let mut hashes = BTreeMap::new(); - loop { - let Some(chain) = self.state.chains.get(&chain_id) else { return hashes }; - - // The parent chains might contain blocks with overlapping numbers or numbers greater - // than original chain tip. Insert the block hash only if it's not present - // for the given block number and the block number does not exceed the - // original chain tip. - let latest_block_number = hashes - .last_key_value() - .map(|(number, _)| *number) - .unwrap_or_else(|| chain.tip().number); - for block in chain.blocks().values().filter(|b| b.number <= latest_block_number) { - if let Entry::Vacant(e) = hashes.entry(block.number) { - e.insert(block.hash()); - } - } - - let fork_block = chain.fork_block(); - if let Some(next_chain_id) = self.block_indices().get_side_chain_id(&fork_block.hash) { - chain_id = next_chain_id; - } else { - // if there is no fork block that point to other chains, break the loop. - // it means that this fork joins to canonical block. - break - } - } - hashes - } - - /// Get the block at which the given chain forks off the current canonical chain. - /// - /// This is used to figure out what kind of state provider the executor should use to execute - /// the block on - /// - /// Returns `None` if the chain is unknown. - fn canonical_fork(&self, chain_id: SidechainId) -> Option { - let mut chain_id = chain_id; - let mut fork; - loop { - // chain fork block - fork = self.state.chains.get(&chain_id)?.fork_block(); - // get fork block chain - if let Some(fork_chain_id) = self.block_indices().get_side_chain_id(&fork.hash) { - chain_id = fork_chain_id; - continue - } - break - } - (self.block_indices().canonical_hash(&fork.number) == Some(fork.hash)).then_some(fork) - } - - /// Insert a chain into the tree. - /// - /// Inserts a chain into the tree and builds the block indices. - fn insert_chain(&mut self, chain: AppendableChain) -> Option { - self.state.insert_chain(chain) - } - - /// Iterate over all child chains that depend on this block and return - /// their ids. - fn find_all_dependent_chains(&self, block: &BlockHash) -> HashSet { - // Find all forks of given block. - let mut dependent_block = - self.block_indices().fork_to_child().get(block).cloned().unwrap_or_default(); - let mut dependent_chains = HashSet::default(); - - while let Some(block) = dependent_block.pop_back() { - // Get chain of dependent block. - let Some(chain_id) = self.block_indices().get_side_chain_id(&block) else { - debug!(target: "blockchain_tree", ?block, "Block not in tree"); - return Default::default(); - }; - - // Find all blocks that fork from this chain. - let Some(chain) = self.state.chains.get(&chain_id) else { - debug!(target: "blockchain_tree", ?chain_id, "Chain not in tree"); - return Default::default(); - }; - for chain_block in chain.blocks().values() { - if let Some(forks) = self.block_indices().fork_to_child().get(&chain_block.hash()) { - // If there are sub forks append them for processing. - dependent_block.extend(forks); - } - } - // Insert dependent chain id. - dependent_chains.insert(chain_id); - } - dependent_chains - } - - /// Inserts unwound chain back into the tree and updates any dependent chains. - /// - /// This method searches for any chain that depended on this block being part of the canonical - /// chain. Each dependent chain's state is then updated with state entries removed from the - /// plain state during the unwind. - /// Returns the result of inserting the chain or None if any of the dependent chains is not - /// in the tree. - fn insert_unwound_chain(&mut self, chain: AppendableChain) -> Option { - // iterate over all blocks in chain and find any fork blocks that are in tree. - for (number, block) in chain.blocks() { - let hash = block.hash(); - - // find all chains that fork from this block. - let chains_to_bump = self.find_all_dependent_chains(&hash); - if !chains_to_bump.is_empty() { - // if there is such chain, revert state to this block. - let mut cloned_execution_outcome = chain.execution_outcome().clone(); - cloned_execution_outcome.revert_to(*number); - - // prepend state to all chains that fork from this block. - for chain_id in chains_to_bump { - let Some(chain) = self.state.chains.get_mut(&chain_id) else { - debug!(target: "blockchain_tree", ?chain_id, "Chain not in tree"); - return None; - }; - - debug!(target: "blockchain_tree", - unwound_block= ?block.num_hash(), - chain_id = ?chain_id, - chain_tip = ?chain.tip().num_hash(), - "Prepend unwound block state to blockchain tree chain"); - - chain.prepend_state(cloned_execution_outcome.state().clone()) - } - } - } - // Insert unwound chain to the tree. - self.insert_chain(chain) - } - - /// Checks the block buffer for the given block. - pub fn get_buffered_block(&self, hash: &BlockHash) -> Option<&SealedBlockWithSenders> { - self.state.get_buffered_block(hash) - } - - /// Gets the lowest ancestor for the given block in the block buffer. - pub fn lowest_buffered_ancestor(&self, hash: &BlockHash) -> Option<&SealedBlockWithSenders> { - self.state.lowest_buffered_ancestor(hash) - } - - /// Insert a new block into the tree. - /// - /// # Note - /// - /// This recovers transaction signers (unlike [`BlockchainTree::insert_block`]). - pub fn insert_block_without_senders( - &mut self, - block: SealedBlock, - ) -> Result { - match block.try_seal_with_senders() { - Ok(block) => self.insert_block(block, BlockValidationKind::Exhaustive), - Err(block) => Err(InsertBlockError::sender_recovery_error(block)), - } - } - - /// Insert block for future execution. - /// - /// Returns an error if the block is invalid. - pub fn buffer_block(&mut self, block: SealedBlockWithSenders) -> Result<(), InsertBlockError> { - // validate block consensus rules - if let Err(err) = self.validate_block(&block) { - return Err(InsertBlockError::consensus_error(err, block.block)); - } - - self.state.buffered_blocks.insert_block(block); - Ok(()) - } - - /// Validate if block is correct and satisfies all the consensus rules that concern the header - /// and block body itself. - fn validate_block(&self, block: &SealedBlockWithSenders) -> Result<(), ConsensusError> { - if let Err(e) = - self.externals.consensus.validate_header_with_total_difficulty(block, U256::MAX) - { - error!(?block, "Failed to validate total difficulty for block {}: {e}", block.hash()); - return Err(e); - } - - if let Err(e) = self.externals.consensus.validate_header(block) { - error!(?block, "Failed to validate header {}: {e}", block.hash()); - return Err(e); - } - - if let Err(e) = self.externals.consensus.validate_block_pre_execution(block) { - error!(?block, "Failed to validate block {}: {e}", block.hash()); - return Err(e); - } - - Ok(()) - } - - /// Check if block is found inside a sidechain and its attachment. - /// - /// if it is canonical or extends the canonical chain, return [`BlockAttachment::Canonical`] - /// if it does not extend the canonical chain, return [`BlockAttachment::HistoricalFork`] - /// if the block is not in the tree or its chain id is not valid, return None - #[track_caller] - fn is_block_inside_sidechain(&self, block: &BlockNumHash) -> Option { - // check if block known and is already in the tree - if let Some(chain_id) = self.block_indices().get_side_chain_id(&block.hash) { - // find the canonical fork of this chain - let Some(canonical_fork) = self.canonical_fork(chain_id) else { - debug!(target: "blockchain_tree", chain_id=?chain_id, block=?block.hash, "Chain id not valid"); - return None; - }; - // if the block's chain extends canonical chain - return if canonical_fork == self.block_indices().canonical_tip() { - Some(BlockAttachment::Canonical) - } else { - Some(BlockAttachment::HistoricalFork) - }; - } - None - } - - /// Insert a block (with recovered senders) into the tree. - /// - /// Returns the [`BlockStatus`] on success: - /// - /// - The block is already part of a sidechain in the tree, or - /// - The block is already part of the canonical chain, or - /// - The parent is part of a sidechain in the tree, and we can fork at this block, or - /// - The parent is part of the canonical chain, and we can fork at this block - /// - /// Otherwise, an error is returned, indicating that neither the block nor its parent are part - /// of the chain or any sidechains. - /// - /// This means that if the block becomes canonical, we need to fetch the missing blocks over - /// P2P. - /// - /// If the [`BlockValidationKind::SkipStateRootValidation`] variant is provided the state root - /// is not validated. - /// - /// # Note - /// - /// If the senders have not already been recovered, call - /// [`BlockchainTree::insert_block_without_senders`] instead. - pub fn insert_block( - &mut self, - block: SealedBlockWithSenders, - block_validation_kind: BlockValidationKind, - ) -> Result { - // check if we already have this block - match self.is_block_known(block.num_hash()) { - Ok(Some(status)) => return Ok(InsertPayloadOk::AlreadySeen(status)), - Err(err) => return Err(InsertBlockError::new(block.block, err)), - _ => {} - } - - // validate block consensus rules - if let Err(err) = self.validate_block(&block) { - return Err(InsertBlockError::consensus_error(err, block.block)); - } - - let status = self - .try_insert_validated_block(block.clone(), block_validation_kind) - .map_err(|kind| InsertBlockError::new(block.block, kind))?; - Ok(InsertPayloadOk::Inserted(status)) - } - - /// Discard all blocks that precede block number from the buffer. - pub fn remove_old_blocks(&mut self, block: BlockNumber) { - self.state.buffered_blocks.remove_old_blocks(block); - } - - /// Finalize blocks up until and including `finalized_block`, and remove them from the tree. - pub fn finalize_block(&mut self, finalized_block: BlockNumber) -> ProviderResult<()> { - // remove blocks - let mut remove_chains = self.state.block_indices.finalize_canonical_blocks( - finalized_block, - self.config.num_of_additional_canonical_block_hashes(), - ); - // remove chains of removed blocks - while let Some(chain_id) = remove_chains.pop_first() { - if let Some(chain) = self.state.chains.remove(&chain_id) { - remove_chains.extend(self.state.block_indices.remove_chain(&chain)); - } - } - // clean block buffer. - self.remove_old_blocks(finalized_block); - - // save finalized block in db. - self.externals.save_finalized_block_number(finalized_block)?; - - Ok(()) - } - - /// Reads the last `N` canonical hashes from the database and updates the block indices of the - /// tree by attempting to connect the buffered blocks to canonical hashes. - /// - /// - /// `N` is the maximum of `max_reorg_depth` and the number of block hashes needed to satisfy the - /// `BLOCKHASH` opcode in the EVM. - /// - /// # Note - /// - /// This finalizes `last_finalized_block` prior to reading the canonical hashes (using - /// [`BlockchainTree::finalize_block`]). - pub fn connect_buffered_blocks_to_canonical_hashes_and_finalize( - &mut self, - last_finalized_block: BlockNumber, - ) -> ProviderResult<()> { - self.finalize_block(last_finalized_block)?; - - let last_canonical_hashes = self.update_block_hashes()?; - - self.connect_buffered_blocks_to_hashes(last_canonical_hashes)?; - - Ok(()) - } - - /// Update all block hashes. iterate over present and new list of canonical hashes and compare - /// them. Remove all mismatches, disconnect them and removes all chains. - pub fn update_block_hashes(&mut self) -> ProviderResult> { - let last_canonical_hashes = self - .externals - .fetch_latest_canonical_hashes(self.config.num_of_canonical_hashes() as usize)?; - - let (mut remove_chains, _) = - self.state.block_indices.update_block_hashes(last_canonical_hashes.clone()); - - // remove all chains that got discarded - while let Some(chain_id) = remove_chains.first() { - if let Some(chain) = self.state.chains.remove(chain_id) { - remove_chains.extend(self.state.block_indices.remove_chain(&chain)); - } - } - - Ok(last_canonical_hashes) - } - - /// Update all block hashes. iterate over present and new list of canonical hashes and compare - /// them. Remove all mismatches, disconnect them, removes all chains and clears all buffered - /// blocks before the tip. - pub fn update_block_hashes_and_clear_buffered( - &mut self, - ) -> ProviderResult> { - let chain = self.update_block_hashes()?; - - if let Some((block, _)) = chain.last_key_value() { - self.remove_old_blocks(*block); - } - - Ok(chain) - } - - /// Reads the last `N` canonical hashes from the database and updates the block indices of the - /// tree by attempting to connect the buffered blocks to canonical hashes. - /// - /// `N` is the maximum of `max_reorg_depth` and the number of block hashes needed to satisfy the - /// `BLOCKHASH` opcode in the EVM. - pub fn connect_buffered_blocks_to_canonical_hashes(&mut self) -> ProviderResult<()> { - let last_canonical_hashes = self - .externals - .fetch_latest_canonical_hashes(self.config.num_of_canonical_hashes() as usize)?; - self.connect_buffered_blocks_to_hashes(last_canonical_hashes)?; - - Ok(()) - } - - fn connect_buffered_blocks_to_hashes( - &mut self, - hashes: impl IntoIterator>, - ) -> ProviderResult<()> { - // check unconnected block buffer for children of the canonical hashes - for added_block in hashes { - self.try_connect_buffered_blocks(added_block.into()) - } - - // check unconnected block buffer for children of the chains - let mut all_chain_blocks = Vec::new(); - for chain in self.state.chains.values() { - all_chain_blocks.reserve_exact(chain.blocks().len()); - for (&number, block) in chain.blocks() { - all_chain_blocks.push(BlockNumHash { number, hash: block.hash() }) - } - } - for block in all_chain_blocks { - self.try_connect_buffered_blocks(block) - } - - Ok(()) - } - - /// Connect unconnected (buffered) blocks if the new block closes a gap. - /// - /// This will try to insert all children of the new block, extending its chain. - /// - /// If all children are valid, then this essentially appends all child blocks to the - /// new block's chain. - fn try_connect_buffered_blocks(&mut self, new_block: BlockNumHash) { - trace!(target: "blockchain_tree", ?new_block, "try_connect_buffered_blocks"); - - // first remove all the children of the new block from the buffer - let include_blocks = self.state.buffered_blocks.remove_block_with_children(&new_block.hash); - // then try to reinsert them into the tree - for block in include_blocks { - // don't fail on error, just ignore the block. - let _ = self - .try_insert_validated_block(block, BlockValidationKind::SkipStateRootValidation) - .map_err(|err| { - debug!(target: "blockchain_tree", %err, "Failed to insert buffered block"); - err - }); - } - } - - /// Removes chain corresponding to provided chain id from block indices, - /// splits it at split target, and returns the canonical part of it. - /// Returns [None] if chain is missing. - /// - /// The pending part of the chain is reinserted back into the tree with the same `chain_id`. - fn remove_and_split_chain( - &mut self, - chain_id: SidechainId, - split_at: ChainSplitTarget, - ) -> Option { - let chain = self.state.chains.remove(&chain_id)?; - match chain.into_inner().split(split_at) { - ChainSplit::Split { canonical, pending } => { - trace!(target: "blockchain_tree", ?canonical, ?pending, "Split chain"); - // rest of split chain is inserted back with same chain_id. - self.state.block_indices.insert_chain(chain_id, &pending); - self.state.chains.insert(chain_id, AppendableChain::new(pending)); - Some(canonical) - } - ChainSplit::NoSplitCanonical(canonical) => { - trace!(target: "blockchain_tree", "No split on canonical chain"); - Some(canonical) - } - ChainSplit::NoSplitPending(_) => { - unreachable!("Should not happen as block indices guarantee structure of blocks") - } - } - } - - /// Attempts to find the header for the given block hash if it is canonical. - /// - /// Returns `Ok(None)` if the block hash is not canonical (block hash does not exist, or is - /// included in a sidechain). - /// - /// Note: this does not distinguish between a block that is finalized and a block that is not - /// finalized yet, only whether it is part of the canonical chain or not. - pub fn find_canonical_header( - &self, - hash: &BlockHash, - ) -> Result, ProviderError> { - // if the indices show that the block hash is not canonical, it's either in a sidechain or - // canonical, but in the db. If it is in a sidechain, it is not canonical. If it is missing - // in the db, then it is also not canonical. - - let provider = self.externals.provider_factory.provider()?; - - let mut header = None; - if let Some(num) = self.block_indices().canonical_number(hash) { - header = provider.header_by_number(num)?; - } - - if header.is_none() && self.sidechain_block_by_hash(*hash).is_some() { - return Ok(None) - } - - if header.is_none() { - header = provider.header(hash)? - } - - Ok(header.map(|header| SealedHeader::new(header, *hash))) - } - - /// Determines whether or not a block is canonical, checking the db if necessary. - /// - /// Note: this does not distinguish between a block that is finalized and a block that is not - /// finalized yet, only whether it is part of the canonical chain or not. - pub fn is_block_hash_canonical(&self, hash: &BlockHash) -> Result { - self.find_canonical_header(hash).map(|header| header.is_some()) - } - - /// Make a block and its parent(s) part of the canonical chain and commit them to the database - /// - /// # Note - /// - /// This unwinds the database if necessary, i.e. if parts of the canonical chain have been - /// reorged. - /// - /// # Returns - /// - /// Returns `Ok` if the blocks were canonicalized, or if the blocks were already canonical. - #[track_caller] - #[instrument(level = "trace", skip(self), target = "blockchain_tree")] - pub fn make_canonical( - &mut self, - block_hash: BlockHash, - ) -> Result { - let mut durations_recorder = MakeCanonicalDurationsRecorder::default(); - - let old_block_indices = self.block_indices().clone(); - let old_buffered_blocks = self.state.buffered_blocks.parent_to_child.clone(); - durations_recorder.record_relative(MakeCanonicalAction::CloneOldBlocks); - - // If block is already canonical don't return error. - let canonical_header = self.find_canonical_header(&block_hash)?; - durations_recorder.record_relative(MakeCanonicalAction::FindCanonicalHeader); - if let Some(header) = canonical_header { - info!(target: "blockchain_tree", %block_hash, "Block is already canonical, ignoring."); - // TODO: this could be fetched from the chainspec first - let td = - self.externals.provider_factory.provider()?.header_td(&block_hash)?.ok_or_else( - || { - CanonicalError::from(BlockValidationError::MissingTotalDifficulty { - hash: block_hash, - }) - }, - )?; - - if !self - .externals - .provider_factory - .chain_spec() - .fork(EthereumHardfork::Paris) - .active_at_ttd(td, U256::ZERO) - { - return Err(CanonicalError::from(BlockValidationError::BlockPreMerge { - hash: block_hash, - })) - } - - let head = self.state.block_indices.canonical_tip(); - return Ok(CanonicalOutcome::AlreadyCanonical { header, head }); - } - - let Some(chain_id) = self.block_indices().get_side_chain_id(&block_hash) else { - debug!(target: "blockchain_tree", ?block_hash, "Block hash not found in block indices"); - return Err(CanonicalError::from(BlockchainTreeError::BlockHashNotFoundInChain { - block_hash, - })) - }; - - // we are splitting chain at the block hash that we want to make canonical - let Some(canonical) = self.remove_and_split_chain(chain_id, block_hash.into()) else { - debug!(target: "blockchain_tree", ?block_hash, ?chain_id, "Chain not present"); - return Err(CanonicalError::from(BlockchainTreeError::BlockSideChainIdConsistency { - chain_id: chain_id.into(), - })) - }; - trace!(target: "blockchain_tree", chain = ?canonical, "Found chain to make canonical"); - durations_recorder.record_relative(MakeCanonicalAction::SplitChain); - - let mut fork_block = canonical.fork_block(); - let mut chains_to_promote = vec![canonical]; - - // loop while fork blocks are found in Tree. - while let Some(chain_id) = self.block_indices().get_side_chain_id(&fork_block.hash) { - // canonical chain is lower part of the chain. - let Some(canonical) = - self.remove_and_split_chain(chain_id, ChainSplitTarget::Number(fork_block.number)) - else { - debug!(target: "blockchain_tree", ?fork_block, ?chain_id, "Fork not present"); - return Err(CanonicalError::from( - BlockchainTreeError::BlockSideChainIdConsistency { chain_id: chain_id.into() }, - )); - }; - fork_block = canonical.fork_block(); - chains_to_promote.push(canonical); - } - durations_recorder.record_relative(MakeCanonicalAction::SplitChainForks); - - let old_tip = self.block_indices().canonical_tip(); - // Merge all chains into one chain. - let Some(mut new_canon_chain) = chains_to_promote.pop() else { - debug!(target: "blockchain_tree", "No blocks in the chain to make canonical"); - return Err(CanonicalError::from(BlockchainTreeError::BlockHashNotFoundInChain { - block_hash: fork_block.hash, - })) - }; - trace!(target: "blockchain_tree", ?new_canon_chain, "Merging chains"); - let mut chain_appended = false; - for chain in chains_to_promote.into_iter().rev() { - trace!(target: "blockchain_tree", ?chain, "Appending chain"); - let block_hash = chain.fork_block().hash; - new_canon_chain.append_chain(chain).map_err(|_| { - CanonicalError::from(BlockchainTreeError::BlockHashNotFoundInChain { block_hash }) - })?; - chain_appended = true; - } - durations_recorder.record_relative(MakeCanonicalAction::MergeAllChains); - - if chain_appended { - trace!(target: "blockchain_tree", ?new_canon_chain, "Canonical chain appended"); - } - // update canonical index - self.state.block_indices.canonicalize_blocks(new_canon_chain.blocks()); - durations_recorder.record_relative(MakeCanonicalAction::UpdateCanonicalIndex); - - debug!( - target: "blockchain_tree", - "Committing new canonical chain: {}", DisplayBlocksChain(new_canon_chain.blocks()) - ); - - // If chain extends the tip - let chain_notification = if new_canon_chain.fork_block().hash == old_tip.hash { - // Commit new canonical chain to database. - self.commit_canonical_to_database(new_canon_chain.clone(), &mut durations_recorder)?; - CanonStateNotification::Commit { new: Arc::new(new_canon_chain) } - } else { - // It forks to canonical block that is not the tip. - let canon_fork: BlockNumHash = new_canon_chain.fork_block(); - // sanity check - if self.block_indices().canonical_hash(&canon_fork.number) != Some(canon_fork.hash) { - error!( - target: "blockchain_tree", - ?canon_fork, - block_indices=?self.block_indices(), - "All chains should point to canonical chain" - ); - unreachable!("all chains should point to canonical chain."); - } - - let old_canon_chain = - self.revert_canonical_from_database(canon_fork.number).inspect_err(|error| { - error!( - target: "blockchain_tree", - "Reverting canonical chain failed with error: {:?}\n\ - Old BlockIndices are:{:?}\n\ - New BlockIndices are: {:?}\n\ - Old BufferedBlocks are:{:?}", - error, old_block_indices, self.block_indices(), old_buffered_blocks - ); - })?; - durations_recorder - .record_relative(MakeCanonicalAction::RevertCanonicalChainFromDatabase); - - // Commit new canonical chain. - self.commit_canonical_to_database(new_canon_chain.clone(), &mut durations_recorder)?; - - if let Some(old_canon_chain) = old_canon_chain { - self.update_reorg_metrics(old_canon_chain.len() as f64); - - // Insert old canonical chain back into tree. - self.insert_unwound_chain(AppendableChain::new(old_canon_chain.clone())); - durations_recorder.record_relative(MakeCanonicalAction::InsertOldCanonicalChain); - - CanonStateNotification::Reorg { - old: Arc::new(old_canon_chain), - new: Arc::new(new_canon_chain), - } - } else { - // error here to confirm that we are reverting nothing from db. - error!(target: "blockchain_tree", %block_hash, "Nothing was removed from database"); - CanonStateNotification::Commit { new: Arc::new(new_canon_chain) } - } - }; - - debug!( - target: "blockchain_tree", - actions = ?durations_recorder.actions, - "Canonicalization finished" - ); - - // clear trie updates for other children - self.block_indices() - .fork_to_child() - .get(&old_tip.hash) - .cloned() - .unwrap_or_default() - .into_iter() - .for_each(|child| { - if let Some(chain_id) = self.block_indices().get_side_chain_id(&child) { - if let Some(chain) = self.state.chains.get_mut(&chain_id) { - chain.clear_trie_updates(); - } - } - }); - - durations_recorder.record_relative(MakeCanonicalAction::ClearTrieUpdatesForOtherChildren); - - // Send notification about new canonical chain and return outcome of canonicalization. - let outcome = - CanonicalOutcome::Committed { head: chain_notification.tip().sealed_header().clone() }; - let _ = self.canon_state_notification_sender.send(chain_notification); - Ok(outcome) - } - - /// Write the given chain to the database as canonical. - fn commit_canonical_to_database( - &self, - chain: Chain, - recorder: &mut MakeCanonicalDurationsRecorder, - ) -> Result<(), CanonicalError> { - let (blocks, state, chain_trie_updates) = chain.into_inner(); - let hashed_state = self.externals.provider_factory.hashed_post_state(state.state()); - let prefix_sets = hashed_state.construct_prefix_sets().freeze(); - let hashed_state_sorted = hashed_state.into_sorted(); - - // Compute state root or retrieve cached trie updates before opening write transaction. - let block_hash_numbers = - blocks.iter().map(|(number, b)| (number, b.hash())).collect::>(); - let trie_updates = match chain_trie_updates { - Some(updates) => { - debug!(target: "blockchain_tree", blocks = ?block_hash_numbers, "Using cached trie updates"); - self.metrics.trie_updates_insert_cached.increment(1); - updates - } - None => { - debug!(target: "blockchain_tree", blocks = ?block_hash_numbers, "Recomputing state root for insert"); - let provider = self - .externals - .provider_factory - .provider()? - // State root calculation can take a while, and we're sure no write transaction - // will be open in parallel. See https://github.com/paradigmxyz/reth/issues/6168. - .disable_long_read_transaction_safety(); - let (state_root, trie_updates) = StateRoot::from_tx(provider.tx_ref()) - .with_hashed_cursor_factory(HashedPostStateCursorFactory::new( - DatabaseHashedCursorFactory::new(provider.tx_ref()), - &hashed_state_sorted, - )) - .with_prefix_sets(prefix_sets) - .root_with_updates() - .map_err(BlockValidationError::from)?; - let tip = blocks.tip(); - if state_root != tip.state_root { - return Err(ProviderError::StateRootMismatch(Box::new(RootMismatch { - root: GotExpected { got: state_root, expected: tip.state_root }, - block_number: tip.number, - block_hash: tip.hash(), - })) - .into()) - } - self.metrics.trie_updates_insert_recomputed.increment(1); - trie_updates - } - }; - recorder.record_relative(MakeCanonicalAction::RetrieveStateTrieUpdates); - - let provider_rw = self.externals.provider_factory.provider_rw()?; - provider_rw - .append_blocks_with_state( - blocks.into_blocks().collect(), - &state, - hashed_state_sorted, - trie_updates, - ) - .map_err(|e| CanonicalError::CanonicalCommit(e.to_string()))?; - - provider_rw.commit()?; - recorder.record_relative(MakeCanonicalAction::CommitCanonicalChainToDatabase); - - Ok(()) - } - - /// Unwind tables and put it inside state - pub fn unwind(&mut self, unwind_to: BlockNumber) -> Result<(), CanonicalError> { - // nothing to be done if unwind_to is higher then the tip - if self.block_indices().canonical_tip().number <= unwind_to { - return Ok(()); - } - // revert `N` blocks from current canonical chain and put them inside BlockchainTree - let old_canon_chain = self.revert_canonical_from_database(unwind_to)?; - - // check if there is block in chain - if let Some(old_canon_chain) = old_canon_chain { - self.state.block_indices.unwind_canonical_chain(unwind_to); - // insert old canonical chain to BlockchainTree. - self.insert_unwound_chain(AppendableChain::new(old_canon_chain)); - } - - Ok(()) - } - - /// Reverts the canonical chain down to the given block from the database and returns the - /// unwound chain. - /// - /// The block, `revert_until`, is __non-inclusive__, i.e. `revert_until` stays in the database. - fn revert_canonical_from_database( - &self, - revert_until: BlockNumber, - ) -> Result, CanonicalError> { - // This should only happen when an optimistic sync target was re-orged. - // - // Static files generally contain finalized data. The blockchain tree only deals - // with non-finalized data. The only scenario where canonical reverts go past the highest - // static file is when an optimistic sync occurred and non-finalized data was written to - // static files. - if self - .externals - .provider_factory - .static_file_provider() - .get_highest_static_file_block(StaticFileSegment::Headers) - .unwrap_or_default() > - revert_until - { - trace!( - target: "blockchain_tree", - "Reverting optimistic canonical chain to block {}", - revert_until - ); - return Err(CanonicalError::OptimisticTargetRevert(revert_until)); - } - - // read data that is needed for new sidechain - let provider_rw = self.externals.provider_factory.provider_rw()?; - - let tip = provider_rw.last_block_number()?; - let revert_range = (revert_until + 1)..=tip; - info!(target: "blockchain_tree", "REORG: revert canonical from database by unwinding chain blocks {:?}", revert_range); - // read block and execution result from database. and remove traces of block from tables. - let blocks_and_execution = provider_rw - .take_block_and_execution_above(revert_until, StorageLocation::Database) - .map_err(|e| CanonicalError::CanonicalRevert(e.to_string()))?; - - provider_rw.commit()?; - - if blocks_and_execution.is_empty() { - Ok(None) - } else { - Ok(Some(blocks_and_execution)) - } - } - - fn update_reorg_metrics(&self, reorg_depth: f64) { - self.metrics.reorgs.increment(1); - self.metrics.latest_reorg_depth.set(reorg_depth); - } - - /// Update blockchain tree chains (canonical and sidechains) and sync metrics. - /// - /// NOTE: this method should not be called during the pipeline sync, because otherwise the sync - /// checkpoint metric will get overwritten. Buffered blocks metrics are updated in - /// [`BlockBuffer`](crate::block_buffer::BlockBuffer) during the pipeline sync. - pub(crate) fn update_chains_metrics(&mut self) { - let height = self.state.block_indices.canonical_tip().number; - - let longest_sidechain_height = - self.state.chains.values().map(|chain| chain.tip().number).max(); - if let Some(longest_sidechain_height) = longest_sidechain_height { - self.metrics.longest_sidechain_height.set(longest_sidechain_height as f64); - } - - self.metrics.sidechains.set(self.state.chains.len() as f64); - self.metrics.canonical_chain_height.set(height as f64); - if let Some(metrics_tx) = self.sync_metrics_tx.as_mut() { - let _ = metrics_tx.send(MetricEvent::SyncHeight { height }); - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use alloy_consensus::{Header, TxEip1559, EMPTY_ROOT_HASH}; - use alloy_eips::{ - eip1559::{ETHEREUM_BLOCK_GAS_LIMIT, INITIAL_BASE_FEE}, - eip4895::Withdrawals, - }; - use alloy_genesis::{Genesis, GenesisAccount}; - use alloy_primitives::{keccak256, Address, PrimitiveSignature as Signature, B256}; - use assert_matches::assert_matches; - use linked_hash_set::LinkedHashSet; - use reth_chainspec::{ChainSpecBuilder, MAINNET, MIN_TRANSACTION_GAS}; - use reth_consensus::test_utils::TestConsensus; - use reth_db::tables; - use reth_db_api::transaction::DbTxMut; - use reth_evm::test_utils::MockExecutorProvider; - use reth_evm_ethereum::execute::EthExecutorProvider; - use reth_node_types::FullNodePrimitives; - use reth_primitives::{ - proofs::{calculate_receipt_root, calculate_transaction_root}, - Account, BlockBody, RecoveredTx, Transaction, TransactionSigned, - }; - use reth_provider::{ - providers::ProviderNodeTypes, - test_utils::{ - blocks::BlockchainTestData, create_test_provider_factory_with_chain_spec, - MockNodeTypesWithDB, - }, - ProviderFactory, StorageLocation, - }; - use reth_stages_api::StageCheckpoint; - use reth_trie::{root::state_root_unhashed, StateRoot}; - use std::collections::HashMap; - - fn setup_externals( - exec_res: Vec, - ) -> TreeExternals { - let chain_spec = Arc::new( - ChainSpecBuilder::default() - .chain(MAINNET.chain) - .genesis(MAINNET.genesis.clone()) - .shanghai_activated() - .build(), - ); - let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec); - let consensus = Arc::new(TestConsensus::default()); - let executor_factory = MockExecutorProvider::default(); - executor_factory.extend(exec_res); - - TreeExternals::new(provider_factory, consensus, executor_factory) - } - - fn setup_genesis< - N: ProviderNodeTypes< - Primitives: FullNodePrimitives< - BlockBody = reth_primitives::BlockBody, - BlockHeader = reth_primitives::Header, - >, - >, - >( - factory: &ProviderFactory, - mut genesis: SealedBlock, - ) { - // insert genesis to db. - - genesis.set_block_number(10); - genesis.set_state_root(EMPTY_ROOT_HASH); - let provider = factory.provider_rw().unwrap(); - - provider - .insert_historical_block( - genesis.try_seal_with_senders().expect("invalid tx signature in genesis"), - ) - .unwrap(); - - // insert first 10 blocks - for i in 0..10 { - provider - .tx_ref() - .put::(i, B256::new([100 + i as u8; 32])) - .unwrap(); - } - provider - .tx_ref() - .put::("Finish".to_string(), StageCheckpoint::new(10)) - .unwrap(); - provider.commit().unwrap(); - } - - /// Test data structure that will check tree internals - #[derive(Default, Debug)] - struct TreeTester { - /// Number of chains - chain_num: Option, - /// Check block to chain index - block_to_chain: Option>, - /// Check fork to child index - fork_to_child: Option>>, - /// Pending blocks - pending_blocks: Option<(BlockNumber, HashSet)>, - /// Buffered blocks - buffered_blocks: Option>, - } - - impl TreeTester { - const fn with_chain_num(mut self, chain_num: usize) -> Self { - self.chain_num = Some(chain_num); - self - } - - fn with_block_to_chain(mut self, block_to_chain: HashMap) -> Self { - self.block_to_chain = Some(block_to_chain); - self - } - - fn with_fork_to_child( - mut self, - fork_to_child: HashMap>, - ) -> Self { - self.fork_to_child = Some(fork_to_child); - self - } - - fn with_buffered_blocks( - mut self, - buffered_blocks: HashMap, - ) -> Self { - self.buffered_blocks = Some(buffered_blocks); - self - } - - fn with_pending_blocks( - mut self, - pending_blocks: (BlockNumber, HashSet), - ) -> Self { - self.pending_blocks = Some(pending_blocks); - self - } - - fn assert(self, tree: &BlockchainTree) { - if let Some(chain_num) = self.chain_num { - assert_eq!(tree.state.chains.len(), chain_num); - } - if let Some(block_to_chain) = self.block_to_chain { - assert_eq!(*tree.state.block_indices.blocks_to_chain(), block_to_chain); - } - if let Some(fork_to_child) = self.fork_to_child { - let mut x: HashMap> = - HashMap::with_capacity(fork_to_child.len()); - for (key, hash_set) in fork_to_child { - x.insert(key, hash_set.into_iter().collect()); - } - assert_eq!(*tree.state.block_indices.fork_to_child(), x); - } - if let Some(pending_blocks) = self.pending_blocks { - let (num, hashes) = tree.state.block_indices.pending_blocks(); - let hashes = hashes.into_iter().collect::>(); - assert_eq!((num, hashes), pending_blocks); - } - if let Some(buffered_blocks) = self.buffered_blocks { - assert_eq!(*tree.state.buffered_blocks.blocks(), buffered_blocks); - } - } - } - - #[test] - fn consecutive_reorgs() { - let signer = Address::random(); - let initial_signer_balance = U256::from(10).pow(U256::from(18)); - let chain_spec = Arc::new( - ChainSpecBuilder::default() - .chain(MAINNET.chain) - .genesis(Genesis { - alloc: BTreeMap::from([( - signer, - GenesisAccount { balance: initial_signer_balance, ..Default::default() }, - )]), - ..MAINNET.genesis.clone() - }) - .shanghai_activated() - .build(), - ); - let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); - let consensus = Arc::new(TestConsensus::default()); - let executor_provider = EthExecutorProvider::ethereum(chain_spec.clone()); - - { - let provider_rw = provider_factory.provider_rw().unwrap(); - provider_rw - .insert_block( - SealedBlock::new(chain_spec.sealed_genesis_header(), Default::default()) - .try_seal_with_senders() - .unwrap(), - StorageLocation::Database, - ) - .unwrap(); - let account = Account { balance: initial_signer_balance, ..Default::default() }; - provider_rw.tx_ref().put::(signer, account).unwrap(); - provider_rw.tx_ref().put::(keccak256(signer), account).unwrap(); - provider_rw.commit().unwrap(); - } - - let single_tx_cost = U256::from(INITIAL_BASE_FEE * MIN_TRANSACTION_GAS); - let mock_tx = |nonce: u64| -> RecoveredTx<_> { - TransactionSigned::new_unhashed( - Transaction::Eip1559(TxEip1559 { - chain_id: chain_spec.chain.id(), - nonce, - gas_limit: MIN_TRANSACTION_GAS, - to: Address::ZERO.into(), - max_fee_per_gas: INITIAL_BASE_FEE as u128, - ..Default::default() - }), - Signature::test_signature(), - ) - .with_signer(signer) - }; - - let mock_block = |number: u64, - parent: Option, - body: Vec>, - num_of_signer_txs: u64| - -> SealedBlockWithSenders { - let signed_body = body.clone().into_iter().map(|tx| tx.into_tx()).collect::>(); - let transactions_root = calculate_transaction_root(&signed_body); - let receipts = body - .iter() - .enumerate() - .map(|(idx, tx)| { - Receipt { - tx_type: tx.tx_type(), - success: true, - cumulative_gas_used: (idx as u64 + 1) * MIN_TRANSACTION_GAS, - ..Default::default() - } - .with_bloom() - }) - .collect::>(); - - // receipts root computation is different for OP - let receipts_root = calculate_receipt_root(&receipts); - - let header = Header { - number, - parent_hash: parent.unwrap_or_default(), - gas_used: body.len() as u64 * MIN_TRANSACTION_GAS, - gas_limit: ETHEREUM_BLOCK_GAS_LIMIT, - mix_hash: B256::random(), - base_fee_per_gas: Some(INITIAL_BASE_FEE), - transactions_root, - receipts_root, - state_root: state_root_unhashed(HashMap::from([( - signer, - Account { - balance: initial_signer_balance - - (single_tx_cost * U256::from(num_of_signer_txs)), - nonce: num_of_signer_txs, - ..Default::default() - } - .into_trie_account(EMPTY_ROOT_HASH), - )])), - ..Default::default() - }; - - SealedBlockWithSenders::new( - SealedBlock::new( - SealedHeader::seal(header), - BlockBody { - transactions: signed_body, - ommers: Vec::new(), - withdrawals: Some(Withdrawals::default()), - }, - ), - body.iter().map(|tx| tx.signer()).collect(), - ) - .unwrap() - }; - - let fork_block = mock_block(1, Some(chain_spec.genesis_hash()), Vec::from([mock_tx(0)]), 1); - - let canonical_block_1 = - mock_block(2, Some(fork_block.hash()), Vec::from([mock_tx(1), mock_tx(2)]), 3); - let canonical_block_2 = mock_block(3, Some(canonical_block_1.hash()), Vec::new(), 3); - let canonical_block_3 = - mock_block(4, Some(canonical_block_2.hash()), Vec::from([mock_tx(3)]), 4); - - let sidechain_block_1 = mock_block(2, Some(fork_block.hash()), Vec::from([mock_tx(1)]), 2); - let sidechain_block_2 = - mock_block(3, Some(sidechain_block_1.hash()), Vec::from([mock_tx(2)]), 3); - - let mut tree = BlockchainTree::new( - TreeExternals::new(provider_factory, consensus, executor_provider), - BlockchainTreeConfig::default(), - ) - .expect("failed to create tree"); - - tree.insert_block(fork_block.clone(), BlockValidationKind::Exhaustive).unwrap(); - - assert_eq!( - tree.make_canonical(fork_block.hash()).unwrap(), - CanonicalOutcome::Committed { head: fork_block.sealed_header().clone() } - ); - - assert_eq!( - tree.insert_block(canonical_block_1.clone(), BlockValidationKind::Exhaustive).unwrap(), - InsertPayloadOk::Inserted(BlockStatus::Valid(BlockAttachment::Canonical)) - ); - - assert_eq!( - tree.make_canonical(canonical_block_1.hash()).unwrap(), - CanonicalOutcome::Committed { head: canonical_block_1.sealed_header().clone() } - ); - - assert_eq!( - tree.insert_block(canonical_block_2, BlockValidationKind::Exhaustive).unwrap(), - InsertPayloadOk::Inserted(BlockStatus::Valid(BlockAttachment::Canonical)) - ); - - assert_eq!( - tree.insert_block(sidechain_block_1.clone(), BlockValidationKind::Exhaustive).unwrap(), - InsertPayloadOk::Inserted(BlockStatus::Valid(BlockAttachment::HistoricalFork)) - ); - - assert_eq!( - tree.make_canonical(sidechain_block_1.hash()).unwrap(), - CanonicalOutcome::Committed { head: sidechain_block_1.sealed_header().clone() } - ); - - assert_eq!( - tree.make_canonical(canonical_block_1.hash()).unwrap(), - CanonicalOutcome::Committed { head: canonical_block_1.sealed_header().clone() } - ); - - assert_eq!( - tree.insert_block(sidechain_block_2.clone(), BlockValidationKind::Exhaustive).unwrap(), - InsertPayloadOk::Inserted(BlockStatus::Valid(BlockAttachment::HistoricalFork)) - ); - - assert_eq!( - tree.make_canonical(sidechain_block_2.hash()).unwrap(), - CanonicalOutcome::Committed { head: sidechain_block_2.sealed_header().clone() } - ); - - assert_eq!( - tree.insert_block(canonical_block_3.clone(), BlockValidationKind::Exhaustive).unwrap(), - InsertPayloadOk::Inserted(BlockStatus::Valid(BlockAttachment::HistoricalFork)) - ); - - assert_eq!( - tree.make_canonical(canonical_block_3.hash()).unwrap(), - CanonicalOutcome::Committed { head: canonical_block_3.sealed_header().clone() } - ); - } - - #[test] - fn sidechain_block_hashes() { - let data = BlockchainTestData::default_from_number(11); - let (block1, exec1) = data.blocks[0].clone(); - let (block2, exec2) = data.blocks[1].clone(); - let (block3, exec3) = data.blocks[2].clone(); - let (block4, exec4) = data.blocks[3].clone(); - let genesis = data.genesis; - - // test pops execution results from vector, so order is from last to first. - let externals = - setup_externals(vec![exec3.clone(), exec2.clone(), exec4, exec3, exec2, exec1]); - - // last finalized block would be number 9. - setup_genesis(&externals.provider_factory, genesis); - - // make tree - let config = BlockchainTreeConfig::new(1, 2, 3, 2); - let mut tree = BlockchainTree::new(externals, config).expect("failed to create tree"); - // genesis block 10 is already canonical - tree.make_canonical(B256::ZERO).unwrap(); - - // make genesis block 10 as finalized - tree.finalize_block(10).unwrap(); - - assert_eq!( - tree.insert_block(block1.clone(), BlockValidationKind::Exhaustive).unwrap(), - InsertPayloadOk::Inserted(BlockStatus::Valid(BlockAttachment::Canonical)) - ); - - assert_eq!( - tree.insert_block(block2.clone(), BlockValidationKind::Exhaustive).unwrap(), - InsertPayloadOk::Inserted(BlockStatus::Valid(BlockAttachment::Canonical)) - ); - - assert_eq!( - tree.insert_block(block3.clone(), BlockValidationKind::Exhaustive).unwrap(), - InsertPayloadOk::Inserted(BlockStatus::Valid(BlockAttachment::Canonical)) - ); - - assert_eq!( - tree.insert_block(block4, BlockValidationKind::Exhaustive).unwrap(), - InsertPayloadOk::Inserted(BlockStatus::Valid(BlockAttachment::Canonical)) - ); - - let mut block2a = block2; - let block2a_hash = B256::new([0x34; 32]); - block2a.set_hash(block2a_hash); - - assert_eq!( - tree.insert_block(block2a.clone(), BlockValidationKind::Exhaustive).unwrap(), - InsertPayloadOk::Inserted(BlockStatus::Valid(BlockAttachment::HistoricalFork)) - ); - - let mut block3a = block3; - let block3a_hash = B256::new([0x35; 32]); - block3a.set_hash(block3a_hash); - block3a.set_parent_hash(block2a.hash()); - - assert_eq!( - tree.insert_block(block3a.clone(), BlockValidationKind::Exhaustive).unwrap(), - InsertPayloadOk::Inserted(BlockStatus::Valid(BlockAttachment::Canonical)) /* TODO: this is incorrect, figure out why */ - ); - - let block3a_chain_id = tree.state.block_indices.get_side_chain_id(&block3a.hash()).unwrap(); - assert_eq!( - tree.all_chain_hashes(block3a_chain_id), - BTreeMap::from([ - (block1.number, block1.hash()), - (block2a.number, block2a.hash()), - (block3a.number, block3a.hash()), - ]) - ); - } - - #[test] - fn cached_trie_updates() { - let data = BlockchainTestData::default_from_number(11); - let (block1, exec1) = data.blocks[0].clone(); - let (block2, exec2) = data.blocks[1].clone(); - let (block3, exec3) = data.blocks[2].clone(); - let (block4, exec4) = data.blocks[3].clone(); - let (block5, exec5) = data.blocks[4].clone(); - let genesis = data.genesis; - - // test pops execution results from vector, so order is from last to first. - let externals = setup_externals(vec![exec5.clone(), exec4, exec3, exec2, exec1]); - - // last finalized block would be number 9. - setup_genesis(&externals.provider_factory, genesis); - - // make tree - let config = BlockchainTreeConfig::new(1, 2, 3, 2); - let mut tree = BlockchainTree::new(externals, config).expect("failed to create tree"); - // genesis block 10 is already canonical - tree.make_canonical(B256::ZERO).unwrap(); - - // make genesis block 10 as finalized - tree.finalize_block(10).unwrap(); - - assert_eq!( - tree.insert_block(block1.clone(), BlockValidationKind::Exhaustive).unwrap(), - InsertPayloadOk::Inserted(BlockStatus::Valid(BlockAttachment::Canonical)) - ); - let block1_chain_id = tree.state.block_indices.get_side_chain_id(&block1.hash()).unwrap(); - let block1_chain = tree.state.chains.get(&block1_chain_id).unwrap(); - assert!(block1_chain.trie_updates().is_some()); - - assert_eq!( - tree.insert_block(block2.clone(), BlockValidationKind::Exhaustive).unwrap(), - InsertPayloadOk::Inserted(BlockStatus::Valid(BlockAttachment::Canonical)) - ); - let block2_chain_id = tree.state.block_indices.get_side_chain_id(&block2.hash()).unwrap(); - let block2_chain = tree.state.chains.get(&block2_chain_id).unwrap(); - assert!(block2_chain.trie_updates().is_none()); - - assert_eq!( - tree.make_canonical(block2.hash()).unwrap(), - CanonicalOutcome::Committed { head: block2.sealed_header().clone() } - ); - - assert_eq!( - tree.insert_block(block3.clone(), BlockValidationKind::Exhaustive).unwrap(), - InsertPayloadOk::Inserted(BlockStatus::Valid(BlockAttachment::Canonical)) - ); - let block3_chain_id = tree.state.block_indices.get_side_chain_id(&block3.hash()).unwrap(); - let block3_chain = tree.state.chains.get(&block3_chain_id).unwrap(); - assert!(block3_chain.trie_updates().is_some()); - - assert_eq!( - tree.make_canonical(block3.hash()).unwrap(), - CanonicalOutcome::Committed { head: block3.sealed_header().clone() } - ); - - assert_eq!( - tree.insert_block(block4.clone(), BlockValidationKind::Exhaustive).unwrap(), - InsertPayloadOk::Inserted(BlockStatus::Valid(BlockAttachment::Canonical)) - ); - let block4_chain_id = tree.state.block_indices.get_side_chain_id(&block4.hash()).unwrap(); - let block4_chain = tree.state.chains.get(&block4_chain_id).unwrap(); - assert!(block4_chain.trie_updates().is_some()); - - assert_eq!( - tree.insert_block(block5.clone(), BlockValidationKind::Exhaustive).unwrap(), - InsertPayloadOk::Inserted(BlockStatus::Valid(BlockAttachment::Canonical)) - ); - - let block5_chain_id = tree.state.block_indices.get_side_chain_id(&block5.hash()).unwrap(); - let block5_chain = tree.state.chains.get(&block5_chain_id).unwrap(); - assert!(block5_chain.trie_updates().is_none()); - - assert_eq!( - tree.make_canonical(block5.hash()).unwrap(), - CanonicalOutcome::Committed { head: block5.sealed_header().clone() } - ); - - let provider = tree.externals.provider_factory.provider().unwrap(); - let prefix_sets = tree - .externals - .provider_factory - .hashed_post_state(exec5.state()) - .construct_prefix_sets() - .freeze(); - let state_root = - StateRoot::from_tx(provider.tx_ref()).with_prefix_sets(prefix_sets).root().unwrap(); - assert_eq!(state_root, block5.state_root); - } - - #[test] - fn test_side_chain_fork() { - let data = BlockchainTestData::default_from_number(11); - let (block1, exec1) = data.blocks[0].clone(); - let (block2, exec2) = data.blocks[1].clone(); - let genesis = data.genesis; - - // test pops execution results from vector, so order is from last to first. - let externals = setup_externals(vec![exec2.clone(), exec2, exec1]); - - // last finalized block would be number 9. - setup_genesis(&externals.provider_factory, genesis); - - // make tree - let config = BlockchainTreeConfig::new(1, 2, 3, 2); - let mut tree = BlockchainTree::new(externals, config).expect("failed to create tree"); - // genesis block 10 is already canonical - tree.make_canonical(B256::ZERO).unwrap(); - - // make genesis block 10 as finalized - tree.finalize_block(10).unwrap(); - - assert_eq!( - tree.insert_block(block1.clone(), BlockValidationKind::Exhaustive).unwrap(), - InsertPayloadOk::Inserted(BlockStatus::Valid(BlockAttachment::Canonical)) - ); - - assert_eq!( - tree.insert_block(block2.clone(), BlockValidationKind::Exhaustive).unwrap(), - InsertPayloadOk::Inserted(BlockStatus::Valid(BlockAttachment::Canonical)) - ); - - // we have one chain that has two blocks. - // Trie state: - // b2 (pending block) - // | - // | - // b1 (pending block) - // / - // / - // g1 (canonical blocks) - // | - TreeTester::default() - .with_chain_num(1) - .with_block_to_chain(HashMap::from([ - (block1.hash(), 0.into()), - (block2.hash(), 0.into()), - ])) - .with_fork_to_child(HashMap::from([( - block1.parent_hash, - HashSet::from([block1.hash()]), - )])) - .assert(&tree); - - let mut block2a = block2.clone(); - let block2a_hash = B256::new([0x34; 32]); - block2a.set_hash(block2a_hash); - - assert_eq!( - tree.insert_block(block2a.clone(), BlockValidationKind::Exhaustive).unwrap(), - InsertPayloadOk::Inserted(BlockStatus::Valid(BlockAttachment::HistoricalFork)) - ); - - // fork chain. - // Trie state: - // b2 b2a (pending blocks in tree) - // | / - // | / - // b1 - // / - // / - // g1 (canonical blocks) - // | - - TreeTester::default() - .with_chain_num(2) - .with_block_to_chain(HashMap::from([ - (block1.hash(), 0.into()), - (block2.hash(), 0.into()), - (block2a.hash(), 1.into()), - ])) - .with_fork_to_child(HashMap::from([ - (block1.parent_hash, HashSet::from([block1.hash()])), - (block2a.parent_hash, HashSet::from([block2a.hash()])), - ])) - .assert(&tree); - // chain 0 has two blocks so receipts and reverts len is 2 - let chain0 = tree.state.chains.get(&0.into()).unwrap().execution_outcome(); - assert_eq!(chain0.receipts().len(), 2); - assert_eq!(chain0.state().reverts.len(), 2); - assert_eq!(chain0.first_block(), block1.number); - // chain 1 has one block so receipts and reverts len is 1 - let chain1 = tree.state.chains.get(&1.into()).unwrap().execution_outcome(); - assert_eq!(chain1.receipts().len(), 1); - assert_eq!(chain1.state().reverts.len(), 1); - assert_eq!(chain1.first_block(), block2.number); - } - - #[test] - fn sanity_path() { - let data = BlockchainTestData::default_from_number(11); - let (block1, exec1) = data.blocks[0].clone(); - let (block2, exec2) = data.blocks[1].clone(); - let genesis = data.genesis; - - // test pops execution results from vector, so order is from last to first. - let externals = setup_externals(vec![exec2.clone(), exec1.clone(), exec2, exec1]); - - // last finalized block would be number 9. - setup_genesis(&externals.provider_factory, genesis); - - // make tree - let config = BlockchainTreeConfig::new(1, 2, 3, 2); - let mut tree = BlockchainTree::new(externals, config).expect("failed to create tree"); - - let mut canon_notif = tree.subscribe_canon_state(); - // genesis block 10 is already canonical - let head = BlockNumHash::new(10, B256::ZERO); - tree.make_canonical(head.hash).unwrap(); - - // make sure is_block_hash_canonical returns true for genesis block - tree.is_block_hash_canonical(&B256::ZERO).unwrap(); - - // make genesis block 10 as finalized - tree.finalize_block(head.number).unwrap(); - - // block 2 parent is not known, block2 is buffered. - assert_eq!( - tree.insert_block(block2.clone(), BlockValidationKind::Exhaustive).unwrap(), - InsertPayloadOk::Inserted(BlockStatus::Disconnected { - head, - missing_ancestor: block2.parent_num_hash() - }) - ); - - // Buffered block: [block2] - // Trie state: - // | - // g1 (canonical blocks) - // | - - TreeTester::default() - .with_buffered_blocks(HashMap::from([(block2.hash(), block2.clone())])) - .assert(&tree); - - assert_eq!( - tree.is_block_known(block2.num_hash()).unwrap(), - Some(BlockStatus::Disconnected { head, missing_ancestor: block2.parent_num_hash() }) - ); - - // check if random block is known - let old_block = BlockNumHash::new(1, B256::new([32; 32])); - let err = BlockchainTreeError::PendingBlockIsFinalized { last_finalized: 10 }; - - assert_eq!(tree.is_block_known(old_block).unwrap_err().as_tree_error(), Some(err)); - - // insert block1 and buffered block2 is inserted - assert_eq!( - tree.insert_block(block1.clone(), BlockValidationKind::Exhaustive).unwrap(), - InsertPayloadOk::Inserted(BlockStatus::Valid(BlockAttachment::Canonical)) - ); - - // Buffered blocks: [] - // Trie state: - // b2 (pending block) - // | - // | - // b1 (pending block) - // / - // / - // g1 (canonical blocks) - // | - TreeTester::default() - .with_chain_num(1) - .with_block_to_chain(HashMap::from([ - (block1.hash(), 0.into()), - (block2.hash(), 0.into()), - ])) - .with_fork_to_child(HashMap::from([( - block1.parent_hash, - HashSet::from([block1.hash()]), - )])) - .with_pending_blocks((block1.number, HashSet::from([block1.hash()]))) - .assert(&tree); - - // already inserted block will `InsertPayloadOk::AlreadySeen(_)` - assert_eq!( - tree.insert_block(block1.clone(), BlockValidationKind::Exhaustive).unwrap(), - InsertPayloadOk::AlreadySeen(BlockStatus::Valid(BlockAttachment::Canonical)) - ); - - // block two is already inserted. - assert_eq!( - tree.insert_block(block2.clone(), BlockValidationKind::Exhaustive).unwrap(), - InsertPayloadOk::AlreadySeen(BlockStatus::Valid(BlockAttachment::Canonical)) - ); - - // make block1 canonical - tree.make_canonical(block1.hash()).unwrap(); - // check notification - assert_matches!(canon_notif.try_recv(), Ok(CanonStateNotification::Commit{ new}) if *new.blocks() == BTreeMap::from([(block1.number,block1.clone())])); - - // make block2 canonicals - tree.make_canonical(block2.hash()).unwrap(); - // check notification. - assert_matches!(canon_notif.try_recv(), Ok(CanonStateNotification::Commit{ new}) if *new.blocks() == BTreeMap::from([(block2.number,block2.clone())])); - - // Trie state: - // b2 (canonical block) - // | - // | - // b1 (canonical block) - // | - // | - // g1 (canonical blocks) - // | - TreeTester::default() - .with_chain_num(0) - .with_block_to_chain(HashMap::from([])) - .with_fork_to_child(HashMap::from([])) - .assert(&tree); - - /**** INSERT SIDE BLOCKS *** */ - - let mut block1a = block1.clone(); - let block1a_hash = B256::new([0x33; 32]); - block1a.set_hash(block1a_hash); - let mut block2a = block2.clone(); - let block2a_hash = B256::new([0x34; 32]); - block2a.set_hash(block2a_hash); - - // reinsert two blocks that point to canonical chain - assert_eq!( - tree.insert_block(block1a.clone(), BlockValidationKind::Exhaustive).unwrap(), - InsertPayloadOk::Inserted(BlockStatus::Valid(BlockAttachment::HistoricalFork)) - ); - - TreeTester::default() - .with_chain_num(1) - .with_block_to_chain(HashMap::from([(block1a_hash, 1.into())])) - .with_fork_to_child(HashMap::from([( - block1.parent_hash, - HashSet::from([block1a_hash]), - )])) - .with_pending_blocks((block2.number + 1, HashSet::from([]))) - .assert(&tree); - - assert_eq!( - tree.insert_block(block2a.clone(), BlockValidationKind::Exhaustive).unwrap(), - InsertPayloadOk::Inserted(BlockStatus::Valid(BlockAttachment::HistoricalFork)) - ); - // Trie state: - // b2 b2a (side chain) - // | / - // | / - // b1 b1a (side chain) - // | / - // |/ - // g1 (10) - // | - TreeTester::default() - .with_chain_num(2) - .with_block_to_chain(HashMap::from([ - (block1a_hash, 1.into()), - (block2a_hash, 2.into()), - ])) - .with_fork_to_child(HashMap::from([ - (block1.parent_hash, HashSet::from([block1a_hash])), - (block1.hash(), HashSet::from([block2a_hash])), - ])) - .with_pending_blocks((block2.number + 1, HashSet::from([]))) - .assert(&tree); - - // make b2a canonical - assert!(tree.make_canonical(block2a_hash).is_ok()); - // check notification. - assert_matches!(canon_notif.try_recv(), - Ok(CanonStateNotification::Reorg{ old, new}) - if *old.blocks() == BTreeMap::from([(block2.number,block2.clone())]) - && *new.blocks() == BTreeMap::from([(block2a.number,block2a.clone())])); - - // Trie state: - // b2a b2 (side chain) - // | / - // | / - // b1 b1a (side chain) - // | / - // |/ - // g1 (10) - // | - TreeTester::default() - .with_chain_num(2) - .with_block_to_chain(HashMap::from([ - (block1a_hash, 1.into()), - (block2.hash(), 3.into()), - ])) - .with_fork_to_child(HashMap::from([ - (block1.parent_hash, HashSet::from([block1a_hash])), - (block1.hash(), HashSet::from([block2.hash()])), - ])) - .with_pending_blocks((block2.number + 1, HashSet::default())) - .assert(&tree); - - assert_matches!(tree.make_canonical(block1a_hash), Ok(_)); - // Trie state: - // b2a b2 (side chain) - // | / - // | / - // b1a b1 (side chain) - // | / - // |/ - // g1 (10) - // | - TreeTester::default() - .with_chain_num(2) - .with_block_to_chain(HashMap::from([ - (block1.hash(), 4.into()), - (block2a_hash, 4.into()), - (block2.hash(), 3.into()), - ])) - .with_fork_to_child(HashMap::from([ - (block1.parent_hash, HashSet::from([block1.hash()])), - (block1.hash(), HashSet::from([block2.hash()])), - ])) - .with_pending_blocks((block1a.number + 1, HashSet::default())) - .assert(&tree); - - // check notification. - assert_matches!(canon_notif.try_recv(), - Ok(CanonStateNotification::Reorg{ old, new}) - if *old.blocks() == BTreeMap::from([(block1.number,block1.clone()),(block2a.number,block2a.clone())]) - && *new.blocks() == BTreeMap::from([(block1a.number,block1a.clone())])); - - // check that b2 and b1 are not canonical - assert!(!tree.is_block_hash_canonical(&block2.hash()).unwrap()); - assert!(!tree.is_block_hash_canonical(&block1.hash()).unwrap()); - - // ensure that b1a is canonical - assert!(tree.is_block_hash_canonical(&block1a.hash()).unwrap()); - - // make b2 canonical - tree.make_canonical(block2.hash()).unwrap(); - // Trie state: - // b2 b2a (side chain) - // | / - // | / - // b1 b1a (side chain) - // | / - // |/ - // g1 (10) - // | - TreeTester::default() - .with_chain_num(2) - .with_block_to_chain(HashMap::from([ - (block1a_hash, 5.into()), - (block2a_hash, 4.into()), - ])) - .with_fork_to_child(HashMap::from([ - (block1.parent_hash, HashSet::from([block1a_hash])), - (block1.hash(), HashSet::from([block2a_hash])), - ])) - .with_pending_blocks((block2.number + 1, HashSet::default())) - .assert(&tree); - - // check notification. - assert_matches!(canon_notif.try_recv(), - Ok(CanonStateNotification::Reorg{ old, new}) - if *old.blocks() == BTreeMap::from([(block1a.number,block1a.clone())]) - && *new.blocks() == BTreeMap::from([(block1.number,block1.clone()),(block2.number,block2.clone())])); - - // check that b2 is now canonical - assert!(tree.is_block_hash_canonical(&block2.hash()).unwrap()); - - // finalize b1 that would make b1a removed from tree - tree.finalize_block(11).unwrap(); - // Trie state: - // b2 b2a (side chain) - // | / - // | / - // b1 (canon) - // | - // g1 (10) - // | - TreeTester::default() - .with_chain_num(1) - .with_block_to_chain(HashMap::from([(block2a_hash, 4.into())])) - .with_fork_to_child(HashMap::from([(block1.hash(), HashSet::from([block2a_hash]))])) - .with_pending_blocks((block2.number + 1, HashSet::from([]))) - .assert(&tree); - - // unwind canonical - assert!(tree.unwind(block1.number).is_ok()); - // Trie state: - // b2 b2a (pending block) - // / / - // / / - // / / - // b1 (canonical block) - // | - // | - // g1 (canonical blocks) - // | - TreeTester::default() - .with_chain_num(2) - .with_block_to_chain(HashMap::from([ - (block2a_hash, 4.into()), - (block2.hash(), 6.into()), - ])) - .with_fork_to_child(HashMap::from([( - block1.hash(), - HashSet::from([block2a_hash, block2.hash()]), - )])) - .with_pending_blocks((block2.number, HashSet::from([block2.hash(), block2a.hash()]))) - .assert(&tree); - - // commit b2a - tree.make_canonical(block2.hash()).unwrap(); - - // Trie state: - // b2 b2a (side chain) - // | / - // | / - // b1 (finalized) - // | - // g1 (10) - // | - TreeTester::default() - .with_chain_num(1) - .with_block_to_chain(HashMap::from([(block2a_hash, 4.into())])) - .with_fork_to_child(HashMap::from([(block1.hash(), HashSet::from([block2a_hash]))])) - .with_pending_blocks((block2.number + 1, HashSet::default())) - .assert(&tree); - - // check notification. - assert_matches!(canon_notif.try_recv(), - Ok(CanonStateNotification::Commit{ new }) - if *new.blocks() == BTreeMap::from([(block2.number,block2.clone())])); - - // insert unconnected block2b - let mut block2b = block2a.clone(); - block2b.set_hash(B256::new([0x99; 32])); - block2b.set_parent_hash(B256::new([0x88; 32])); - - assert_eq!( - tree.insert_block(block2b.clone(), BlockValidationKind::Exhaustive).unwrap(), - InsertPayloadOk::Inserted(BlockStatus::Disconnected { - head: block2.num_hash(), - missing_ancestor: block2b.parent_num_hash() - }) - ); - - TreeTester::default() - .with_buffered_blocks(HashMap::from([(block2b.hash(), block2b.clone())])) - .assert(&tree); - - // update canonical block to b2, this would make b2a be removed - assert!(tree.connect_buffered_blocks_to_canonical_hashes_and_finalize(12).is_ok()); - - assert_eq!( - tree.is_block_known(block2.num_hash()).unwrap(), - Some(BlockStatus::Valid(BlockAttachment::Canonical)) - ); - - // Trie state: - // b2 (finalized) - // | - // b1 (finalized) - // | - // g1 (10) - // | - TreeTester::default() - .with_chain_num(0) - .with_block_to_chain(HashMap::default()) - .with_fork_to_child(HashMap::default()) - .with_pending_blocks((block2.number + 1, HashSet::default())) - .with_buffered_blocks(HashMap::default()) - .assert(&tree); - } - - #[test] - fn last_finalized_block_initialization() { - let data = BlockchainTestData::default_from_number(11); - let (block1, exec1) = data.blocks[0].clone(); - let (block2, exec2) = data.blocks[1].clone(); - let (block3, exec3) = data.blocks[2].clone(); - let genesis = data.genesis; - - // test pops execution results from vector, so order is from last to first. - let externals = - setup_externals(vec![exec3.clone(), exec2.clone(), exec1.clone(), exec3, exec2, exec1]); - let cloned_externals_1 = TreeExternals { - provider_factory: externals.provider_factory.clone(), - executor_factory: externals.executor_factory.clone(), - consensus: externals.consensus.clone(), - }; - let cloned_externals_2 = TreeExternals { - provider_factory: externals.provider_factory.clone(), - executor_factory: externals.executor_factory.clone(), - consensus: externals.consensus.clone(), - }; - - // last finalized block would be number 9. - setup_genesis(&externals.provider_factory, genesis); - - // make tree - let config = BlockchainTreeConfig::new(1, 2, 3, 2); - let mut tree = BlockchainTree::new(externals, config).expect("failed to create tree"); - - assert_eq!( - tree.insert_block(block1.clone(), BlockValidationKind::Exhaustive).unwrap(), - InsertPayloadOk::Inserted(BlockStatus::Valid(BlockAttachment::Canonical)) - ); - - assert_eq!( - tree.insert_block(block2.clone(), BlockValidationKind::Exhaustive).unwrap(), - InsertPayloadOk::Inserted(BlockStatus::Valid(BlockAttachment::Canonical)) - ); - - assert_eq!( - tree.insert_block(block3, BlockValidationKind::Exhaustive).unwrap(), - InsertPayloadOk::Inserted(BlockStatus::Valid(BlockAttachment::Canonical)) - ); - - tree.make_canonical(block2.hash()).unwrap(); - - // restart - let mut tree = - BlockchainTree::new(cloned_externals_1, config).expect("failed to create tree"); - assert_eq!(tree.block_indices().last_finalized_block(), 0); - - let mut block1a = block1; - let block1a_hash = B256::new([0x33; 32]); - block1a.set_hash(block1a_hash); - - assert_eq!( - tree.insert_block(block1a.clone(), BlockValidationKind::Exhaustive).unwrap(), - InsertPayloadOk::Inserted(BlockStatus::Valid(BlockAttachment::HistoricalFork)) - ); - - tree.make_canonical(block1a.hash()).unwrap(); - tree.finalize_block(block1a.number).unwrap(); - - // restart - let tree = BlockchainTree::new(cloned_externals_2, config).expect("failed to create tree"); - - assert_eq!(tree.block_indices().last_finalized_block(), block1a.number); - } -} diff --git a/crates/blockchain-tree/src/bundle.rs b/crates/blockchain-tree/src/bundle.rs deleted file mode 100644 index ef9fc2167..000000000 --- a/crates/blockchain-tree/src/bundle.rs +++ /dev/null @@ -1,69 +0,0 @@ -//! [`ExecutionDataProvider`] implementations used by the tree. - -use alloy_eips::ForkBlock; -use alloy_primitives::{BlockHash, BlockNumber}; -use reth_provider::{BlockExecutionForkProvider, ExecutionDataProvider, ExecutionOutcome}; -use std::collections::BTreeMap; - -/// Structure that combines references of required data to be an [`ExecutionDataProvider`]. -#[derive(Clone, Debug)] -pub struct BundleStateDataRef<'a> { - /// The execution outcome after execution of one or more transactions and/or blocks. - pub execution_outcome: &'a ExecutionOutcome, - /// The blocks in the sidechain. - pub sidechain_block_hashes: &'a BTreeMap, - /// The blocks in the canonical chain. - pub canonical_block_hashes: &'a BTreeMap, - /// Canonical fork - pub canonical_fork: ForkBlock, -} - -impl ExecutionDataProvider for BundleStateDataRef<'_> { - fn execution_outcome(&self) -> &ExecutionOutcome { - self.execution_outcome - } - - fn block_hash(&self, block_number: BlockNumber) -> Option { - let block_hash = self.sidechain_block_hashes.get(&block_number).copied(); - if block_hash.is_some() { - return block_hash; - } - - self.canonical_block_hashes.get(&block_number).copied() - } -} - -impl BlockExecutionForkProvider for BundleStateDataRef<'_> { - fn canonical_fork(&self) -> ForkBlock { - self.canonical_fork - } -} - -/// Structure that owns the relevant data needs to be an [`ExecutionDataProvider`] -#[derive(Clone, Debug)] -pub struct ExecutionData { - /// Execution outcome. - pub execution_outcome: ExecutionOutcome, - /// Parent block hashes needs for evm BLOCKHASH opcode. - /// NOTE: it does not mean that all hashes are there but all until finalized are there. - /// Other hashes can be obtained from provider - pub parent_block_hashes: BTreeMap, - /// Canonical block where state forked from. - pub canonical_fork: ForkBlock, -} - -impl ExecutionDataProvider for ExecutionData { - fn execution_outcome(&self) -> &ExecutionOutcome { - &self.execution_outcome - } - - fn block_hash(&self, block_number: BlockNumber) -> Option { - self.parent_block_hashes.get(&block_number).copied() - } -} - -impl BlockExecutionForkProvider for ExecutionData { - fn canonical_fork(&self) -> ForkBlock { - self.canonical_fork - } -} diff --git a/crates/blockchain-tree/src/canonical_chain.rs b/crates/blockchain-tree/src/canonical_chain.rs deleted file mode 100644 index 253f799fe..000000000 --- a/crates/blockchain-tree/src/canonical_chain.rs +++ /dev/null @@ -1,241 +0,0 @@ -use alloy_eips::BlockNumHash; -use alloy_primitives::{BlockHash, BlockNumber}; -use std::collections::BTreeMap; - -/// This keeps track of (non-finalized) blocks of the canonical chain. -/// -/// This is a wrapper type around an ordered set of block numbers and hashes that belong to the -/// canonical chain that is not yet finalized. -#[derive(Debug, Clone, Default)] -pub(crate) struct CanonicalChain { - /// All blocks of the canonical chain in order of their block number. - chain: BTreeMap, -} - -impl CanonicalChain { - pub(crate) const fn new(chain: BTreeMap) -> Self { - Self { chain } - } - - /// Replaces the current chain with the given one. - #[inline] - pub(crate) fn replace(&mut self, chain: BTreeMap) { - self.chain = chain; - } - - /// Returns the block hash of the (non-finalized) canonical block with the given number. - #[inline] - pub(crate) fn canonical_hash(&self, number: &BlockNumber) -> Option { - self.chain.get(number).copied() - } - - /// Returns the block number of the (non-finalized) canonical block with the given hash. - #[inline] - pub(crate) fn canonical_number(&self, block_hash: &BlockHash) -> Option { - self.chain.iter().find_map(|(number, hash)| (hash == block_hash).then_some(*number)) - } - - /// Extends all items from the given iterator to the chain. - #[inline] - pub(crate) fn extend(&mut self, blocks: impl Iterator) { - self.chain.extend(blocks) - } - - /// Retains only the elements specified by the predicate. - #[inline] - pub(crate) fn retain(&mut self, f: F) - where - F: FnMut(&BlockNumber, &mut BlockHash) -> bool, - { - self.chain.retain(f) - } - - #[inline] - pub(crate) const fn inner(&self) -> &BTreeMap { - &self.chain - } - - #[inline] - pub(crate) fn tip(&self) -> BlockNumHash { - self.chain - .last_key_value() - .map(|(&number, &hash)| BlockNumHash { number, hash }) - .unwrap_or_default() - } - - #[inline] - pub(crate) fn iter(&self) -> impl Iterator + '_ { - self.chain.iter().map(|(&number, &hash)| (number, hash)) - } - - #[inline] - pub(crate) fn into_iter(self) -> impl Iterator { - self.chain.into_iter() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_replace_canonical_chain() { - // Initialize a chain with some blocks - let mut initial_chain = BTreeMap::new(); - initial_chain.insert(BlockNumber::from(1u64), BlockHash::from([0x01; 32])); - initial_chain.insert(BlockNumber::from(2u64), BlockHash::from([0x02; 32])); - - let mut canonical_chain = CanonicalChain::new(initial_chain.clone()); - - // Verify initial chain state - assert_eq!(canonical_chain.chain.len(), 2); - assert_eq!( - canonical_chain.chain.get(&BlockNumber::from(1u64)), - Some(&BlockHash::from([0x01; 32])) - ); - - // Replace with a new chain - let mut new_chain = BTreeMap::new(); - new_chain.insert(BlockNumber::from(3u64), BlockHash::from([0x03; 32])); - new_chain.insert(BlockNumber::from(4u64), BlockHash::from([0x04; 32])); - new_chain.insert(BlockNumber::from(5u64), BlockHash::from([0x05; 32])); - - canonical_chain.replace(new_chain.clone()); - - // Verify replaced chain state - assert_eq!(canonical_chain.chain.len(), 3); - assert!(!canonical_chain.chain.contains_key(&BlockNumber::from(1u64))); - assert_eq!( - canonical_chain.chain.get(&BlockNumber::from(3u64)), - Some(&BlockHash::from([0x03; 32])) - ); - } - - #[test] - fn test_canonical_hash_canonical_chain() { - // Initialize a chain with some blocks - let mut chain = BTreeMap::new(); - chain.insert(BlockNumber::from(1u64), BlockHash::from([0x01; 32])); - chain.insert(BlockNumber::from(2u64), BlockHash::from([0x02; 32])); - chain.insert(BlockNumber::from(3u64), BlockHash::from([0x03; 32])); - - // Create an instance of a canonical chain - let canonical_chain = CanonicalChain::new(chain.clone()); - - // Check that the function returns the correct hash for a given block number - let block_number = BlockNumber::from(2u64); - let expected_hash = BlockHash::from([0x02; 32]); - assert_eq!(canonical_chain.canonical_hash(&block_number), Some(expected_hash)); - - // Check that a non-existent block returns None - let non_existent_block = BlockNumber::from(5u64); - assert_eq!(canonical_chain.canonical_hash(&non_existent_block), None); - } - - #[test] - fn test_canonical_number_canonical_chain() { - // Initialize a chain with some blocks - let mut chain = BTreeMap::new(); - chain.insert(BlockNumber::from(1u64), BlockHash::from([0x01; 32])); - chain.insert(BlockNumber::from(2u64), BlockHash::from([0x02; 32])); - chain.insert(BlockNumber::from(3u64), BlockHash::from([0x03; 32])); - - // Create an instance of a canonical chain - let canonical_chain = CanonicalChain::new(chain.clone()); - - // Check that the function returns the correct block number for a given block hash - let block_hash = BlockHash::from([0x02; 32]); - let expected_number = BlockNumber::from(2u64); - assert_eq!(canonical_chain.canonical_number(&block_hash), Some(expected_number)); - - // Check that a non-existent block hash returns None - let non_existent_hash = BlockHash::from([0x05; 32]); - assert_eq!(canonical_chain.canonical_number(&non_existent_hash), None); - } - - #[test] - fn test_extend_canonical_chain() { - // Initialize an empty chain - let mut canonical_chain = CanonicalChain::new(BTreeMap::new()); - - // Create an iterator with some blocks - let blocks = vec![ - (BlockNumber::from(1u64), BlockHash::from([0x01; 32])), - (BlockNumber::from(2u64), BlockHash::from([0x02; 32])), - ] - .into_iter(); - - // Extend the chain with the created blocks - canonical_chain.extend(blocks); - - // Check if the blocks were added correctly - assert_eq!(canonical_chain.chain.len(), 2); - assert_eq!( - canonical_chain.chain.get(&BlockNumber::from(1u64)), - Some(&BlockHash::from([0x01; 32])) - ); - assert_eq!( - canonical_chain.chain.get(&BlockNumber::from(2u64)), - Some(&BlockHash::from([0x02; 32])) - ); - - // Test extending with additional blocks again - let more_blocks = vec![(BlockNumber::from(3u64), BlockHash::from([0x03; 32]))].into_iter(); - canonical_chain.extend(more_blocks); - - assert_eq!(canonical_chain.chain.len(), 3); - assert_eq!( - canonical_chain.chain.get(&BlockNumber::from(3u64)), - Some(&BlockHash::from([0x03; 32])) - ); - } - - #[test] - fn test_retain_canonical_chain() { - // Initialize a chain with some blocks - let mut chain = BTreeMap::new(); - chain.insert(BlockNumber::from(1u64), BlockHash::from([0x01; 32])); - chain.insert(BlockNumber::from(2u64), BlockHash::from([0x02; 32])); - chain.insert(BlockNumber::from(3u64), BlockHash::from([0x03; 32])); - - // Create an instance of CanonicalChain - let mut canonical_chain = CanonicalChain::new(chain); - - // Retain only blocks with even block numbers - canonical_chain.retain(|number, _| number % 2 == 0); - - // Check if the chain only contains the block with number 2 - assert_eq!(canonical_chain.chain.len(), 1); - assert_eq!( - canonical_chain.chain.get(&BlockNumber::from(2u64)), - Some(&BlockHash::from([0x02; 32])) - ); - - // Ensure that the blocks with odd numbers were removed - assert_eq!(canonical_chain.chain.get(&BlockNumber::from(1u64)), None); - assert_eq!(canonical_chain.chain.get(&BlockNumber::from(3u64)), None); - } - - #[test] - fn test_tip_canonical_chain() { - // Initialize a chain with some blocks - let mut chain = BTreeMap::new(); - chain.insert(BlockNumber::from(1u64), BlockHash::from([0x01; 32])); - chain.insert(BlockNumber::from(2u64), BlockHash::from([0x02; 32])); - chain.insert(BlockNumber::from(3u64), BlockHash::from([0x03; 32])); - - // Create an instance of a canonical chain - let canonical_chain = CanonicalChain::new(chain); - - // Call the tip method and verify the returned value - let tip = canonical_chain.tip(); - assert_eq!(tip.number, BlockNumber::from(3u64)); - assert_eq!(tip.hash, BlockHash::from([0x03; 32])); - - // Test with an empty chain - let empty_chain = CanonicalChain::new(BTreeMap::new()); - let empty_tip = empty_chain.tip(); - assert_eq!(empty_tip.number, BlockNumber::default()); - assert_eq!(empty_tip.hash, BlockHash::default()); - } -} diff --git a/crates/blockchain-tree/src/chain.rs b/crates/blockchain-tree/src/chain.rs deleted file mode 100644 index e607d00d2..000000000 --- a/crates/blockchain-tree/src/chain.rs +++ /dev/null @@ -1,311 +0,0 @@ -//! A chain in a [`BlockchainTree`][super::BlockchainTree]. -//! -//! A [`Chain`] contains the state of accounts for the chain after execution of its constituent -//! blocks, as well as a list of the blocks the chain is composed of. - -use super::externals::TreeExternals; -use crate::BundleStateDataRef; -use alloy_eips::ForkBlock; -use alloy_primitives::{BlockHash, BlockNumber}; -use reth_blockchain_tree_api::{ - error::{BlockchainTreeError, InsertBlockErrorKind}, - BlockAttachment, BlockValidationKind, -}; -use reth_consensus::{ConsensusError, PostExecutionInput}; -use reth_evm::execute::{BlockExecutorProvider, Executor}; -use reth_execution_errors::BlockExecutionError; -use reth_execution_types::{Chain, ExecutionOutcome}; -use reth_primitives::{GotExpected, SealedBlockWithSenders, SealedHeader}; -use reth_provider::{ - providers::{BundleStateProvider, ConsistentDbView, TreeNodeTypes}, - DBProvider, FullExecutionDataProvider, HashedPostStateProvider, ProviderError, - StateRootProvider, TryIntoHistoricalStateProvider, -}; -use reth_revm::database::StateProviderDatabase; -use reth_trie::{updates::TrieUpdates, TrieInput}; -use reth_trie_parallel::root::ParallelStateRoot; -use std::{ - collections::BTreeMap, - ops::{Deref, DerefMut}, - time::Instant, -}; - -/// A chain in the blockchain tree that has functionality to execute blocks and append them to -/// itself. -#[derive(Clone, Debug, Default, PartialEq, Eq)] -pub struct AppendableChain { - chain: Chain, -} - -impl Deref for AppendableChain { - type Target = Chain; - - fn deref(&self) -> &Self::Target { - &self.chain - } -} - -impl DerefMut for AppendableChain { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.chain - } -} - -impl AppendableChain { - /// Create a new appendable chain from a given chain. - pub const fn new(chain: Chain) -> Self { - Self { chain } - } - - /// Get the chain. - pub fn into_inner(self) -> Chain { - self.chain - } - - /// Create a new chain that forks off of the canonical chain. - /// - /// if [`BlockValidationKind::Exhaustive`] is specified, the method will verify the state root - /// of the block. - pub fn new_canonical_fork( - block: SealedBlockWithSenders, - parent_header: &SealedHeader, - canonical_block_hashes: &BTreeMap, - canonical_fork: ForkBlock, - externals: &TreeExternals, - block_attachment: BlockAttachment, - block_validation_kind: BlockValidationKind, - ) -> Result - where - N: TreeNodeTypes, - E: BlockExecutorProvider, - { - let execution_outcome = ExecutionOutcome::default(); - let empty = BTreeMap::new(); - - let state_provider = BundleStateDataRef { - execution_outcome: &execution_outcome, - sidechain_block_hashes: &empty, - canonical_block_hashes, - canonical_fork, - }; - - let (bundle_state, trie_updates) = Self::validate_and_execute( - block.clone(), - parent_header, - state_provider, - externals, - block_attachment, - block_validation_kind, - )?; - - Ok(Self::new(Chain::new(vec![block], bundle_state, trie_updates))) - } - - /// Create a new chain that forks off of an existing sidechain. - /// - /// This differs from [`AppendableChain::new_canonical_fork`] in that this starts a new fork. - pub(crate) fn new_chain_fork( - &self, - block: SealedBlockWithSenders, - side_chain_block_hashes: BTreeMap, - canonical_block_hashes: &BTreeMap, - canonical_fork: ForkBlock, - externals: &TreeExternals, - block_validation_kind: BlockValidationKind, - ) -> Result - where - N: TreeNodeTypes, - E: BlockExecutorProvider, - { - let parent_number = - block.number.checked_sub(1).ok_or(BlockchainTreeError::GenesisBlockHasNoParent)?; - let parent = self.blocks().get(&parent_number).ok_or( - BlockchainTreeError::BlockNumberNotFoundInChain { block_number: parent_number }, - )?; - - let mut execution_outcome = self.execution_outcome().clone(); - - // Revert state to the state after execution of the parent block - execution_outcome.revert_to(parent.number); - - // Revert changesets to get the state of the parent that we need to apply the change. - let bundle_state_data = BundleStateDataRef { - execution_outcome: &execution_outcome, - sidechain_block_hashes: &side_chain_block_hashes, - canonical_block_hashes, - canonical_fork, - }; - let (block_state, _) = Self::validate_and_execute( - block.clone(), - parent, - bundle_state_data, - externals, - BlockAttachment::HistoricalFork, - block_validation_kind, - )?; - // extending will also optimize few things, mostly related to selfdestruct and wiping of - // storage. - execution_outcome.extend(block_state); - - // remove all receipts and reverts (except the last one), as they belong to the chain we - // forked from and not the new chain we are creating. - let size = execution_outcome.receipts().len(); - execution_outcome.receipts_mut().drain(0..size - 1); - execution_outcome.state_mut().take_n_reverts(size - 1); - execution_outcome.set_first_block(block.number); - - // If all is okay, return new chain back. Present chain is not modified. - Ok(Self::new(Chain::from_block(block, execution_outcome, None))) - } - - /// Validate and execute the given block that _extends the canonical chain_, validating its - /// state root after execution if possible and requested. - /// - /// Note: State root validation is limited to blocks that extend the canonical chain and is - /// optional, see [`BlockValidationKind`]. So this function takes two parameters to determine - /// if the state can and should be validated. - /// - [`BlockAttachment`] represents if the block extends the canonical chain, and thus we can - /// cache the trie state updates. - /// - [`BlockValidationKind`] determines if the state root __should__ be validated. - fn validate_and_execute( - block: SealedBlockWithSenders, - parent_block: &SealedHeader, - bundle_state_data_provider: EDP, - externals: &TreeExternals, - block_attachment: BlockAttachment, - block_validation_kind: BlockValidationKind, - ) -> Result<(ExecutionOutcome, Option), BlockExecutionError> - where - EDP: FullExecutionDataProvider, - N: TreeNodeTypes, - E: BlockExecutorProvider, - { - // some checks are done before blocks comes here. - externals.consensus.validate_header_against_parent(&block, parent_block)?; - - // get the state provider. - let canonical_fork = bundle_state_data_provider.canonical_fork(); - - // SAFETY: For block execution and parallel state root computation below we open multiple - // independent database transactions. Upon opening the database transaction the consistent - // view will check a current tip in the database and throw an error if it doesn't match - // the one recorded during initialization. - // It is safe to use consistent view without any special error handling as long as - // we guarantee that plain state cannot change during processing of new payload. - // The usage has to be re-evaluated if that was ever to change. - let consistent_view = - ConsistentDbView::new_with_latest_tip(externals.provider_factory.clone())?; - let state_provider = consistent_view - .provider_ro()? - // State root calculation can take a while, and we're sure no write transaction - // will be open in parallel. See https://github.com/paradigmxyz/reth/issues/7509. - .disable_long_read_transaction_safety() - .try_into_history_at_block(canonical_fork.number)?; - - let provider = BundleStateProvider::new(state_provider, bundle_state_data_provider); - - let db = StateProviderDatabase::new(&provider); - let executor = externals.executor_factory.executor(db); - let block_hash = block.hash(); - let block = block.unseal(); - - let state = executor.execute(&block)?; - externals.consensus.validate_block_post_execution( - &block, - PostExecutionInput::new(&state.receipts, &state.requests), - )?; - - let initial_execution_outcome = ExecutionOutcome::from((state, block.number)); - - // check state root if the block extends the canonical chain __and__ if state root - // validation was requested. - if block_validation_kind.is_exhaustive() { - // calculate and check state root - let start = Instant::now(); - let (state_root, trie_updates) = if block_attachment.is_canonical() { - let mut execution_outcome = - provider.block_execution_data_provider.execution_outcome().clone(); - execution_outcome.extend(initial_execution_outcome.clone()); - ParallelStateRoot::new( - consistent_view, - TrieInput::from_state(provider.hashed_post_state(execution_outcome.state())), - ) - .incremental_root_with_updates() - .map(|(root, updates)| (root, Some(updates))) - .map_err(ProviderError::from)? - } else { - let hashed_state = provider.hashed_post_state(initial_execution_outcome.state()); - let state_root = provider.state_root(hashed_state)?; - (state_root, None) - }; - if block.state_root != state_root { - return Err(ConsensusError::BodyStateRootDiff( - GotExpected { got: state_root, expected: block.state_root }.into(), - ) - .into()) - } - - tracing::debug!( - target: "blockchain_tree::chain", - number = block.number, - hash = %block_hash, - elapsed = ?start.elapsed(), - "Validated state root" - ); - - Ok((initial_execution_outcome, trie_updates)) - } else { - Ok((initial_execution_outcome, None)) - } - } - - /// Validate and execute the given block, and append it to this chain. - /// - /// This expects that the block's ancestors can be traced back to the `canonical_fork` (the - /// first parent block of the `block`'s chain that is in the canonical chain). - /// - /// In other words, expects a gap less (side-) chain: [`canonical_fork..block`] in order to be - /// able to __execute__ the block. - /// - /// CAUTION: This will only perform state root check if it's possible: if the `canonical_fork` - /// is the canonical head, or: state root check can't be performed if the given canonical is - /// __not__ the canonical head. - #[track_caller] - #[allow(clippy::too_many_arguments)] - pub(crate) fn append_block( - &mut self, - block: SealedBlockWithSenders, - side_chain_block_hashes: BTreeMap, - canonical_block_hashes: &BTreeMap, - externals: &TreeExternals, - canonical_fork: ForkBlock, - block_attachment: BlockAttachment, - block_validation_kind: BlockValidationKind, - ) -> Result<(), InsertBlockErrorKind> - where - N: TreeNodeTypes, - E: BlockExecutorProvider, - { - let parent_block = self.chain.tip(); - - let bundle_state_data = BundleStateDataRef { - execution_outcome: self.execution_outcome(), - sidechain_block_hashes: &side_chain_block_hashes, - canonical_block_hashes, - canonical_fork, - }; - - let (block_state, _) = Self::validate_and_execute( - block.clone(), - parent_block, - bundle_state_data, - externals, - block_attachment, - block_validation_kind, - )?; - // extend the state. - self.chain.append_block(block, block_state); - - Ok(()) - } -} diff --git a/crates/blockchain-tree/src/config.rs b/crates/blockchain-tree/src/config.rs deleted file mode 100644 index 8dda5dc82..000000000 --- a/crates/blockchain-tree/src/config.rs +++ /dev/null @@ -1,91 +0,0 @@ -//! Blockchain tree configuration - -/// The configuration for the blockchain tree. -#[derive(Clone, Copy, Debug)] -pub struct BlockchainTreeConfig { - /// Number of blocks after the last finalized block that we are storing. - /// - /// It should be more than the finalization window for the canonical chain. - max_blocks_in_chain: u64, - /// The number of blocks that can be re-orged (finalization windows) - max_reorg_depth: u64, - /// The number of unconnected blocks that we are buffering - max_unconnected_blocks: u32, - /// Number of additional block hashes to save in blockchain tree. For `BLOCKHASH` EVM opcode we - /// need last 256 block hashes. - /// - /// The total number of block hashes retained in-memory will be - /// `max(additional_canonical_block_hashes, max_reorg_depth)`, and for Ethereum that would - /// be 256. It covers both number of blocks required for reorg, and number of blocks - /// required for `BLOCKHASH` EVM opcode. - num_of_additional_canonical_block_hashes: u64, -} - -impl Default for BlockchainTreeConfig { - fn default() -> Self { - // The defaults for Ethereum mainnet - Self { - // Gasper allows reorgs of any length from 1 to 64. - max_reorg_depth: 64, - // This default is just an assumption. Has to be greater than the `max_reorg_depth`. - max_blocks_in_chain: 65, - // EVM requires that last 256 block hashes are available. - num_of_additional_canonical_block_hashes: 256, - // max unconnected blocks. - max_unconnected_blocks: 200, - } - } -} - -impl BlockchainTreeConfig { - /// Create tree configuration. - pub fn new( - max_reorg_depth: u64, - max_blocks_in_chain: u64, - num_of_additional_canonical_block_hashes: u64, - max_unconnected_blocks: u32, - ) -> Self { - assert!( - max_reorg_depth <= max_blocks_in_chain, - "Side chain size should be more than finalization window" - ); - Self { - max_blocks_in_chain, - max_reorg_depth, - num_of_additional_canonical_block_hashes, - max_unconnected_blocks, - } - } - - /// Return the maximum reorg depth. - pub const fn max_reorg_depth(&self) -> u64 { - self.max_reorg_depth - } - - /// Return the maximum number of blocks in one chain. - pub const fn max_blocks_in_chain(&self) -> u64 { - self.max_blocks_in_chain - } - - /// Return number of additional canonical block hashes that we need to retain - /// in order to have enough information for EVM execution. - pub const fn num_of_additional_canonical_block_hashes(&self) -> u64 { - self.num_of_additional_canonical_block_hashes - } - - /// Return total number of canonical hashes that we need to retain in order to have enough - /// information for reorg and EVM execution. - /// - /// It is calculated as the maximum of `max_reorg_depth` (which is the number of blocks required - /// for the deepest reorg possible according to the consensus protocol) and - /// `num_of_additional_canonical_block_hashes` (which is the number of block hashes needed to - /// satisfy the `BLOCKHASH` opcode in the EVM. See [`crate::BundleStateDataRef`]). - pub fn num_of_canonical_hashes(&self) -> u64 { - self.max_reorg_depth.max(self.num_of_additional_canonical_block_hashes) - } - - /// Return max number of unconnected blocks that we are buffering - pub const fn max_unconnected_blocks(&self) -> u32 { - self.max_unconnected_blocks - } -} diff --git a/crates/blockchain-tree/src/externals.rs b/crates/blockchain-tree/src/externals.rs deleted file mode 100644 index ad22417a9..000000000 --- a/crates/blockchain-tree/src/externals.rs +++ /dev/null @@ -1,106 +0,0 @@ -//! Blockchain tree externals. - -use alloy_primitives::{BlockHash, BlockNumber}; -use reth_consensus::{ConsensusError, FullConsensus}; -use reth_db::{static_file::BlockHashMask, tables}; -use reth_db_api::{cursor::DbCursorRO, transaction::DbTx}; -use reth_node_types::NodeTypesWithDB; -use reth_primitives::StaticFileSegment; -use reth_provider::{ - providers::ProviderNodeTypes, ChainStateBlockReader, ChainStateBlockWriter, ProviderFactory, - StaticFileProviderFactory, StatsReader, -}; -use reth_storage_errors::provider::ProviderResult; -use std::{collections::BTreeMap, sync::Arc}; - -pub use reth_provider::providers::{NodeTypesForTree, TreeNodeTypes}; - -/// A container for external components. -/// -/// This is a simple container for external components used throughout the blockchain tree -/// implementation: -/// -/// - A handle to the database -/// - A handle to the consensus engine -/// - The executor factory to execute blocks with -#[derive(Debug)] -pub struct TreeExternals { - /// The provider factory, used to commit the canonical chain, or unwind it. - pub(crate) provider_factory: ProviderFactory, - /// The consensus engine. - pub(crate) consensus: Arc>, - /// The executor factory to execute blocks with. - pub(crate) executor_factory: E, -} - -impl TreeExternals { - /// Create new tree externals. - pub fn new( - provider_factory: ProviderFactory, - consensus: Arc>, - executor_factory: E, - ) -> Self { - Self { provider_factory, consensus, executor_factory } - } -} - -impl TreeExternals { - /// Fetches the latest canonical block hashes by walking backwards from the head. - /// - /// Returns the hashes sorted by increasing block numbers - pub(crate) fn fetch_latest_canonical_hashes( - &self, - num_hashes: usize, - ) -> ProviderResult> { - // Fetch the latest canonical hashes from the database - let mut hashes = self - .provider_factory - .provider()? - .tx_ref() - .cursor_read::()? - .walk_back(None)? - .take(num_hashes) - .collect::, _>>()?; - - // Fetch the same number of latest canonical hashes from the static_files and merge them - // with the database hashes. It is needed due to the fact that we're writing - // directly to static_files in pipeline sync, but to the database in live sync, - // which means that the latest canonical hashes in the static file might be more recent - // than in the database, and vice versa, or even some ranges of the latest - // `num_hashes` blocks may be in database, and some ranges in static_files. - let static_file_provider = self.provider_factory.static_file_provider(); - let total_headers = static_file_provider.count_entries::()? as u64; - if total_headers > 0 { - let range = - total_headers.saturating_sub(1).saturating_sub(num_hashes as u64)..total_headers; - - hashes.extend(range.clone().zip(static_file_provider.fetch_range_with_predicate( - StaticFileSegment::Headers, - range, - |cursor, number| cursor.get_one::(number.into()), - |_| true, - )?)); - } - - // We may have fetched more than `num_hashes` hashes, so we need to truncate the result to - // the requested number. - let hashes = hashes.into_iter().rev().take(num_hashes).collect(); - Ok(hashes) - } - - pub(crate) fn fetch_latest_finalized_block_number( - &self, - ) -> ProviderResult> { - self.provider_factory.provider()?.last_finalized_block_number() - } - - pub(crate) fn save_finalized_block_number( - &self, - block_number: BlockNumber, - ) -> ProviderResult<()> { - let provider_rw = self.provider_factory.provider_rw()?; - provider_rw.save_finalized_block_number(block_number)?; - provider_rw.commit()?; - Ok(()) - } -} diff --git a/crates/blockchain-tree/src/lib.rs b/crates/blockchain-tree/src/lib.rs deleted file mode 100644 index 3f501bead..000000000 --- a/crates/blockchain-tree/src/lib.rs +++ /dev/null @@ -1,59 +0,0 @@ -//! Implementation of a tree-like structure for blockchains. -//! -//! The [`BlockchainTree`] can validate, execute, and revert blocks in multiple competing -//! sidechains. This structure is used for Reth's sync mode at the tip instead of the pipeline, and -//! is the primary executor and validator of payloads sent from the consensus layer. -//! -//! Blocks and their resulting state transitions are kept in-memory until they are persisted. -//! -//! ## Feature Flags -//! -//! - `test-utils`: Export utilities for testing - -#![doc( - html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", - html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", - issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/" -)] -#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -#![cfg_attr(not(test), warn(unused_crate_dependencies))] - -/// Re-export of the blockchain tree API. -pub use reth_blockchain_tree_api::*; - -pub mod blockchain_tree; -pub use blockchain_tree::BlockchainTree; - -pub mod block_indices; -pub use block_indices::BlockIndices; - -pub mod chain; -pub use chain::AppendableChain; - -pub mod config; -pub use config::BlockchainTreeConfig; - -pub mod externals; -pub use externals::TreeExternals; - -pub mod shareable; -pub use shareable::ShareableBlockchainTree; - -mod bundle; -pub use bundle::{BundleStateDataRef, ExecutionData}; - -/// Buffer of not executed blocks. -pub mod block_buffer; -mod canonical_chain; - -/// Common blockchain tree metrics. -pub mod metrics; - -pub use block_buffer::BlockBuffer; - -/// Implementation of Tree traits that does nothing. -pub mod noop; - -mod state; - -use aquamarine as _; diff --git a/crates/blockchain-tree/src/metrics.rs b/crates/blockchain-tree/src/metrics.rs deleted file mode 100644 index 121d0a697..000000000 --- a/crates/blockchain-tree/src/metrics.rs +++ /dev/null @@ -1,153 +0,0 @@ -use metrics::Histogram; -use reth_metrics::{ - metrics::{Counter, Gauge}, - Metrics, -}; -use std::time::{Duration, Instant}; - -/// Metrics for the blockchain tree block buffer -#[derive(Metrics)] -#[metrics(scope = "blockchain_tree.block_buffer")] -pub struct BlockBufferMetrics { - /// Total blocks in the block buffer - pub blocks: Gauge, -} - -#[derive(Debug)] -pub(crate) struct MakeCanonicalDurationsRecorder { - start: Instant, - pub(crate) actions: Vec<(MakeCanonicalAction, Duration)>, - latest: Option, - current_metrics: MakeCanonicalMetrics, -} - -impl Default for MakeCanonicalDurationsRecorder { - fn default() -> Self { - Self { - start: Instant::now(), - actions: Vec::new(), - latest: None, - current_metrics: MakeCanonicalMetrics::default(), - } - } -} - -impl MakeCanonicalDurationsRecorder { - /// Records the duration since last record, saves it for future logging and instantly reports as - /// a metric with `action` label. - pub(crate) fn record_relative(&mut self, action: MakeCanonicalAction) { - let elapsed = self.start.elapsed(); - let duration = elapsed - self.latest.unwrap_or_default(); - - self.actions.push((action, duration)); - self.current_metrics.record(action, duration); - self.latest = Some(elapsed); - } -} - -/// Metrics for the entire blockchain tree -#[derive(Metrics)] -#[metrics(scope = "blockchain_tree")] -pub struct TreeMetrics { - /// Total number of sidechains (not including the canonical chain) - pub sidechains: Gauge, - /// The highest block number in the canonical chain - pub canonical_chain_height: Gauge, - /// The number of reorgs - pub reorgs: Counter, - /// The latest reorg depth - pub latest_reorg_depth: Gauge, - /// Longest sidechain height - pub longest_sidechain_height: Gauge, - /// The number of times cached trie updates were used for insert. - pub trie_updates_insert_cached: Counter, - /// The number of times trie updates were recomputed for insert. - pub trie_updates_insert_recomputed: Counter, -} - -/// Represents actions for making a canonical chain. -#[derive(Debug, Copy, Clone)] -pub(crate) enum MakeCanonicalAction { - /// Cloning old blocks for canonicalization. - CloneOldBlocks, - /// Finding the canonical header. - FindCanonicalHeader, - /// Splitting the chain for canonicalization. - SplitChain, - /// Splitting chain forks for canonicalization. - SplitChainForks, - /// Merging all chains for canonicalization. - MergeAllChains, - /// Updating the canonical index during canonicalization. - UpdateCanonicalIndex, - /// Retrieving (cached or recomputed) state trie updates - RetrieveStateTrieUpdates, - /// Committing the canonical chain to the database. - CommitCanonicalChainToDatabase, - /// Reverting the canonical chain from the database. - RevertCanonicalChainFromDatabase, - /// Inserting an old canonical chain. - InsertOldCanonicalChain, - /// Clearing trie updates of other children chains after fork choice update. - ClearTrieUpdatesForOtherChildren, -} - -/// Canonicalization metrics -#[derive(Metrics)] -#[metrics(scope = "blockchain_tree.make_canonical")] -struct MakeCanonicalMetrics { - /// Duration of the clone old blocks action. - clone_old_blocks: Histogram, - /// Duration of the find canonical header action. - find_canonical_header: Histogram, - /// Duration of the split chain action. - split_chain: Histogram, - /// Duration of the split chain forks action. - split_chain_forks: Histogram, - /// Duration of the merge all chains action. - merge_all_chains: Histogram, - /// Duration of the update canonical index action. - update_canonical_index: Histogram, - /// Duration of the retrieve state trie updates action. - retrieve_state_trie_updates: Histogram, - /// Duration of the commit canonical chain to database action. - commit_canonical_chain_to_database: Histogram, - /// Duration of the revert canonical chain from database action. - revert_canonical_chain_from_database: Histogram, - /// Duration of the insert old canonical chain action. - insert_old_canonical_chain: Histogram, - /// Duration of the clear trie updates of other children chains after fork choice update - /// action. - clear_trie_updates_for_other_children: Histogram, -} - -impl MakeCanonicalMetrics { - /// Records the duration for the given action. - pub(crate) fn record(&self, action: MakeCanonicalAction, duration: Duration) { - match action { - MakeCanonicalAction::CloneOldBlocks => self.clone_old_blocks.record(duration), - MakeCanonicalAction::FindCanonicalHeader => self.find_canonical_header.record(duration), - MakeCanonicalAction::SplitChain => self.split_chain.record(duration), - MakeCanonicalAction::SplitChainForks => self.split_chain_forks.record(duration), - MakeCanonicalAction::MergeAllChains => self.merge_all_chains.record(duration), - MakeCanonicalAction::UpdateCanonicalIndex => { - self.update_canonical_index.record(duration) - } - MakeCanonicalAction::RetrieveStateTrieUpdates => { - self.retrieve_state_trie_updates.record(duration) - } - MakeCanonicalAction::CommitCanonicalChainToDatabase => { - self.commit_canonical_chain_to_database.record(duration) - } - MakeCanonicalAction::RevertCanonicalChainFromDatabase => { - self.revert_canonical_chain_from_database.record(duration) - } - MakeCanonicalAction::InsertOldCanonicalChain => { - self.insert_old_canonical_chain.record(duration) - } - MakeCanonicalAction::ClearTrieUpdatesForOtherChildren => { - self.clear_trie_updates_for_other_children.record(duration) - } - } - } -} diff --git a/crates/blockchain-tree/src/noop.rs b/crates/blockchain-tree/src/noop.rs deleted file mode 100644 index f5d2ad8c6..000000000 --- a/crates/blockchain-tree/src/noop.rs +++ /dev/null @@ -1,140 +0,0 @@ -use alloy_eips::BlockNumHash; -use alloy_primitives::{BlockHash, BlockNumber}; -use reth_blockchain_tree_api::{ - self, - error::{BlockchainTreeError, CanonicalError, InsertBlockError, ProviderError}, - BlockValidationKind, BlockchainTreeEngine, BlockchainTreeViewer, CanonicalOutcome, - InsertPayloadOk, -}; -use reth_primitives::{EthPrimitives, Receipt, SealedBlock, SealedBlockWithSenders, SealedHeader}; -use reth_provider::{ - BlockchainTreePendingStateProvider, CanonStateNotificationSender, CanonStateNotifications, - CanonStateSubscriptions, FullExecutionDataProvider, NodePrimitivesProvider, -}; -use reth_storage_errors::provider::ProviderResult; -use std::collections::BTreeMap; - -/// A `BlockchainTree` that does nothing. -/// -/// Caution: this is only intended for testing purposes, or for wiring components together. -#[derive(Debug, Clone, Default)] -#[non_exhaustive] -pub struct NoopBlockchainTree { - /// Broadcast channel for canon state changes notifications. - pub canon_state_notification_sender: Option, -} - -impl NoopBlockchainTree { - /// Create a new `NoopBlockchainTree` with a canon state notification sender. - pub const fn with_canon_state_notifications( - canon_state_notification_sender: CanonStateNotificationSender, - ) -> Self { - Self { canon_state_notification_sender: Some(canon_state_notification_sender) } - } -} - -impl BlockchainTreeEngine for NoopBlockchainTree { - fn buffer_block(&self, _block: SealedBlockWithSenders) -> Result<(), InsertBlockError> { - Ok(()) - } - - fn insert_block( - &self, - block: SealedBlockWithSenders, - _validation_kind: BlockValidationKind, - ) -> Result { - Err(InsertBlockError::tree_error( - BlockchainTreeError::BlockHashNotFoundInChain { block_hash: block.hash() }, - block.block, - )) - } - - fn finalize_block(&self, _finalized_block: BlockNumber) -> ProviderResult<()> { - Ok(()) - } - - fn connect_buffered_blocks_to_canonical_hashes_and_finalize( - &self, - _last_finalized_block: BlockNumber, - ) -> Result<(), CanonicalError> { - Ok(()) - } - - fn update_block_hashes_and_clear_buffered( - &self, - ) -> Result, CanonicalError> { - Ok(BTreeMap::new()) - } - - fn connect_buffered_blocks_to_canonical_hashes(&self) -> Result<(), CanonicalError> { - Ok(()) - } - - fn make_canonical(&self, block_hash: BlockHash) -> Result { - Err(BlockchainTreeError::BlockHashNotFoundInChain { block_hash }.into()) - } -} - -impl BlockchainTreeViewer for NoopBlockchainTree { - fn header_by_hash(&self, _hash: BlockHash) -> Option { - None - } - - fn block_by_hash(&self, _hash: BlockHash) -> Option { - None - } - - fn block_with_senders_by_hash(&self, _hash: BlockHash) -> Option { - None - } - - fn buffered_header_by_hash(&self, _block_hash: BlockHash) -> Option { - None - } - - fn is_canonical(&self, _block_hash: BlockHash) -> Result { - Ok(false) - } - - fn lowest_buffered_ancestor(&self, _hash: BlockHash) -> Option { - None - } - - fn canonical_tip(&self) -> BlockNumHash { - Default::default() - } - - fn pending_block_num_hash(&self) -> Option { - None - } - - fn pending_block_and_receipts(&self) -> Option<(SealedBlock, Vec)> { - None - } - - fn receipts_by_block_hash(&self, _block_hash: BlockHash) -> Option> { - None - } -} - -impl BlockchainTreePendingStateProvider for NoopBlockchainTree { - fn find_pending_state_provider( - &self, - _block_hash: BlockHash, - ) -> Option> { - None - } -} - -impl NodePrimitivesProvider for NoopBlockchainTree { - type Primitives = EthPrimitives; -} - -impl CanonStateSubscriptions for NoopBlockchainTree { - fn subscribe_to_canonical_state(&self) -> CanonStateNotifications { - self.canon_state_notification_sender - .as_ref() - .map(|sender| sender.subscribe()) - .unwrap_or_else(|| CanonStateNotificationSender::new(1).subscribe()) - } -} diff --git a/crates/blockchain-tree/src/shareable.rs b/crates/blockchain-tree/src/shareable.rs deleted file mode 100644 index 6cb36cfab..000000000 --- a/crates/blockchain-tree/src/shareable.rs +++ /dev/null @@ -1,205 +0,0 @@ -//! Wrapper around `BlockchainTree` that allows for it to be shared. - -use crate::externals::TreeNodeTypes; - -use super::BlockchainTree; -use alloy_eips::BlockNumHash; -use alloy_primitives::{BlockHash, BlockNumber}; -use parking_lot::RwLock; -use reth_blockchain_tree_api::{ - error::{CanonicalError, InsertBlockError}, - BlockValidationKind, BlockchainTreeEngine, BlockchainTreeViewer, CanonicalOutcome, - InsertPayloadOk, -}; -use reth_evm::execute::BlockExecutorProvider; -use reth_node_types::NodeTypesWithDB; -use reth_primitives::{Receipt, SealedBlock, SealedBlockWithSenders, SealedHeader}; -use reth_provider::{ - providers::ProviderNodeTypes, BlockchainTreePendingStateProvider, CanonStateNotifications, - CanonStateSubscriptions, FullExecutionDataProvider, NodePrimitivesProvider, ProviderError, -}; -use reth_storage_errors::provider::ProviderResult; -use std::{collections::BTreeMap, sync::Arc}; -use tracing::trace; - -/// Shareable blockchain tree that is behind a `RwLock` -#[derive(Clone, Debug)] -pub struct ShareableBlockchainTree { - /// `BlockchainTree` - pub tree: Arc>>, -} - -impl ShareableBlockchainTree { - /// Create a new shareable database. - pub fn new(tree: BlockchainTree) -> Self { - Self { tree: Arc::new(RwLock::new(tree)) } - } -} - -impl BlockchainTreeEngine for ShareableBlockchainTree -where - N: TreeNodeTypes, - E: BlockExecutorProvider, -{ - fn buffer_block(&self, block: SealedBlockWithSenders) -> Result<(), InsertBlockError> { - let mut tree = self.tree.write(); - // Blockchain tree metrics shouldn't be updated here, see - // `BlockchainTree::update_chains_metrics` documentation. - tree.buffer_block(block) - } - - fn insert_block( - &self, - block: SealedBlockWithSenders, - validation_kind: BlockValidationKind, - ) -> Result { - trace!(target: "blockchain_tree", hash = %block.hash(), number = block.number, parent_hash = %block.parent_hash, "Inserting block"); - let mut tree = self.tree.write(); - let res = tree.insert_block(block, validation_kind); - tree.update_chains_metrics(); - res - } - - fn finalize_block(&self, finalized_block: BlockNumber) -> ProviderResult<()> { - trace!(target: "blockchain_tree", finalized_block, "Finalizing block"); - let mut tree = self.tree.write(); - tree.finalize_block(finalized_block)?; - tree.update_chains_metrics(); - - Ok(()) - } - - fn connect_buffered_blocks_to_canonical_hashes_and_finalize( - &self, - last_finalized_block: BlockNumber, - ) -> Result<(), CanonicalError> { - trace!(target: "blockchain_tree", last_finalized_block, "Connecting buffered blocks to canonical hashes and finalizing the tree"); - let mut tree = self.tree.write(); - let res = - tree.connect_buffered_blocks_to_canonical_hashes_and_finalize(last_finalized_block); - tree.update_chains_metrics(); - Ok(res?) - } - - fn update_block_hashes_and_clear_buffered( - &self, - ) -> Result, CanonicalError> { - let mut tree = self.tree.write(); - let res = tree.update_block_hashes_and_clear_buffered(); - tree.update_chains_metrics(); - Ok(res?) - } - - fn connect_buffered_blocks_to_canonical_hashes(&self) -> Result<(), CanonicalError> { - trace!(target: "blockchain_tree", "Connecting buffered blocks to canonical hashes"); - let mut tree = self.tree.write(); - let res = tree.connect_buffered_blocks_to_canonical_hashes(); - tree.update_chains_metrics(); - Ok(res?) - } - - fn make_canonical(&self, block_hash: BlockHash) -> Result { - trace!(target: "blockchain_tree", %block_hash, "Making block canonical"); - let mut tree = self.tree.write(); - let res = tree.make_canonical(block_hash); - tree.update_chains_metrics(); - res - } -} - -impl BlockchainTreeViewer for ShareableBlockchainTree -where - N: TreeNodeTypes, - E: BlockExecutorProvider, -{ - fn header_by_hash(&self, hash: BlockHash) -> Option { - trace!(target: "blockchain_tree", ?hash, "Returning header by hash"); - self.tree.read().sidechain_block_by_hash(hash).map(|b| b.sealed_header().clone()) - } - - fn block_by_hash(&self, block_hash: BlockHash) -> Option { - trace!(target: "blockchain_tree", ?block_hash, "Returning block by hash"); - self.tree.read().sidechain_block_by_hash(block_hash).cloned() - } - - fn block_with_senders_by_hash(&self, block_hash: BlockHash) -> Option { - trace!(target: "blockchain_tree", ?block_hash, "Returning block by hash"); - self.tree.read().block_with_senders_by_hash(block_hash).cloned() - } - - fn buffered_header_by_hash(&self, block_hash: BlockHash) -> Option { - self.tree.read().get_buffered_block(&block_hash).map(|b| b.sealed_header().clone()) - } - - fn is_canonical(&self, hash: BlockHash) -> Result { - trace!(target: "blockchain_tree", ?hash, "Checking if block is canonical"); - self.tree.read().is_block_hash_canonical(&hash) - } - - fn lowest_buffered_ancestor(&self, hash: BlockHash) -> Option { - trace!(target: "blockchain_tree", ?hash, "Returning lowest buffered ancestor"); - self.tree.read().lowest_buffered_ancestor(&hash).cloned() - } - - fn canonical_tip(&self) -> BlockNumHash { - trace!(target: "blockchain_tree", "Returning canonical tip"); - self.tree.read().block_indices().canonical_tip() - } - - fn pending_block_num_hash(&self) -> Option { - trace!(target: "blockchain_tree", "Returning first pending block"); - self.tree.read().block_indices().pending_block_num_hash() - } - - fn pending_block(&self) -> Option { - trace!(target: "blockchain_tree", "Returning first pending block"); - self.tree.read().pending_block().cloned() - } - - fn pending_block_and_receipts(&self) -> Option<(SealedBlock, Vec)> { - let tree = self.tree.read(); - let pending_block = tree.pending_block()?.clone(); - let receipts = - tree.receipts_by_block_hash(pending_block.hash())?.into_iter().cloned().collect(); - Some((pending_block, receipts)) - } - - fn receipts_by_block_hash(&self, block_hash: BlockHash) -> Option> { - let tree = self.tree.read(); - Some(tree.receipts_by_block_hash(block_hash)?.into_iter().cloned().collect()) - } -} - -impl BlockchainTreePendingStateProvider for ShareableBlockchainTree -where - N: TreeNodeTypes, - E: BlockExecutorProvider, -{ - fn find_pending_state_provider( - &self, - block_hash: BlockHash, - ) -> Option> { - trace!(target: "blockchain_tree", ?block_hash, "Finding pending state provider"); - let provider = self.tree.read().post_state_data(block_hash)?; - Some(Box::new(provider)) - } -} - -impl NodePrimitivesProvider for ShareableBlockchainTree -where - N: ProviderNodeTypes, - E: Send + Sync, -{ - type Primitives = N::Primitives; -} - -impl CanonStateSubscriptions for ShareableBlockchainTree -where - N: TreeNodeTypes, - E: Send + Sync, -{ - fn subscribe_to_canonical_state(&self) -> CanonStateNotifications { - trace!(target: "blockchain_tree", "Registered subscriber for canonical state"); - self.tree.read().subscribe_canon_state() - } -} diff --git a/crates/blockchain-tree/src/state.rs b/crates/blockchain-tree/src/state.rs deleted file mode 100644 index 762ced6bf..000000000 --- a/crates/blockchain-tree/src/state.rs +++ /dev/null @@ -1,430 +0,0 @@ -//! Blockchain tree state. - -use crate::{AppendableChain, BlockBuffer, BlockIndices}; -use alloy_primitives::{BlockHash, BlockNumber}; -use reth_primitives::{Receipt, SealedBlock, SealedBlockWithSenders}; -use std::collections::{BTreeMap, HashMap}; - -/// Container to hold the state of the blockchain tree. -#[derive(Debug)] -pub(crate) struct TreeState { - /// Keeps track of new unique identifiers for chains - block_chain_id_generator: u64, - /// The tracked chains and their current data. - pub(crate) chains: HashMap, - /// Indices to block and their connection to the canonical chain. - /// - /// This gets modified by the tree itself and is read from engine API/RPC to access the pending - /// block for example. - pub(crate) block_indices: BlockIndices, - /// Unconnected block buffer. - pub(crate) buffered_blocks: BlockBuffer, -} - -impl TreeState { - /// Initializes the tree state with the given last finalized block number and last canonical - /// hashes. - pub(crate) fn new( - last_finalized_block_number: BlockNumber, - last_canonical_hashes: impl IntoIterator, - buffer_limit: u32, - ) -> Self { - Self { - block_chain_id_generator: 0, - chains: Default::default(), - block_indices: BlockIndices::new( - last_finalized_block_number, - BTreeMap::from_iter(last_canonical_hashes), - ), - buffered_blocks: BlockBuffer::new(buffer_limit), - } - } - - /// Issues a new unique identifier for a new sidechain. - #[inline] - fn next_id(&mut self) -> SidechainId { - let id = self.block_chain_id_generator; - self.block_chain_id_generator += 1; - SidechainId(id) - } - - /// Expose internal indices of the `BlockchainTree`. - #[inline] - pub(crate) const fn block_indices(&self) -> &BlockIndices { - &self.block_indices - } - - /// Returns the block with matching hash from any side-chain. - /// - /// Caution: This will not return blocks from the canonical chain. - #[inline] - pub(crate) fn block_by_hash(&self, block_hash: BlockHash) -> Option<&SealedBlock> { - self.block_with_senders_by_hash(block_hash).map(|block| &block.block) - } - - /// Returns the block with matching hash from any side-chain. - /// - /// Caution: This will not return blocks from the canonical chain. - #[inline] - pub(crate) fn block_with_senders_by_hash( - &self, - block_hash: BlockHash, - ) -> Option<&SealedBlockWithSenders> { - let id = self.block_indices.get_side_chain_id(&block_hash)?; - let chain = self.chains.get(&id)?; - chain.block_with_senders(block_hash) - } - - /// Returns the block's receipts with matching hash from any side-chain. - /// - /// Caution: This will not return blocks from the canonical chain. - pub(crate) fn receipts_by_block_hash(&self, block_hash: BlockHash) -> Option> { - let id = self.block_indices.get_side_chain_id(&block_hash)?; - let chain = self.chains.get(&id)?; - chain.receipts_by_block_hash(block_hash) - } - - /// Insert a chain into the tree. - /// - /// Inserts a chain into the tree and builds the block indices. - pub(crate) fn insert_chain(&mut self, chain: AppendableChain) -> Option { - if chain.is_empty() { - return None - } - let chain_id = self.next_id(); - - self.block_indices.insert_chain(chain_id, &chain); - // add chain_id -> chain index - self.chains.insert(chain_id, chain); - Some(chain_id) - } - - /// Checks the block buffer for the given block. - pub(crate) fn get_buffered_block(&self, hash: &BlockHash) -> Option<&SealedBlockWithSenders> { - self.buffered_blocks.block(hash) - } - - /// Gets the lowest ancestor for the given block in the block buffer. - pub(crate) fn lowest_buffered_ancestor( - &self, - hash: &BlockHash, - ) -> Option<&SealedBlockWithSenders> { - self.buffered_blocks.lowest_ancestor(hash) - } -} - -/// The ID of a sidechain internally in a [`BlockchainTree`][super::BlockchainTree]. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)] -pub(crate) struct SidechainId(u64); - -impl From for u64 { - fn from(value: SidechainId) -> Self { - value.0 - } -} - -#[cfg(test)] -impl From for SidechainId { - fn from(value: u64) -> Self { - Self(value) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::canonical_chain::CanonicalChain; - use alloy_primitives::B256; - use reth_execution_types::Chain; - use reth_provider::ExecutionOutcome; - - #[test] - fn test_tree_state_initialization() { - // Set up some dummy data for initialization - let last_finalized_block_number = 10u64; - let last_canonical_hashes = vec![(9u64, B256::random()), (10u64, B256::random())]; - let buffer_limit = 5; - - // Initialize the tree state - let tree_state = TreeState::new( - last_finalized_block_number, - last_canonical_hashes.clone(), - buffer_limit, - ); - - // Verify the tree state after initialization - assert_eq!(tree_state.block_chain_id_generator, 0); - assert_eq!(tree_state.block_indices().last_finalized_block(), last_finalized_block_number); - assert_eq!( - *tree_state.block_indices.canonical_chain().inner(), - *CanonicalChain::new(last_canonical_hashes.into_iter().collect()).inner() - ); - assert!(tree_state.chains.is_empty()); - assert!(tree_state.buffered_blocks.lru.is_empty()); - } - - #[test] - fn test_tree_state_next_id() { - // Initialize the tree state - let mut tree_state = TreeState::new(0, vec![], 5); - - // Generate a few sidechain IDs - let first_id = tree_state.next_id(); - let second_id = tree_state.next_id(); - - // Verify the generated sidechain IDs and the updated generator state - assert_eq!(first_id, SidechainId(0)); - assert_eq!(second_id, SidechainId(1)); - assert_eq!(tree_state.block_chain_id_generator, 2); - } - - #[test] - fn test_tree_state_insert_chain() { - // Initialize tree state - let mut tree_state = TreeState::new(0, vec![], 5); - - // Create a chain with two blocks - let block: SealedBlockWithSenders = Default::default(); - let block1_hash = B256::random(); - let block2_hash = B256::random(); - - let mut block1 = block.clone(); - let mut block2 = block; - - block1.block.set_hash(block1_hash); - block1.block.set_block_number(9); - block2.block.set_hash(block2_hash); - block2.block.set_block_number(10); - - let chain = AppendableChain::new(Chain::new( - [block1, block2], - Default::default(), - Default::default(), - )); - - // Insert the chain into the TreeState - let chain_id = tree_state.insert_chain(chain).unwrap(); - - // Verify the chain ID and that it was added to the chains collection - assert_eq!(chain_id, SidechainId(0)); - assert!(tree_state.chains.contains_key(&chain_id)); - - // Ensure that the block indices are updated - assert_eq!( - tree_state.block_indices.get_side_chain_id(&block1_hash).unwrap(), - SidechainId(0) - ); - assert_eq!( - tree_state.block_indices.get_side_chain_id(&block2_hash).unwrap(), - SidechainId(0) - ); - - // Ensure that the block chain ID generator was updated - assert_eq!(tree_state.block_chain_id_generator, 1); - - // Create an empty chain - let chain_empty = AppendableChain::new(Chain::default()); - - // Insert the empty chain into the tree state - let chain_id = tree_state.insert_chain(chain_empty); - - // Ensure that the empty chain was not inserted - assert!(chain_id.is_none()); - - // Nothing should have changed and no new chain should have been added - assert!(tree_state.chains.contains_key(&SidechainId(0))); - assert!(!tree_state.chains.contains_key(&SidechainId(1))); - assert_eq!( - tree_state.block_indices.get_side_chain_id(&block1_hash).unwrap(), - SidechainId(0) - ); - assert_eq!( - tree_state.block_indices.get_side_chain_id(&block2_hash).unwrap(), - SidechainId(0) - ); - assert_eq!(tree_state.block_chain_id_generator, 1); - } - - #[test] - fn test_block_by_hash_side_chain() { - // Initialize a tree state with some dummy data - let mut tree_state = TreeState::new(0, vec![], 5); - - // Create two side-chain blocks with random hashes - let block1_hash = B256::random(); - let block2_hash = B256::random(); - - let mut block1: SealedBlockWithSenders = Default::default(); - let mut block2: SealedBlockWithSenders = Default::default(); - - block1.block.set_hash(block1_hash); - block1.block.set_block_number(9); - block2.block.set_hash(block2_hash); - block2.block.set_block_number(10); - - // Create an chain with these blocks - let chain = AppendableChain::new(Chain::new( - vec![block1.clone(), block2.clone()], - Default::default(), - Default::default(), - )); - - // Insert the side chain into the TreeState - tree_state.insert_chain(chain).unwrap(); - - // Retrieve the blocks by their hashes - let retrieved_block1 = tree_state.block_by_hash(block1_hash); - assert_eq!(*retrieved_block1.unwrap(), block1.block); - - let retrieved_block2 = tree_state.block_by_hash(block2_hash); - assert_eq!(*retrieved_block2.unwrap(), block2.block); - - // Test block_by_hash with a random hash that doesn't exist - let non_existent_hash = B256::random(); - let result = tree_state.block_by_hash(non_existent_hash); - - // Ensure that no block is found - assert!(result.is_none()); - } - - #[test] - fn test_block_with_senders_by_hash() { - // Initialize a tree state with some dummy data - let mut tree_state = TreeState::new(0, vec![], 5); - - // Create two side-chain blocks with random hashes - let block1_hash = B256::random(); - let block2_hash = B256::random(); - - let mut block1: SealedBlockWithSenders = Default::default(); - let mut block2: SealedBlockWithSenders = Default::default(); - - block1.block.set_hash(block1_hash); - block1.block.set_block_number(9); - block2.block.set_hash(block2_hash); - block2.block.set_block_number(10); - - // Create a chain with these blocks - let chain = AppendableChain::new(Chain::new( - vec![block1.clone(), block2.clone()], - Default::default(), - Default::default(), - )); - - // Insert the side chain into the TreeState - tree_state.insert_chain(chain).unwrap(); - - // Test to retrieve the blocks with senders by their hashes - let retrieved_block1 = tree_state.block_with_senders_by_hash(block1_hash); - assert_eq!(*retrieved_block1.unwrap(), block1); - - let retrieved_block2 = tree_state.block_with_senders_by_hash(block2_hash); - assert_eq!(*retrieved_block2.unwrap(), block2); - - // Test block_with_senders_by_hash with a random hash that doesn't exist - let non_existent_hash = B256::random(); - let result = tree_state.block_with_senders_by_hash(non_existent_hash); - - // Ensure that no block is found - assert!(result.is_none()); - } - - #[test] - fn test_get_buffered_block() { - // Initialize a tree state with some dummy data - let mut tree_state = TreeState::new(0, vec![], 5); - - // Create a block with a random hash and add it to the buffer - let block_hash = B256::random(); - let mut block: SealedBlockWithSenders = Default::default(); - block.block.set_hash(block_hash); - - // Add the block to the buffered blocks in the TreeState - tree_state.buffered_blocks.insert_block(block.clone()); - - // Test get_buffered_block to retrieve the block by its hash - let retrieved_block = tree_state.get_buffered_block(&block_hash); - assert_eq!(*retrieved_block.unwrap(), block); - - // Test get_buffered_block with a non-existent hash - let non_existent_hash = B256::random(); - let result = tree_state.get_buffered_block(&non_existent_hash); - - // Ensure that no block is found - assert!(result.is_none()); - } - - #[test] - fn test_lowest_buffered_ancestor() { - // Initialize a tree state with some dummy data - let mut tree_state = TreeState::new(0, vec![], 5); - - // Create blocks with random hashes and set up parent-child relationships - let ancestor_hash = B256::random(); - let descendant_hash = B256::random(); - - let mut ancestor_block: SealedBlockWithSenders = Default::default(); - let mut descendant_block: SealedBlockWithSenders = Default::default(); - - ancestor_block.block.set_hash(ancestor_hash); - descendant_block.block.set_hash(descendant_hash); - descendant_block.block.set_parent_hash(ancestor_hash); - - // Insert the blocks into the buffer - tree_state.buffered_blocks.insert_block(ancestor_block.clone()); - tree_state.buffered_blocks.insert_block(descendant_block.clone()); - - // Test lowest_buffered_ancestor for the descendant block - let lowest_ancestor = tree_state.lowest_buffered_ancestor(&descendant_hash); - assert!(lowest_ancestor.is_some()); - assert_eq!(lowest_ancestor.unwrap().block.hash(), ancestor_hash); - - // Test lowest_buffered_ancestor with a non-existent hash - let non_existent_hash = B256::random(); - let result = tree_state.lowest_buffered_ancestor(&non_existent_hash); - - // Ensure that no ancestor is found - assert!(result.is_none()); - } - - #[test] - fn test_receipts_by_block_hash() { - // Initialize a tree state with some dummy data - let mut tree_state = TreeState::new(0, vec![], 5); - - // Create a block with a random hash and receipts - let block_hash = B256::random(); - let receipt1 = Receipt::default(); - let receipt2 = Receipt::default(); - - let mut block: SealedBlockWithSenders = Default::default(); - block.block.set_hash(block_hash); - - let receipts = vec![receipt1, receipt2]; - - // Create a chain with the block and its receipts - let chain = AppendableChain::new(Chain::new( - vec![block.clone()], - ExecutionOutcome { receipts: receipts.clone().into(), ..Default::default() }, - Default::default(), - )); - - // Insert the chain into the TreeState - tree_state.insert_chain(chain).unwrap(); - - // Test receipts_by_block_hash for the inserted block - let retrieved_receipts = tree_state.receipts_by_block_hash(block_hash); - assert!(retrieved_receipts.is_some()); - - // Check if the correct receipts are returned - let receipts_ref: Vec<&Receipt> = receipts.iter().collect(); - assert_eq!(retrieved_receipts.unwrap(), receipts_ref); - - // Test receipts_by_block_hash with a non-existent block hash - let non_existent_hash = B256::random(); - let result = tree_state.receipts_by_block_hash(non_existent_hash); - - // Ensure that no receipts are found - assert!(result.is_none()); - } -}