chore(sync): blockchain tree config and externals (#1760)

This commit is contained in:
Roman Krasiuk
2023-03-15 19:44:26 +02:00
committed by GitHub
parent ed9c6a61ee
commit 7d30bd3941
4 changed files with 152 additions and 97 deletions

View File

@@ -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<BlockNumber, BlockHash>,
) -> 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<BlockHash, HashSet<BlockHash>> {
&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<BlockChainId> {
// 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();

View File

@@ -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
}
}

View File

@@ -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<DB, C, EF> {
/// 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<ChainSpec>,
}
impl<DB, C, EF> TreeExternals<DB, C, EF> {
/// Create new tree externals.
pub fn new(db: DB, consensus: C, executor_factory: EF, chain_spec: Arc<ChainSpec>) -> Self {
Self { db, consensus, executor_factory, chain_spec }
}
}
impl<DB: Database, C, EF> TreeExternals<DB, C, EF> {
/// Return shareable database helper structure.
pub fn shareable_db(&self) -> ShareableDatabase<&DB> {
ShareableDatabase::new(&self.db, self.chain_spec.clone())
}
}

View File

@@ -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<DB: Database, C: Consensus, EF: ExecutorFactory> {
/// chains and present data
chains: HashMap<BlockChainId, Chain>,
@@ -65,32 +67,10 @@ pub struct BlockchainTree<DB: Database, C: Consensus, EF: ExecutorFactory> {
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<DB, C, EF>,
}
/// Container for external abstractions.
struct Externals<DB: Database, C: Consensus, EF: ExecutorFactory> {
/// 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<ChainSpec>,
}
impl<DB: Database, C: Consensus, EF: ExecutorFactory> Externals<DB, C, EF> {
/// Return sharable database helper structure.
fn sharable_db(&self) -> ShareableDatabase<&DB> {
ShareableDatabase::new(&self.db, self.chain_spec.clone())
}
externals: TreeExternals<DB, C, EF>,
}
/// Helper structure that wraps chains and indices to search for block hash accross the chains.
@@ -104,23 +84,17 @@ pub struct BlockHashes<'a> {
impl<DB: Database, C: Consensus, EF: ExecutorFactory> BlockchainTree<DB, C, EF> {
/// New blockchain tree
pub fn new(
db: DB,
consensus: C,
executor_factory: EF,
chain_spec: Arc<ChainSpec>,
max_reorg_depth: u64,
max_blocks_in_chain: u64,
num_of_additional_canonical_block_hashes: u64,
externals: TreeExternals<DB, C, EF>,
config: BlockchainTreeConfig,
) -> Result<Self, Error> {
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::<tables::CanonicalHeaders>()?
.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::<Result<Vec<(BlockNumber, BlockHash)>, _>>()?;
// TODO(rakita) save last finalized block inside database but for now just take
@@ -134,19 +108,15 @@ impl<DB: Database, C: Consensus, EF: ExecutorFactory> BlockchainTree<DB, C, EF>
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<DB: Database, C: Consensus, EF: ExecutorFactory> BlockchainTree<DB, C, EF>
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<DB: Database, C: Consensus, EF: ExecutorFactory> BlockchainTree<DB, C, EF>
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<DB: Database, C: Consensus, EF: ExecutorFactory> BlockchainTree<DB, C, EF>
}
// 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<DB: Database, C: Consensus, EF: ExecutorFactory> BlockchainTree<DB, C, EF>
/// 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<DB: Database, C: Consensus, EF: ExecutorFactory> BlockchainTree<DB, C, EF>
}
/// 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<Mutex<Vec<ExecutionResult>>>,
chain_spec: Arc<ChainSpec>,
@@ -618,11 +594,11 @@ mod tests {
}
}
type TestExternals = (Arc<Env<WriteMap>>, TestConsensus, TestFactory, Arc<ChainSpec>);
fn externals(exec_res: Vec<ExecutionResult>) -> TestExternals {
fn setup_externals(
exec_res: Vec<ExecutionResult>,
) -> TreeExternals<Arc<Env<WriteMap>>, Arc<TestConsensus>, 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: Database>(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)
// |