feat(BlockchainTree): ShareableBlockchainTree and pending state (#2007)

This commit is contained in:
rakita
2023-03-29 20:59:24 +02:00
committed by GitHub
parent 2eb4306efe
commit efbaf6474c
33 changed files with 878 additions and 646 deletions

View File

@@ -1,8 +1,8 @@
//! Implementation of [`BlockIndices`] related to [`super::BlockchainTree`]
use super::chain::{BlockChainId, Chain, ForkBlock};
use reth_primitives::{BlockHash, BlockNumber, SealedBlockWithSenders};
use std::collections::{hash_map::Entry, BTreeMap, BTreeSet, HashMap, HashSet};
use super::chain::{BlockChainId, Chain};
use reth_primitives::{BlockHash, BlockNumHash, BlockNumber, SealedBlockWithSenders};
use std::collections::{btree_map, hash_map, BTreeMap, BTreeSet, HashMap, HashSet};
/// Internal indices of the blocks and chains.
///
@@ -25,7 +25,7 @@ pub struct BlockIndices {
blocks_to_chain: HashMap<BlockHash, BlockChainId>,
/// Utility index. Block number to block hash. Can be used for
/// RPC to fetch all pending block in chain by its number.
index_number_to_block: HashMap<BlockNumber, HashSet<BlockHash>>,
index_number_to_block: BTreeMap<BlockNumber, HashSet<BlockHash>>,
}
impl BlockIndices {
@@ -43,6 +43,11 @@ impl BlockIndices {
}
}
/// Return internal index that maps all pending block number to their hash.
pub fn index_of_number_to_pending_blocks(&self) -> &BTreeMap<BlockNumber, HashSet<BlockHash>> {
&self.index_number_to_block
}
/// Return fork to child indices
pub fn fork_to_child(&self) -> &HashMap<BlockHash, HashSet<BlockHash>> {
&self.fork_to_child
@@ -169,7 +174,9 @@ impl BlockIndices {
block_hash: BlockHash,
) -> BTreeSet<BlockChainId> {
// rm number -> block
if let Entry::Occupied(mut entry) = self.index_number_to_block.entry(block_number) {
if let btree_map::Entry::Occupied(mut entry) =
self.index_number_to_block.entry(block_number)
{
let set = entry.get_mut();
set.remove(&block_hash);
// remove set if empty
@@ -214,7 +221,9 @@ impl BlockIndices {
self.blocks_to_chain.remove(&hash);
// rm number -> block
if let Entry::Occupied(mut entry) = self.index_number_to_block.entry(number) {
if let btree_map::Entry::Occupied(mut entry) =
self.index_number_to_block.entry(number)
{
let set = entry.get_mut();
set.remove(&hash);
// remove set if empty
@@ -223,7 +232,8 @@ impl BlockIndices {
}
}
// rm fork block -> hash
if let Entry::Occupied(mut entry) = self.fork_to_child.entry(parent_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
@@ -295,13 +305,14 @@ impl BlockIndices {
}
/// get canonical tip
pub fn canonical_tip(&self) -> ForkBlock {
let (&number, &hash) =
self.canonical_chain.last_key_value().expect("There is always the canonical chain");
ForkBlock { number, hash }
pub fn canonical_tip(&self) -> BlockNumHash {
self.canonical_chain
.last_key_value()
.map(|(&number, &hash)| BlockNumHash { number, hash })
.unwrap_or_default()
}
/// Canonical chain needs for execution of EVM. It should contains last 256 block hashes.
/// Canonical chain needed for execution of EVM. It should contains last 256 block hashes.
pub fn canonical_chain(&self) -> &BTreeMap<BlockNumber, BlockHash> {
&self.canonical_chain
}

View File

@@ -2,12 +2,20 @@
//!
//! 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 crate::{post_state::PostState, substate::PostStateProvider};
use crate::{blockchain_tree::PostStateDataRef, post_state::PostState};
use reth_db::database::Database;
use reth_interfaces::{consensus::Consensus, executor::Error as ExecError, Error};
use reth_primitives::{BlockHash, BlockNumber, SealedBlockWithSenders, SealedHeader, U256};
use reth_provider::{BlockExecutor, ExecutorFactory, StateProvider};
use reth_primitives::{
BlockHash, BlockNumHash, BlockNumber, SealedBlockWithSenders, SealedHeader, U256,
};
use reth_provider::{
providers::PostStateProvider, BlockExecutor, ExecutorFactory, PostStateDataProvider,
StateProviderFactory,
};
use std::collections::BTreeMap;
use super::externals::TreeExternals;
/// The ID of a sidechain internally in a [`BlockchainTree`][super::BlockchainTree].
pub(crate) type BlockChainId = u64;
@@ -33,21 +41,8 @@ pub struct Chain {
block_transitions: BTreeMap<BlockNumber, usize>,
}
/// Describes a fork block by its number and hash.
#[derive(Clone, Copy, Eq, PartialEq)]
pub struct ForkBlock {
/// Block number of block that chains branches from
pub number: u64,
/// Block hash of block that chains branches from
pub hash: BlockHash,
}
impl ForkBlock {
/// Return the `(block_number, block_hash)` tuple for this fork block.
pub fn num_hash(&self) -> (BlockNumber, BlockHash) {
(self.number, self.hash)
}
}
/// Block number and hash of the forked block.
pub type ForkBlock = BlockNumHash;
impl Chain {
/// Get the blocks in this chain.
@@ -55,6 +50,31 @@ impl Chain {
&self.blocks
}
/// Get post state of this chain
pub fn state(&self) -> &PostState {
&self.state
}
/// Return block number of the block hash.
pub fn block_number(&self, block_hash: BlockHash) -> Option<BlockNumber> {
self.blocks.iter().find_map(|(num, block)| (block.hash() == block_hash).then_some(*num))
}
/// Return post state of the block at the `block_number` or None if block is not known
pub fn state_at_block(&self, block_number: BlockNumber) -> Option<PostState> {
let mut state = self.state.clone();
if self.tip().number == block_number {
return Some(state)
}
if let Some(&transition_id) = self.block_transitions.get(&block_number) {
state.revert_to(transition_id);
return Some(state)
}
None
}
/// Destructure the chain into its inner components, the blocks and the state.
pub fn into_inner(self) -> (BTreeMap<BlockNumber, SealedBlockWithSenders>, PostState) {
(self.blocks, self.state)
@@ -105,41 +125,53 @@ impl Chain {
}
/// Create a new chain that forks off of the canonical chain.
pub fn new_canonical_fork<SP: StateProvider, C: Consensus, EF: ExecutorFactory>(
pub fn new_canonical_fork<DB, C, EF>(
block: &SealedBlockWithSenders,
parent_header: &SealedHeader,
canonical_block_hashes: &BTreeMap<BlockNumber, BlockHash>,
provider: &SP,
consensus: &C,
factory: &EF,
) -> Result<Self, Error> {
canonical_fork: ForkBlock,
externals: &TreeExternals<DB, C, EF>,
) -> Result<Self, Error>
where
DB: Database,
C: Consensus,
EF: ExecutorFactory,
{
let state = PostState::default();
let empty = BTreeMap::new();
let state_provider =
PostStateProvider::new(&state, provider, &empty, canonical_block_hashes);
let state_provider = PostStateDataRef {
state: &state,
sidechain_block_hashes: &empty,
canonical_block_hashes,
canonical_fork,
};
let changeset = Self::validate_and_execute(
block.clone(),
parent_header,
canonical_fork,
state_provider,
consensus,
factory,
externals,
)?;
Ok(Self::new(vec![(block.clone(), changeset)]))
}
/// Create a new chain that forks off of an existing sidechain.
pub fn new_chain_fork<SP: StateProvider, C: Consensus, EF: ExecutorFactory>(
pub fn new_chain_fork<DB, C, EF>(
&self,
block: SealedBlockWithSenders,
side_chain_block_hashes: BTreeMap<BlockNumber, BlockHash>,
canonical_block_hashes: &BTreeMap<BlockNumber, BlockHash>,
provider: &SP,
consensus: &C,
factory: &EF,
) -> Result<Self, Error> {
canonical_fork: ForkBlock,
externals: &TreeExternals<DB, C, EF>,
) -> Result<Self, Error>
where
DB: Database,
C: Consensus,
EF: ExecutorFactory,
{
let parent_number = block.number - 1;
let parent = self
.blocks
@@ -156,14 +188,19 @@ impl Chain {
state.revert_to(*revert_to_transition_id);
// Revert changesets to get the state of the parent that we need to apply the change.
let state_provider = PostStateProvider::new(
&state,
provider,
&side_chain_block_hashes,
let post_state_data = PostStateDataRef {
state: &state,
sidechain_block_hashes: &side_chain_block_hashes,
canonical_block_hashes,
);
let block_state =
Self::validate_and_execute(block.clone(), parent, state_provider, consensus, factory)?;
canonical_fork,
};
let block_state = Self::validate_and_execute(
block.clone(),
parent,
canonical_fork,
post_state_data,
externals,
)?;
state.extend(block_state);
let chain = Self {
@@ -177,49 +214,67 @@ impl Chain {
}
/// Validate and execute the given block.
fn validate_and_execute<SP: StateProvider, C: Consensus, EF: ExecutorFactory>(
fn validate_and_execute<PSDP, DB, C, EF>(
block: SealedBlockWithSenders,
parent_block: &SealedHeader,
state_provider: PostStateProvider<'_, SP>,
consensus: &C,
factory: &EF,
) -> Result<PostState, Error> {
consensus.validate_header(&block, U256::MAX)?;
consensus.pre_validate_header(&block, parent_block)?;
consensus.pre_validate_block(&block)?;
canonical_fork: ForkBlock,
post_state_data_provider: PSDP,
externals: &TreeExternals<DB, C, EF>,
) -> Result<PostState, Error>
where
PSDP: PostStateDataProvider,
DB: Database,
C: Consensus,
EF: ExecutorFactory,
{
externals.consensus.validate_header(&block, U256::MAX)?;
externals.consensus.pre_validate_header(&block, parent_block)?;
externals.consensus.pre_validate_block(&block)?;
let (unseal, senders) = block.into_components();
let unseal = unseal.unseal();
factory
.with_sp(state_provider)
.execute_and_verify_receipt(&unseal, U256::MAX, Some(senders))
.map_err(Into::into)
//get state provider.
let db = externals.shareable_db();
// TODO, small perf can check if caonical fork is the latest state.
let history_provider = db.history_by_block_number(canonical_fork.number)?;
let state_provider = history_provider;
let provider = PostStateProvider { state_provider, post_state_data_provider };
let mut executor = externals.executor_factory.with_sp(&provider);
executor.execute_and_verify_receipt(&unseal, U256::MAX, Some(senders)).map_err(Into::into)
}
/// Validate and execute the given block, and append it to this chain.
pub fn append_block<SP: StateProvider, C: Consensus, EF: ExecutorFactory>(
pub fn append_block<DB, C, EF>(
&mut self,
block: SealedBlockWithSenders,
side_chain_block_hashes: BTreeMap<BlockNumber, BlockHash>,
canonical_block_hashes: &BTreeMap<BlockNumber, BlockHash>,
provider: &SP,
consensus: &C,
factory: &EF,
) -> Result<(), Error> {
canonical_fork: ForkBlock,
externals: &TreeExternals<DB, C, EF>,
) -> Result<(), Error>
where
DB: Database,
C: Consensus,
EF: ExecutorFactory,
{
let (_, parent_block) = self.blocks.last_key_value().expect("Chain has at least one block");
let post_state_data = PostStateDataRef {
state: &self.state,
sidechain_block_hashes: &side_chain_block_hashes,
canonical_block_hashes,
canonical_fork,
};
let block_state = Self::validate_and_execute(
block.clone(),
parent_block,
PostStateProvider::new(
&self.state,
provider,
&side_chain_block_hashes,
canonical_block_hashes,
),
consensus,
factory,
canonical_fork,
post_state_data,
externals,
)?;
self.state.extend(block_state);
self.block_transitions.insert(block.number, self.state.transitions_count());
@@ -235,7 +290,7 @@ impl Chain {
if chain_tip.hash != chain.fork_block_hash() {
return Err(ExecError::AppendChainDoesntConnect {
chain_tip: chain_tip.num_hash(),
other_chain_fork: chain.fork_block().num_hash(),
other_chain_fork: chain.fork_block().into_components(),
}
.into())
}
@@ -273,11 +328,7 @@ impl Chain {
let chain_tip = *self.blocks.last_entry().expect("chain is never empty").key();
let block_number = match split_at {
SplitAt::Hash(block_hash) => {
let block_number = self
.blocks
.iter()
.find_map(|(num, block)| (block.hash() == block_hash).then_some(*num));
let Some(block_number) = block_number else { return ChainSplit::NoSplitPending(self)};
let Some(block_number) = self.block_number(block_hash) else { return ChainSplit::NoSplitPending(self)};
// If block number is same as tip whole chain is becoming canonical.
if block_number == chain_tip {
return ChainSplit::NoSplitCanonical(self)
@@ -299,7 +350,7 @@ impl Chain {
let mut canonical_state = std::mem::take(&mut self.state);
let new_state = canonical_state.split_at(
*self.block_transitions.get(&(block_number)).expect("Unknown block transition ID"),
*self.block_transitions.get(&block_number).expect("Unknown block transition ID"),
);
self.state = new_state;
@@ -433,6 +484,12 @@ mod tests {
blocks: BTreeMap::from([(2, block2.clone())]),
};
// return tip state
assert_eq!(chain.state_at_block(block2.number), Some(chain.state.clone()));
assert_eq!(chain.state_at_block(block1.number), Some(chain_split1.state.clone()));
// state at unknown block
assert_eq!(chain.state_at_block(100), None);
// split in two
assert_eq!(
chain.clone().split(SplitAt::Hash(block1_hash)),

View File

@@ -17,7 +17,7 @@ use std::sync::Arc;
#[derive(Debug)]
pub struct TreeExternals<DB, C, EF> {
/// The database, used to commit the canonical chain, or unwind it.
pub db: Arc<DB>,
pub db: DB,
/// The consensus engine.
pub consensus: C,
/// The executor factory to execute blocks with.
@@ -28,12 +28,7 @@ pub struct TreeExternals<DB, C, EF> {
impl<DB, C, EF> TreeExternals<DB, C, EF> {
/// Create new tree externals.
pub fn new(
db: Arc<DB>,
consensus: C,
executor_factory: EF,
chain_spec: Arc<ChainSpec>,
) -> Self {
pub fn new(db: DB, consensus: C, executor_factory: EF, chain_spec: Arc<ChainSpec>) -> Self {
Self { db, consensus, executor_factory, chain_spec }
}
}

View File

@@ -1,13 +1,13 @@
//! Implementation of [`BlockchainTree`]
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_interfaces::{
blockchain_tree::BlockStatus, consensus::Consensus, executor::Error as ExecError, Error,
};
use reth_primitives::{
BlockHash, BlockNumber, Hardfork, SealedBlock, SealedBlockWithSenders, U256,
};
use reth_provider::{
providers::ChainState, ExecutorFactory, HeaderProvider, StateProviderFactory, Transaction,
BlockHash, BlockNumHash, BlockNumber, Hardfork, SealedBlock, SealedBlockWithSenders, U256,
};
use reth_provider::{post_state::PostState, ExecutorFactory, HeaderProvider, Transaction};
use std::collections::{BTreeMap, HashMap};
pub mod block_indices;
@@ -22,6 +22,12 @@ use config::BlockchainTreeConfig;
pub mod externals;
use externals::TreeExternals;
pub mod shareable;
pub use shareable::ShareableBlockchainTree;
pub mod post_state_data;
pub use post_state_data::{PostStateData, PostStateDataRef};
#[cfg_attr(doc, aquamarine::aquamarine)]
/// Tree of chains and its identifications.
///
@@ -76,23 +82,6 @@ pub struct BlockchainTree<DB: Database, C: Consensus, EF: ExecutorFactory> {
config: BlockchainTreeConfig,
}
/// From Engine API spec, block inclusion can be valid, accepted or invalid.
/// Invalid case is already covered by error but we needs to make distinction
/// between if it is valid (extends canonical chain) or just accepted (is side chain).
/// If we dont know the block parent we are returning Disconnected status
/// as we can't make a claim if block is valid or not.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum BlockStatus {
/// If block validation is valid and block extends canonical chain.
/// In BlockchainTree sense it forks on canonical tip.
Valid,
/// If the block is valid, but it does not extend canonical chain
/// (It is side chain) or hasn't been fully validated but ancestors of a payload are known.
Accepted,
/// If blocks is not connected to canonical chain.
Disconnected,
}
/// A container that wraps chains and block indices to allow searching for block hashes across all
/// sidechains.
pub struct BlockHashes<'a> {
@@ -141,120 +130,161 @@ impl<DB: Database, C: Consensus, EF: ExecutorFactory> BlockchainTree<DB, C, EF>
})
}
/// Return the tip of the canonical chain
pub fn canonical_tip_number(&self) -> Option<BlockNumber> {
self.block_indices.canonical_chain().last_key_value().map(|(number, _)| *number)
/// Expose internal indices of the BlockchainTree.
pub fn block_indices(&self) -> &BlockIndices {
&self.block_indices
}
/// Create a new sidechain by forking the given chain, or append the block if the parent block
/// is the top of the given chain.
fn fork_side_chain(
/// 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.
/// * `PostState` changes that happened at the asked `block_hash`
/// * `BTreeMap<BlockNumber,BlockHash>` list of past pending and canonical hashes, That are
/// needed for evm `BLOCKHASH` opcode.
/// Return none if block is not known.
pub fn post_state_data(&self, block_hash: BlockHash) -> Option<PostStateData> {
// if it is part of the chain
if let Some(chain_id) = self.block_indices.get_blocks_chain_id(&block_hash) {
// get block state
let chain = self.chains.get(&chain_id).expect("Chain should be present");
let block_number = chain.block_number(block_hash)?;
let state = chain.state_at_block(block_number)?;
// get parent hashes
let mut parent_block_hashed = self.all_chain_hashes(chain_id);
let first_pending_block_number =
*parent_block_hashed.first_key_value().expect("There is at least one block hash").0;
let canonical_chain = self
.block_indices
.canonical_chain()
.clone()
.into_iter()
.filter(|&(key, _)| key < first_pending_block_number)
.collect::<Vec<_>>();
parent_block_hashed.extend(canonical_chain.into_iter());
// get canonical fork.
let canonical_fork = self.canonical_fork(chain_id)?;
return Some(PostStateData { state, parent_block_hashed, canonical_fork })
}
// check if there is canonical block
if let Some(canonical_fork) =
self.block_indices().canonical_chain().iter().find(|(_, value)| **value == block_hash)
{
return Some(PostStateData {
canonical_fork: ForkBlock { number: *canonical_fork.0, hash: *canonical_fork.1 },
state: PostState::new(),
parent_block_hashed: self.block_indices().canonical_chain().clone(),
})
}
None
}
/// Try inserting block inside the tree.
/// If blocks does not have parent [`BlockStatus::Disconnected`] would be returned
pub fn try_insert_block(
&mut self,
block: SealedBlockWithSenders,
chain_id: BlockChainId,
) -> Result<BlockStatus, Error> {
let block_hashes = self.all_chain_hashes(chain_id);
// check if block parent can be found in Tree
// get canonical fork.
let canonical_fork =
self.canonical_fork(chain_id).ok_or(ExecError::BlockChainIdConsistency { chain_id })?;
// Create a new sidechain by forking the given chain, or append the block if the parent
// block is the top of the given chain.
if let Some(chain_id) = self.block_indices.get_blocks_chain_id(&block.parent_hash) {
let block_hashes = self.all_chain_hashes(chain_id);
// get chain that block needs to join to.
let parent_chain = self
.chains
.get_mut(&chain_id)
.ok_or(ExecError::BlockChainIdConsistency { chain_id })?;
let chain_tip = parent_chain.tip().hash();
// get canonical fork.
let canonical_fork = self
.canonical_fork(chain_id)
.ok_or(ExecError::BlockChainIdConsistency { chain_id })?;
let canonical_block_hashes = self.block_indices.canonical_chain();
// get chain that block needs to join to.
let parent_chain = self
.chains
.get_mut(&chain_id)
.ok_or(ExecError::BlockChainIdConsistency { chain_id })?;
let chain_tip = parent_chain.tip().hash();
// get canonical tip
let canonical_tip =
canonical_block_hashes.last_key_value().map(|(_, hash)| *hash).unwrap_or_default();
let canonical_block_hashes = self.block_indices.canonical_chain();
let db = self.externals.shareable_db();
let provider = if canonical_fork.hash == canonical_tip {
ChainState::boxed(db.latest()?)
} else {
ChainState::boxed(db.history_by_block_number(canonical_fork.number)?)
};
// append the block if it is continuing the chain.
if chain_tip == block.parent_hash {
let block_hash = block.hash();
let block_number = block.number;
parent_chain.append_block(
block,
block_hashes,
canonical_block_hashes,
canonical_fork,
&self.externals,
)?;
// append the block if it is continuing the chain.
if chain_tip == block.parent_hash {
let block_hash = block.hash();
let block_number = block.number;
parent_chain.append_block(
block,
block_hashes,
self.block_indices.insert_non_fork_block(block_number, block_hash, chain_id);
return Ok(BlockStatus::Valid)
} else {
let chain = parent_chain.new_chain_fork(
block,
block_hashes,
canonical_block_hashes,
canonical_fork,
&self.externals,
)?;
self.insert_chain(chain);
return Ok(BlockStatus::Accepted)
}
}
// if not found, check if the parent can be found inside canonical chain.
if Some(block.parent_hash) == self.block_indices.canonical_hash(&(block.number - 1)) {
// create new chain that points to that block
//return self.fork_canonical_chain(block.clone());
// TODO save pending block to database
// https://github.com/paradigmxyz/reth/issues/1713
let db = self.externals.shareable_db();
let fork_block = ForkBlock { number: block.number - 1, hash: block.parent_hash };
// Validate that the block is post merge
let parent_td = db
.header_td(&block.parent_hash)?
.ok_or(ExecError::CanonicalChain { block_hash: block.parent_hash })?;
// Pass the parent total difficulty to short-circuit unnecessary calculations.
if !self.externals.chain_spec.fork(Hardfork::Paris).active_at_ttd(parent_td, U256::ZERO)
{
return Err(ExecError::BlockPreMerge { hash: block.hash }.into())
}
// Create state provider
let canonical_block_hashes = self.block_indices.canonical_chain();
let canonical_tip =
canonical_block_hashes.last_key_value().map(|(_, hash)| *hash).unwrap_or_default();
let block_status = if block.parent_hash == canonical_tip {
BlockStatus::Valid
} else {
BlockStatus::Accepted
};
let parent_header = db
.header(&block.parent_hash)?
.ok_or(ExecError::CanonicalChain { block_hash: block.parent_hash })?
.seal(block.parent_hash);
let chain = Chain::new_canonical_fork(
&block,
&parent_header,
canonical_block_hashes,
&provider,
&self.externals.consensus,
&self.externals.executor_factory,
fork_block,
&self.externals,
)?;
drop(provider);
self.block_indices.insert_non_fork_block(block_number, block_hash, chain_id);
Ok(BlockStatus::Valid)
} else {
let chain = parent_chain.new_chain_fork(
block,
block_hashes,
canonical_block_hashes,
&provider,
&self.externals.consensus,
&self.externals.executor_factory,
)?;
// release the lifetime with a drop
drop(provider);
self.insert_chain(chain);
Ok(BlockStatus::Accepted)
}
}
/// Create a new sidechain by forking the canonical chain.
// TODO(onbjerg): Is this not a specialized case of [`fork_side_chain`]? If so, can we merge?
pub fn fork_canonical_chain(
&mut self,
block: SealedBlockWithSenders,
) -> Result<BlockStatus, Error> {
let db = self.externals.shareable_db();
// Validate that the block is post merge
let parent_td = db
.header_td(&block.parent_hash)?
.ok_or(ExecError::CanonicalChain { block_hash: block.parent_hash })?;
// Pass the parent total difficulty to short-circuit unnecessary calculations.
if !self.externals.chain_spec.fork(Hardfork::Paris).active_at_ttd(parent_td, U256::ZERO) {
return Err(ExecError::BlockPreMerge { hash: block.hash }.into())
return Ok(block_status)
}
// Create state provider
let canonical_block_hashes = self.block_indices.canonical_chain();
let canonical_tip =
canonical_block_hashes.last_key_value().map(|(_, hash)| *hash).unwrap_or_default();
let (block_status, provider) = if block.parent_hash == canonical_tip {
(BlockStatus::Valid, ChainState::boxed(db.latest()?))
} else {
(
BlockStatus::Accepted,
ChainState::boxed(db.history_by_block_number(block.number - 1)?),
)
};
let parent_header = db
.header(&block.parent_hash)?
.ok_or(ExecError::CanonicalChain { block_hash: block.parent_hash })?
.seal(block.parent_hash);
let chain = Chain::new_canonical_fork(
&block,
&parent_header,
canonical_block_hashes,
&provider,
&self.externals.consensus,
&self.externals.executor_factory,
)?;
drop(provider);
self.insert_chain(chain);
Ok(block_status)
// NOTE: Block doesn't have a parent, and if we receive this block in `make_canonical`
// function this could be a trigger to initiate p2p syncing, as we are missing the
// parent.
Ok(BlockStatus::Disconnected)
}
/// Get all block hashes from a sidechain that are not part of the canonical chain.
@@ -325,7 +355,7 @@ impl<DB: Database, C: Consensus, EF: ExecutorFactory> BlockchainTree<DB, C, EF>
/// This recovers transaction signers (unlike [`BlockchainTree::insert_block_with_senders`]).
pub fn insert_block(&mut self, block: SealedBlock) -> Result<BlockStatus, Error> {
let block = block.seal_with_senders().ok_or(ExecError::SenderRecoveryError)?;
self.insert_block_with_senders(&block)
self.insert_block_with_senders(block)
}
/// Insert a block (with senders recovered) in the tree.
@@ -349,7 +379,7 @@ impl<DB: Database, C: Consensus, EF: ExecutorFactory> BlockchainTree<DB, C, EF>
/// instead.
pub fn insert_block_with_senders(
&mut self,
block: &SealedBlockWithSenders,
block: SealedBlockWithSenders,
) -> Result<BlockStatus, Error> {
// check if block number is inside pending block slide
let last_finalized_block = self.block_indices.last_finalized_block();
@@ -389,24 +419,7 @@ impl<DB: Database, C: Consensus, EF: ExecutorFactory> BlockchainTree<DB, C, EF>
return Ok(BlockStatus::Valid)
}
// check if block parent can be found in Tree
if let Some(parent_chain) = self.block_indices.get_blocks_chain_id(&block.parent_hash) {
return self.fork_side_chain(block.clone(), parent_chain)
// TODO save pending block to database
// https://github.com/paradigmxyz/reth/issues/1713
}
// if not found, check if the parent can be found inside canonical chain.
if Some(block.parent_hash) == self.block_indices.canonical_hash(&(block.number - 1)) {
// create new chain that points to that block
return self.fork_canonical_chain(block.clone())
// TODO save pending block to database
// https://github.com/paradigmxyz/reth/issues/1713
}
// NOTE: Block doesn't have a parent, and if we receive this block in `make_canonical`
// function this could be a trigger to initiate p2p syncing, as we are missing the
// parent.
Ok(BlockStatus::Disconnected)
self.try_insert_block(block)
}
/// Finalize blocks up until and including `finalized_block`, and remove them from the tree.
@@ -613,6 +626,11 @@ impl<DB: Database, C: Consensus, EF: ExecutorFactory> BlockchainTree<DB, C, EF>
Ok(Chain::new(blocks_and_execution))
}
/// Return best known canonical tip
pub fn canonical_tip(&self) -> BlockNumHash {
self.block_indices.canonical_tip()
}
}
#[cfg(test)]
@@ -632,7 +650,7 @@ mod tests {
fn setup_externals(
exec_res: Vec<PostState>,
) -> TreeExternals<Env<WriteMap>, Arc<TestConsensus>, TestExecutorFactory> {
) -> TreeExternals<Arc<Env<WriteMap>>, Arc<TestConsensus>, TestExecutorFactory> {
let db = create_test_rw_db();
let consensus = Arc::new(TestConsensus::default());
let chain_spec = Arc::new(
@@ -731,7 +749,7 @@ mod tests {
// insert block2 hits max chain size
assert_eq!(
tree.insert_block_with_senders(&block2),
tree.insert_block_with_senders(block2.clone()),
Err(ExecError::PendingBlockIsInFuture {
block_number: block2.number,
block_hash: block2.hash(),
@@ -744,15 +762,15 @@ mod tests {
tree.finalize_block(10);
// block 2 parent is not known.
assert_eq!(tree.insert_block_with_senders(&block2), Ok(BlockStatus::Disconnected));
assert_eq!(tree.insert_block_with_senders(block2.clone()), Ok(BlockStatus::Disconnected));
// insert block1
assert_eq!(tree.insert_block_with_senders(&block1), Ok(BlockStatus::Valid));
assert_eq!(tree.insert_block_with_senders(block1.clone()), Ok(BlockStatus::Valid));
// already inserted block will return true.
assert_eq!(tree.insert_block_with_senders(&block1), Ok(BlockStatus::Valid));
assert_eq!(tree.insert_block_with_senders(block1.clone()), Ok(BlockStatus::Valid));
// insert block2
assert_eq!(tree.insert_block_with_senders(&block2), Ok(BlockStatus::Valid));
assert_eq!(tree.insert_block_with_senders(block2.clone()), Ok(BlockStatus::Valid));
// Trie state:
// b2 (pending block)
@@ -797,7 +815,7 @@ mod tests {
block2a.hash = block2a_hash;
// reinsert two blocks that point to canonical chain
assert_eq!(tree.insert_block_with_senders(&block1a), Ok(BlockStatus::Accepted));
assert_eq!(tree.insert_block_with_senders(block1a.clone()), Ok(BlockStatus::Accepted));
TreeTester::default()
.with_chain_num(1)
@@ -808,7 +826,7 @@ mod tests {
)]))
.assert(&tree);
assert_eq!(tree.insert_block_with_senders(&block2a), Ok(BlockStatus::Accepted));
assert_eq!(tree.insert_block_with_senders(block2a.clone()), Ok(BlockStatus::Accepted));
// Trie state:
// b2 b2a (side chain)
// | /

View File

@@ -0,0 +1,65 @@
//! Substate for blockchain trees
use crate::blockchain_tree::chain::ForkBlock;
use reth_primitives::{BlockHash, BlockNumber};
use reth_provider::{post_state::PostState, PostStateDataProvider};
use std::collections::BTreeMap;
/// Structure that bundles references of data needs to implement [`PostStateDataProvider`]
#[derive(Clone, Debug)]
pub struct PostStateDataRef<'a> {
/// The wrapped state after execution of one or more transactions and/or blocks.
pub state: &'a PostState,
/// The blocks in the sidechain.
pub sidechain_block_hashes: &'a BTreeMap<BlockNumber, BlockHash>,
/// The blocks in the canonical chain.
pub canonical_block_hashes: &'a BTreeMap<BlockNumber, BlockHash>,
/// Canonical fork
pub canonical_fork: ForkBlock,
}
impl<'a> PostStateDataProvider for PostStateDataRef<'a> {
fn state(&self) -> &PostState {
self.state
}
fn block_hash(&self, block_number: BlockNumber) -> Option<BlockHash> {
let block_hash = self.sidechain_block_hashes.get(&block_number).cloned();
if block_hash.is_some() {
return block_hash
}
self.canonical_block_hashes.get(&block_number).cloned()
}
fn canonical_fork(&self) -> ForkBlock {
self.canonical_fork
}
}
/// Structure that contains data needs to implement [`PostStateDataProvider`]
#[derive(Clone, Debug)]
pub struct PostStateData {
/// Post state with changes
pub state: PostState,
/// 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_hashed: BTreeMap<BlockNumber, BlockHash>,
/// Canonical block where state forked from.
pub canonical_fork: ForkBlock,
}
impl PostStateDataProvider for PostStateData {
fn state(&self) -> &PostState {
&self.state
}
fn block_hash(&self, block_number: BlockNumber) -> Option<BlockHash> {
self.parent_block_hashed.get(&block_number).cloned()
}
fn canonical_fork(&self) -> ForkBlock {
self.canonical_fork
}
}

View File

@@ -0,0 +1,84 @@
//! Wrapper around BlockchainTree that allows for it to be shared.
use parking_lot::RwLock;
use reth_db::database::Database;
use reth_interfaces::{
blockchain_tree::{BlockStatus, BlockchainTreeEngine, BlockchainTreeViewer},
consensus::Consensus,
Error,
};
use reth_primitives::{BlockHash, BlockNumHash, BlockNumber, SealedBlockWithSenders};
use reth_provider::{BlockchainTreePendingStateProvider, ExecutorFactory, PostStateDataProvider};
use std::{
collections::{BTreeMap, HashSet},
sync::Arc,
};
use super::BlockchainTree;
/// Shareable blockchain tree that is behind tokio::RwLock
pub struct ShareableBlockchainTree<DB: Database, C: Consensus, EF: ExecutorFactory> {
/// BlockchainTree
pub tree: Arc<RwLock<BlockchainTree<DB, C, EF>>>,
}
impl<DB: Database, C: Consensus, EF: ExecutorFactory> ShareableBlockchainTree<DB, C, EF> {
/// Create New sharable database.
pub fn new(tree: BlockchainTree<DB, C, EF>) -> Self {
Self { tree: Arc::new(RwLock::new(tree)) }
}
}
impl<DB: Database, C: Consensus, EF: ExecutorFactory> BlockchainTreeEngine
for ShareableBlockchainTree<DB, C, EF>
{
fn insert_block_with_senders(
&self,
block: SealedBlockWithSenders,
) -> Result<BlockStatus, Error> {
self.tree.write().insert_block_with_senders(block)
}
fn finalize_block(&self, finalized_block: BlockNumber) {
self.tree.write().finalize_block(finalized_block)
}
fn restore_canonical_hashes(&self, last_finalized_block: BlockNumber) -> Result<(), Error> {
self.tree.write().restore_canonical_hashes(last_finalized_block)
}
fn make_canonical(&self, block_hash: &BlockHash) -> Result<(), Error> {
self.tree.write().make_canonical(block_hash)
}
fn unwind(&self, unwind_to: BlockNumber) -> Result<(), Error> {
self.tree.write().unwind(unwind_to)
}
}
impl<DB: Database, C: Consensus, EF: ExecutorFactory> BlockchainTreeViewer
for ShareableBlockchainTree<DB, C, EF>
{
fn pending_blocks(&self) -> BTreeMap<BlockNumber, HashSet<BlockHash>> {
self.tree.read().block_indices().index_of_number_to_pending_blocks().clone()
}
fn canonical_blocks(&self) -> BTreeMap<BlockNumber, BlockHash> {
self.tree.read().block_indices().canonical_chain().clone()
}
fn canonical_tip(&self) -> BlockNumHash {
self.tree.read().canonical_tip()
}
}
impl<DB: Database, C: Consensus, EF: ExecutorFactory> BlockchainTreePendingStateProvider
for ShareableBlockchainTree<DB, C, EF>
{
fn pending_state_provider(
&self,
block_hash: BlockHash,
) -> Result<Box<dyn PostStateDataProvider>, Error> {
let Some(post_state) = self.tree.read().post_state_data(block_hash) else { panic!("")};
Ok(Box::new(post_state))
}
}

View File

@@ -8,7 +8,6 @@
//! Reth executor executes transaction in block of data.
pub mod eth_dao_fork;
pub mod substate;
/// Execution result types.
pub use reth_provider::post_state;

View File

@@ -1,99 +0,0 @@
//! Substate for blockchain trees
use reth_interfaces::{provider::ProviderError, Result};
use reth_primitives::{Account, Address, BlockHash, BlockNumber, Bytecode, Bytes, H256, U256};
use reth_provider::{post_state::PostState, AccountProvider, BlockHashProvider, StateProvider};
use std::collections::BTreeMap;
/// A state provider that either resolves to data in a wrapped [`PostState`], or an underlying state
/// provider.
pub struct PostStateProvider<'a, SP: StateProvider> {
/// The wrapped state after execution of one or more transactions and/or blocks.
state: &'a PostState,
/// The inner state provider.
provider: SP,
/// The blocks in the sidechain.
sidechain_block_hashes: &'a BTreeMap<BlockNumber, BlockHash>,
/// The blocks in the canonical chain.
canonical_block_hashes: &'a BTreeMap<BlockNumber, BlockHash>,
}
impl<'a, SP: StateProvider> PostStateProvider<'a, SP> {
/// Create new post-state provider
pub fn new(
state: &'a PostState,
provider: SP,
sidechain_block_hashes: &'a BTreeMap<BlockNumber, BlockHash>,
canonical_block_hashes: &'a BTreeMap<BlockNumber, BlockHash>,
) -> Self {
Self { state, provider, sidechain_block_hashes, canonical_block_hashes }
}
}
/* Implement StateProvider traits */
impl<'a, SP: StateProvider> BlockHashProvider for PostStateProvider<'a, SP> {
fn block_hash(&self, block_number: BlockNumber) -> Result<Option<H256>> {
if let Some(sidechain_block_hash) = self.sidechain_block_hashes.get(&block_number).cloned()
{
return Ok(Some(sidechain_block_hash))
}
Ok(Some(
self.canonical_block_hashes
.get(&block_number)
.cloned()
.ok_or(ProviderError::BlockchainTreeBlockHash { block_number })?,
))
}
fn canonical_hashes_range(&self, _start: BlockNumber, _end: BlockNumber) -> Result<Vec<H256>> {
unimplemented!()
}
}
impl<'a, SP: StateProvider> AccountProvider for PostStateProvider<'a, SP> {
fn basic_account(&self, address: Address) -> Result<Option<Account>> {
if let Some(account) = self.state.account(&address) {
Ok(*account)
} else {
self.provider.basic_account(address)
}
}
}
impl<'a, SP: StateProvider> StateProvider for PostStateProvider<'a, SP> {
fn storage(
&self,
account: Address,
storage_key: reth_primitives::StorageKey,
) -> Result<Option<reth_primitives::StorageValue>> {
if let Some(storage) = self.state.account_storage(&account) {
if let Some(value) =
storage.storage.get(&U256::from_be_bytes(storage_key.to_fixed_bytes()))
{
return Ok(Some(*value))
} else if storage.wiped {
return Ok(Some(U256::ZERO))
}
}
self.provider.storage(account, storage_key)
}
fn bytecode_by_hash(&self, code_hash: H256) -> Result<Option<Bytecode>> {
if let Some(bytecode) = self.state.bytecode(&code_hash).cloned() {
return Ok(Some(bytecode))
}
self.provider.bytecode_by_hash(code_hash)
}
fn proof(
&self,
_address: Address,
_keys: &[H256],
) -> Result<(Vec<Bytes>, H256, Vec<Vec<Bytes>>)> {
Err(ProviderError::HistoryStateRoot.into())
}
}