diff --git a/crates/executor/src/blockchain_tree/block_indices.rs b/crates/executor/src/blockchain_tree/block_indices.rs index 0a96fc89d9..586c277364 100644 --- a/crates/executor/src/blockchain_tree/block_indices.rs +++ b/crates/executor/src/blockchain_tree/block_indices.rs @@ -12,10 +12,6 @@ use std::collections::{hash_map::Entry, BTreeMap, BTreeSet, HashMap, HashSet}; pub struct BlockIndices { /// Last finalized block. last_finalized_block: BlockNumber, - /// For EVM's "BLOCKHASH" opcode we require last 256 block hashes. So we need to specify - /// at least `additional_canonical_block_hashes`+`max_reorg_depth`, for eth that would be - /// 256+64. - num_of_additional_canonical_block_hashes: u64, /// 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 chain specific indices. @@ -34,25 +30,17 @@ impl BlockIndices { /// Create new block indices structure pub fn new( last_finalized_block: BlockNumber, - num_of_additional_canonical_block_hashes: u64, canonical_chain: BTreeMap, ) -> Self { Self { last_finalized_block, - num_of_additional_canonical_block_hashes, - fork_to_child: Default::default(), canonical_chain, + fork_to_child: Default::default(), blocks_to_chain: Default::default(), index_number_to_block: Default::default(), } } - /// Return number of additional canonical block hashes that we need - /// to have to be able to have enought information for EVM execution. - pub fn num_of_additional_canonical_block_hashes(&self) -> u64 { - self.num_of_additional_canonical_block_hashes - } - /// Return fork to child indices pub fn fork_to_child(&self) -> &HashMap> { &self.fork_to_child @@ -258,6 +246,7 @@ impl BlockIndices { pub 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. @@ -270,7 +259,7 @@ impl BlockIndices { // remove unneeded canonical hashes. let remove_until = - finalized_block.saturating_sub(self.num_of_additional_canonical_block_hashes); + 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(); diff --git a/crates/executor/src/blockchain_tree/config.rs b/crates/executor/src/blockchain_tree/config.rs new file mode 100644 index 0000000000..3de52c1104 --- /dev/null +++ b/crates/executor/src/blockchain_tree/config.rs @@ -0,0 +1,59 @@ +//! Blockchain tree configuration + +/// The configuration for the blockchain tree. +#[derive(Clone, Debug)] +pub struct BlockchainTreeConfig { + /// Finalization windows. Number of blocks that can be reorged + max_reorg_depth: u64, + /// Number of block after finalized block that we are storing. It should be more then + /// finalization window + max_blocks_in_chain: u64, + /// For EVM's "BLOCKHASH" opcode we require last 256 block hashes. So we need to specify + /// at least `additional_canonical_block_hashes`+`max_reorg_depth`, for eth that would be + /// 256+64. + 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, + } + } +} + +impl BlockchainTreeConfig { + /// Create tree configuration. + pub fn new( + max_reorg_depth: u64, + max_blocks_in_chain: u64, + num_of_additional_canonical_block_hashes: u64, + ) -> Self { + if max_reorg_depth > max_blocks_in_chain { + panic!("Side chain size should be more then finalization window"); + } + Self { max_blocks_in_chain, max_reorg_depth, num_of_additional_canonical_block_hashes } + } + + /// Return the maximum reorg depth. + pub fn max_reorg_depth(&self) -> u64 { + self.max_reorg_depth + } + + /// Return the maximum number of blocks in one chain. + pub 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 fn num_of_additional_canonical_block_hashes(&self) -> u64 { + self.num_of_additional_canonical_block_hashes + } +} diff --git a/crates/executor/src/blockchain_tree/externals.rs b/crates/executor/src/blockchain_tree/externals.rs new file mode 100644 index 0000000000..72e31f4fb3 --- /dev/null +++ b/crates/executor/src/blockchain_tree/externals.rs @@ -0,0 +1,32 @@ +//! Blockchain tree externals. + +use reth_db::database::Database; +use reth_primitives::ChainSpec; +use reth_provider::ShareableDatabase; +use std::sync::Arc; + +/// Container for external abstractions. +pub struct TreeExternals { + /// Save sidechain, do reorgs and push new block to canonical chain that is inside db. + pub db: DB, + /// Consensus checks + pub consensus: C, + /// Create executor to execute blocks. + pub executor_factory: EF, + /// Chain spec + pub chain_spec: Arc, +} + +impl TreeExternals { + /// Create new tree externals. + pub fn new(db: DB, consensus: C, executor_factory: EF, chain_spec: Arc) -> Self { + Self { db, consensus, executor_factory, chain_spec } + } +} + +impl TreeExternals { + /// Return shareable database helper structure. + pub fn shareable_db(&self) -> ShareableDatabase<&DB> { + ShareableDatabase::new(&self.db, self.chain_spec.clone()) + } +} diff --git a/crates/executor/src/blockchain_tree/mod.rs b/crates/executor/src/blockchain_tree/mod.rs index 30347fc2a4..722c1c4995 100644 --- a/crates/executor/src/blockchain_tree/mod.rs +++ b/crates/executor/src/blockchain_tree/mod.rs @@ -1,23 +1,24 @@ //! Implementation of [`BlockchainTree`] -pub mod block_indices; -pub mod chain; - -use self::{ - block_indices::BlockIndices, - chain::{ChainSplit, SplitAt}, -}; use chain::{BlockChainId, Chain, ForkBlock}; use reth_db::{cursor::DbCursorRO, database::Database, tables, transaction::DbTx}; use reth_interfaces::{consensus::Consensus, executor::Error as ExecError, Error}; -use reth_primitives::{BlockHash, BlockNumber, ChainSpec, SealedBlock, SealedBlockWithSenders}; +use reth_primitives::{BlockHash, BlockNumber, SealedBlock, SealedBlockWithSenders}; use reth_provider::{ - providers::ChainState, ExecutorFactory, HeaderProvider, ShareableDatabase, - StateProviderFactory, Transaction, -}; -use std::{ - collections::{BTreeMap, HashMap}, - sync::Arc, + providers::ChainState, ExecutorFactory, HeaderProvider, StateProviderFactory, Transaction, }; +use std::collections::{BTreeMap, HashMap}; + +pub mod block_indices; +use block_indices::BlockIndices; + +pub mod chain; +use chain::{ChainSplit, SplitAt}; + +pub mod config; +use config::BlockchainTreeConfig; + +pub mod externals; +use externals::TreeExternals; #[cfg_attr(doc, aquamarine::aquamarine)] /// Tree of chains and its identifications. @@ -52,12 +53,13 @@ use std::{ /// /// /// main functions: -/// * insert_block: Connect block to chain, execute it and if valid insert block inside tree. -/// * finalize_block: Remove chains that join to now finalized block, as chain becomes invalid. -/// * make_canonical: Check if we have the hash of block that we want to finalize and commit it to -/// db. If we dont have the block, pipeline syncing should start to fetch the blocks from p2p. Do -/// reorg in tables if canonical chain if needed. - +/// * [BlockchainTree::insert_block]: Connect block to chain, execute it and if valid insert block +/// inside tree. +/// * [BlockchainTree::finalize_block]: Remove chains that join to now finalized block, as chain +/// becomes invalid. +/// * [BlockchainTree::make_canonical]: Check if we have the hash of block that we want to finalize +/// and commit it to db. If we dont have the block, pipeline syncing should start to fetch the +/// blocks from p2p. Do reorg in tables if canonical chain if needed. pub struct BlockchainTree { /// chains and present data chains: HashMap, @@ -65,32 +67,10 @@ pub struct BlockchainTree { block_chain_id_generator: u64, /// Indices to block and their connection. block_indices: BlockIndices, - /// Number of block after finalized block that we are storing. It should be more then - /// finalization window - max_blocks_in_chain: u64, - /// Finalization windows. Number of blocks that can be reorged - max_reorg_depth: u64, + /// Tree configuration. + config: BlockchainTreeConfig, /// Externals - externals: Externals, -} - -/// Container for external abstractions. -struct Externals { - /// Save sidechain, do reorgs and push new block to canonical chain that is inside db. - db: DB, - /// Consensus checks - consensus: C, - /// Create executor to execute blocks. - executor_factory: EF, - /// Chain spec - chain_spec: Arc, -} - -impl Externals { - /// Return sharable database helper structure. - fn sharable_db(&self) -> ShareableDatabase<&DB> { - ShareableDatabase::new(&self.db, self.chain_spec.clone()) - } + externals: TreeExternals, } /// Helper structure that wraps chains and indices to search for block hash accross the chains. @@ -104,23 +84,17 @@ pub struct BlockHashes<'a> { impl BlockchainTree { /// New blockchain tree pub fn new( - db: DB, - consensus: C, - executor_factory: EF, - chain_spec: Arc, - max_reorg_depth: u64, - max_blocks_in_chain: u64, - num_of_additional_canonical_block_hashes: u64, + externals: TreeExternals, + config: BlockchainTreeConfig, ) -> Result { - if max_reorg_depth > max_blocks_in_chain { - panic!("Side chain size should be more then finalization window"); - } + let max_reorg_depth = config.max_reorg_depth(); - let last_canonical_hashes = db + let last_canonical_hashes = externals + .db .tx()? .cursor_read::()? .walk_back(None)? - .take((max_reorg_depth + num_of_additional_canonical_block_hashes) as usize) + .take((max_reorg_depth + config.num_of_additional_canonical_block_hashes()) as usize) .collect::, _>>()?; // TODO(rakita) save last finalized block inside database but for now just take @@ -134,19 +108,15 @@ impl BlockchainTree last_canonical_hashes.last().cloned().unwrap_or_default() }; - let externals = Externals { db, consensus, executor_factory, chain_spec }; - Ok(Self { externals, block_chain_id_generator: 0, chains: Default::default(), block_indices: BlockIndices::new( last_finalized_block_number, - num_of_additional_canonical_block_hashes, BTreeMap::from_iter(last_canonical_hashes.into_iter()), ), - max_blocks_in_chain, - max_reorg_depth, + config, }) } @@ -175,7 +145,7 @@ impl BlockchainTree let (_, canonical_tip_hash) = canonical_block_hashes.last_key_value().map(|(i, j)| (*i, *j)).unwrap_or_default(); - let db = self.externals.sharable_db(); + let db = self.externals.shareable_db(); let provider = if canonical_fork.hash == canonical_tip_hash { ChainState::boxed(db.latest()?) } else { @@ -220,7 +190,7 @@ impl BlockchainTree canonical_block_hashes.last_key_value().map(|(i, j)| (*i, *j)).unwrap_or_default(); // create state provider - let db = self.externals.sharable_db(); + let db = self.externals.shareable_db(); let parent_header = db .header(&block.parent_hash)? .ok_or(ExecError::CanonicalChain { block_hash: block.parent_hash })?; @@ -333,7 +303,7 @@ impl BlockchainTree } // we will not even try to insert blocks that are too far in future. - if block.number > last_finalized_block + self.max_blocks_in_chain { + if block.number > last_finalized_block + self.config.max_blocks_in_chain() { return Err(ExecError::PendingBlockIsInFuture { block_number: block.number, block_hash: block.hash(), @@ -378,7 +348,10 @@ impl BlockchainTree /// Do finalization of blocks. Remove them from tree pub fn finalize_block(&mut self, finalized_block: BlockNumber) { - let mut remove_chains = self.block_indices.finalize_canonical_blocks(finalized_block); + let mut remove_chains = self.block_indices.finalize_canonical_blocks( + finalized_block, + self.config.num_of_additional_canonical_block_hashes(), + ); while let Some(chain_id) = remove_chains.pop_first() { if let Some(chain) = self.chains.remove(&chain_id) { @@ -388,14 +361,14 @@ impl BlockchainTree } /// Update canonical hashes. Reads last N canonical blocks from database and update all indices. - pub fn update_canonical_hashes( + pub fn restore_canonical_hashes( &mut self, last_finalized_block: BlockNumber, ) -> Result<(), Error> { self.finalize_block(last_finalized_block); let num_of_canonical_hashes = - self.max_reorg_depth + self.block_indices.num_of_additional_canonical_block_hashes(); + self.config.max_reorg_depth() + self.config.num_of_additional_canonical_block_hashes(); let last_canonical_hashes = self .externals @@ -561,13 +534,16 @@ mod tests { transaction::DbTxMut, }; use reth_interfaces::test_utils::TestConsensus; - use reth_primitives::{hex_literal::hex, proofs::EMPTY_ROOT, ChainSpecBuilder, H256, MAINNET}; + use reth_primitives::{ + hex_literal::hex, proofs::EMPTY_ROOT, ChainSpec, ChainSpecBuilder, H256, MAINNET, + }; use reth_provider::{ execution_result::ExecutionResult, insert_block, test_utils::blocks::BlockChainTestData, BlockExecutor, StateProvider, }; - use std::collections::HashSet; + use std::{collections::HashSet, sync::Arc}; + #[derive(Clone)] struct TestFactory { exec_result: Arc>>, chain_spec: Arc, @@ -618,11 +594,11 @@ mod tests { } } - type TestExternals = (Arc>, TestConsensus, TestFactory, Arc); - - fn externals(exec_res: Vec) -> TestExternals { + fn setup_externals( + exec_res: Vec, + ) -> TreeExternals>, Arc, TestFactory> { let db = create_test_rw_db(); - let consensus = TestConsensus::default(); + let consensus = Arc::new(TestConsensus::default()); let chain_spec = Arc::new( ChainSpecBuilder::default() .chain(MAINNET.chain) @@ -633,15 +609,15 @@ mod tests { let executor_factory = TestFactory::new(chain_spec.clone()); executor_factory.extend(exec_res); - (db, consensus, executor_factory, chain_spec) + TreeExternals::new(db, consensus, executor_factory, chain_spec) } - fn setup(mut genesis: SealedBlock, externals: &TestExternals) { + fn setup_genesis(db: DB, mut genesis: SealedBlock) { // insert genesis to db. genesis.header.header.number = 10; genesis.header.header.state_root = EMPTY_ROOT; - let tx_mut = externals.0.tx_mut().unwrap(); + let tx_mut = db.tx_mut().unwrap(); insert_block(&tx_mut, genesis, None, false, Some((0, 0))).unwrap(); @@ -709,15 +685,14 @@ mod tests { H256(hex!("90101a13dd059fa5cca99ed93d1dc23657f63626c5b8f993a2ccbdf7446b64f8")); // test pops execution results from vector, so order is from last to first.ß - let externals = externals(vec![exec2.clone(), exec1.clone(), exec2, exec1]); + let externals = setup_externals(vec![exec2.clone(), exec1.clone(), exec2, exec1]); // last finalized block would be number 9. - setup(data.genesis, &externals); + setup_genesis(externals.db.clone(), data.genesis); // make tree - let (db, consensus, exec_factory, chain_spec) = externals; - let mut tree = - BlockchainTree::new(db, consensus, exec_factory, chain_spec, 1, 2, 3).unwrap(); + let config = BlockchainTreeConfig::new(1, 2, 3); + let mut tree = BlockchainTree::new(externals, config).expect("failed to create tree"); // genesis block 10 is already canonical assert_eq!(tree.make_canonical(&H256::zero()), Ok(())); @@ -900,7 +875,7 @@ mod tests { .assert(&tree); // update canonical block to b2, this would make b2a be removed - assert_eq!(tree.update_canonical_hashes(12), Ok(())); + assert_eq!(tree.restore_canonical_hashes(12), Ok(())); // Trie state: // b2 (canon) // |