mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-01-30 01:28:21 -05:00
chore(sync): blockchain tree config and externals (#1760)
This commit is contained in:
@@ -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();
|
||||
|
||||
59
crates/executor/src/blockchain_tree/config.rs
Normal file
59
crates/executor/src/blockchain_tree/config.rs
Normal 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
|
||||
}
|
||||
}
|
||||
32
crates/executor/src/blockchain_tree/externals.rs
Normal file
32
crates/executor/src/blockchain_tree/externals.rs
Normal 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())
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
// |
|
||||
|
||||
Reference in New Issue
Block a user