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

1
Cargo.lock generated
View File

@ -4705,7 +4705,6 @@ name = "reth-executor"
version = "0.1.0"
dependencies = [
"aquamarine",
"async-trait",
"auto_impl",
"hash-db",
"parking_lot 0.12.1",

View File

@ -26,7 +26,10 @@ use reth_downloaders::{
headers::reverse_headers::ReverseHeadersDownloaderBuilder,
};
use reth_executor::{
blockchain_tree::{config::BlockchainTreeConfig, externals::TreeExternals, BlockchainTree},
blockchain_tree::{
config::BlockchainTreeConfig, externals::TreeExternals, BlockchainTree,
ShareableBlockchainTree,
},
Factory,
};
use reth_interfaces::{
@ -324,6 +327,7 @@ impl Command {
Ok((pipeline, events))
}
#[allow(clippy::type_complexity)]
fn build_consensus_engine<DB, U, C>(
&self,
db: Arc<DB>,
@ -331,7 +335,9 @@ impl Command {
consensus: C,
pipeline: Pipeline<DB, U>,
message_rx: UnboundedReceiver<BeaconEngineMessage>,
) -> eyre::Result<BeaconConsensusEngine<DB, TaskExecutor, U, C, Factory>>
) -> eyre::Result<
BeaconConsensusEngine<DB, TaskExecutor, U, ShareableBlockchainTree<Arc<DB>, C, Factory>>,
>
where
DB: Database + Unpin + 'static,
U: SyncStateUpdater + Unpin + 'static,
@ -340,7 +346,10 @@ impl Command {
let executor_factory = Factory::new(self.chain.clone());
let tree_externals =
TreeExternals::new(db.clone(), consensus, executor_factory, self.chain.clone());
let blockchain_tree = BlockchainTree::new(tree_externals, BlockchainTreeConfig::default())?;
let blockchain_tree = ShareableBlockchainTree::new(BlockchainTree::new(
tree_externals,
BlockchainTreeConfig::default(),
)?);
Ok(BeaconConsensusEngine::new(
db,

View File

@ -13,8 +13,6 @@ reth-primitives = { path = "../../primitives" }
reth-interfaces = { path = "../../interfaces" }
reth-stages = { path = "../../stages" }
reth-db = { path = "../../storage/db" }
reth-provider = { path = "../../storage/provider" }
reth-executor = { path = "../../executor" }
reth-rpc-types = { path = "../../rpc/rpc-types" }
reth-tasks = { path = "../../tasks" }

View File

@ -1,14 +1,13 @@
use futures::{Future, FutureExt, StreamExt};
use reth_db::{database::Database, tables, transaction::DbTx};
use reth_executor::blockchain_tree::{BlockStatus, BlockchainTree};
use reth_interfaces::{
consensus::{Consensus, ForkchoiceState},
blockchain_tree::{BlockStatus, BlockchainTreeEngine},
consensus::ForkchoiceState,
executor::Error as ExecutorError,
sync::SyncStateUpdater,
Error,
};
use reth_primitives::{BlockHash, BlockNumber, SealedBlock, H256};
use reth_provider::ExecutorFactory;
use reth_rpc_types::engine::{
ExecutionPayload, ForkchoiceUpdated, PayloadAttributes, PayloadStatus, PayloadStatusEnum,
};
@ -40,7 +39,7 @@ pub use pipeline_state::PipelineState;
/// The consensus engine is idle until it receives the first
/// [BeaconEngineMessage::ForkchoiceUpdated] message from the CL which would initiate the sync. At
/// first, the consensus engine would run the [Pipeline] until the latest known block hash.
/// Afterwards, it would attempt to create/restore the [BlockchainTree] from the blocks
/// Afterwards, it would attempt to create/restore the [`BlockchainTreeEngine`] from the blocks
/// that are currently available. In case the restoration is successful, the consensus engine would
/// run in a live sync mode, which mean it would solemnly rely on the messages from Engine API to
/// construct the chain forward.
@ -49,13 +48,12 @@ pub use pipeline_state::PipelineState;
///
/// If the future is polled more than once. Leads to undefined state.
#[must_use = "Future does nothing unless polled"]
pub struct BeaconConsensusEngine<DB, TS, U, C, EF>
pub struct BeaconConsensusEngine<DB, TS, U, BT>
where
DB: Database,
TS: TaskSpawner,
U: SyncStateUpdater,
C: Consensus,
EF: ExecutorFactory,
BT: BlockchainTreeEngine,
{
/// The database handle.
db: Arc<DB>,
@ -66,7 +64,7 @@ where
/// The pipeline is used for historical sync by setting the current forkchoice head.
pipeline_state: Option<PipelineState<DB, U>>,
/// The blockchain tree used for live sync and reorg tracking.
blockchain_tree: BlockchainTree<DB, C, EF>,
blockchain_tree: BT,
/// The Engine API message receiver.
message_rx: UnboundedReceiverStream<BeaconEngineMessage>,
/// Current forkchoice state. The engine must receive the initial state in order to start
@ -79,13 +77,12 @@ where
max_block: Option<BlockNumber>,
}
impl<DB, TS, U, C, EF> BeaconConsensusEngine<DB, TS, U, C, EF>
impl<DB, TS, U, BT> BeaconConsensusEngine<DB, TS, U, BT>
where
DB: Database + Unpin + 'static,
TS: TaskSpawner,
U: SyncStateUpdater + 'static,
C: Consensus,
EF: ExecutorFactory + 'static,
BT: BlockchainTreeEngine + 'static,
{
/// Create new instance of the [BeaconConsensusEngine].
///
@ -95,7 +92,7 @@ where
db: Arc<DB>,
task_spawner: TS,
pipeline: Pipeline<DB, U>,
blockchain_tree: BlockchainTree<DB, C, EF>,
blockchain_tree: BT,
message_rx: UnboundedReceiver<BeaconEngineMessage>,
max_block: Option<BlockNumber>,
) -> Self {
@ -283,8 +280,8 @@ where
}
/// Check if the engine reached max block as specified by `max_block` parameter.
fn has_reached_max_block(&self, progress: Option<BlockNumber>) -> bool {
if progress.zip(self.max_block).map_or(false, |(progress, target)| progress >= target) {
fn has_reached_max_block(&self, progress: BlockNumber) -> bool {
if self.max_block.map_or(false, |target| progress >= target) {
trace!(
target: "consensus::engine",
?progress,
@ -305,13 +302,12 @@ where
/// local forkchoice state, it will launch the pipeline to sync to the head hash.
/// While the pipeline is syncing, the consensus engine will keep processing messages from the
/// receiver and forwarding them to the blockchain tree.
impl<DB, TS, U, C, EF> Future for BeaconConsensusEngine<DB, TS, U, C, EF>
impl<DB, TS, U, BT> Future for BeaconConsensusEngine<DB, TS, U, BT>
where
DB: Database + Unpin + 'static,
TS: TaskSpawner + Unpin,
U: SyncStateUpdater + Unpin + 'static,
C: Consensus + Unpin,
EF: ExecutorFactory + Unpin + 'static,
BT: BlockchainTreeEngine + Unpin + 'static,
{
type Output = Result<(), BeaconEngineError>;
@ -338,7 +334,7 @@ where
// Terminate the sync early if it's reached the maximum user
// configured block.
if is_valid_response {
let tip_number = this.blockchain_tree.canonical_tip_number();
let tip_number = this.blockchain_tree.canonical_tip().number;
if this.has_reached_max_block(tip_number) {
return Poll::Ready(Ok(()))
}
@ -373,7 +369,7 @@ where
// Terminate the sync early if it's reached the maximum user
// configured block.
let minimum_pipeline_progress =
*pipeline.minimum_progress();
pipeline.minimum_progress().unwrap_or_default();
if this.has_reached_max_block(minimum_pipeline_progress) {
return Poll::Ready(Ok(()))
}
@ -443,7 +439,10 @@ mod tests {
use assert_matches::assert_matches;
use reth_db::mdbx::{test_utils::create_test_rw_db, Env, WriteMap};
use reth_executor::{
blockchain_tree::{config::BlockchainTreeConfig, externals::TreeExternals},
blockchain_tree::{
config::BlockchainTreeConfig, externals::TreeExternals, BlockchainTree,
ShareableBlockchainTree,
},
post_state::PostState,
test_utils::TestExecutorFactory,
};
@ -463,8 +462,7 @@ mod tests {
Env<WriteMap>,
TokioTaskExecutor,
NoopSyncStateUpdate,
TestConsensus,
TestExecutorFactory,
ShareableBlockchainTree<Arc<Env<WriteMap>>, TestConsensus, TestExecutorFactory>,
>;
struct TestEnv<DB> {
@ -528,7 +526,9 @@ mod tests {
// Setup blockchain tree
let externals = TreeExternals::new(db.clone(), consensus, executor_factory, chain_spec);
let config = BlockchainTreeConfig::new(1, 2, 3);
let tree = BlockchainTree::new(externals, config).expect("failed to create tree");
let tree = ShareableBlockchainTree::new(
BlockchainTree::new(externals, config).expect("failed to create tree"),
);
let (sync_tx, sync_rx) = unbounded_channel();
(

View File

@ -26,15 +26,14 @@ reth-provider = { path = "../storage/provider" }
revm = { version = "3.0.0" }
# common
async-trait = "0.1.57"
thiserror = "1.0.37"
auto_impl = "1.0"
tracing = "0.1.37"
tokio = { version = "1.21.2", features = ["sync"] }
parking_lot = { version = "0.12"}
# mics
aquamarine = "0.3.0"
parking_lot = { version = "0.12", optional = true }
triehash = "0.8"
# See to replace hashers to simplify libraries
@ -54,4 +53,4 @@ reth-provider = { path = "../storage/provider", features = ["test-utils"] }
parking_lot = "0.12"
[features]
test-utils = ["parking_lot"]
test-utils = []

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())
}
}

View File

@ -0,0 +1,83 @@
use crate::{executor::Error as ExecutionError, Error};
use reth_primitives::{BlockHash, BlockNumHash, BlockNumber, SealedBlock, SealedBlockWithSenders};
use std::collections::{BTreeMap, HashSet};
/// * [BlockchainTreeEngine::insert_block]: Connect block to chain, execute it and if valid insert
/// block inside tree.
/// * [BlockchainTreeEngine::finalize_block]: Remove chains that join to now finalized block, as
/// chain becomes invalid.
/// * [BlockchainTreeEngine::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 trait BlockchainTreeEngine: BlockchainTreeViewer + Send + Sync {
/// Recover senders and call [`BlockchainTreeEngine::insert_block_with_senders`].
fn insert_block(&self, block: SealedBlock) -> Result<BlockStatus, Error> {
let block = block.seal_with_senders().ok_or(ExecutionError::SenderRecoveryError)?;
self.insert_block_with_senders(block)
}
/// Insert block with senders
fn insert_block_with_senders(
&self,
block: SealedBlockWithSenders,
) -> Result<BlockStatus, Error>;
/// Finalize blocks up until and including `finalized_block`, and remove them from the tree.
fn finalize_block(&self, finalized_block: BlockNumber);
/// Reads the last `N` canonical hashes from the database and updates the block indices of the
/// tree.
///
/// `N` is the `max_reorg_depth` plus 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
/// [`BlockchainTreeEngine::finalize_block`]).
fn restore_canonical_hashes(&self, last_finalized_block: BlockNumber) -> Result<(), Error>;
/// Make a block and its parent part of the canonical chain.
///
/// # Note
///
/// This unwinds the database if necessary, i.e. if parts of the canonical chain have been
/// re-orged.
///
/// # Returns
///
/// Returns `Ok` if the blocks were canonicalized, or if the blocks were already canonical.
fn make_canonical(&self, block_hash: &BlockHash) -> Result<(), Error>;
/// Unwind tables and put it inside state
fn unwind(&self, unwind_to: BlockNumber) -> Result<(), Error>;
}
/// 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,
}
/// Allows read only functionality on the blockchain tree.
pub trait BlockchainTreeViewer: Send + Sync {
/// Get all pending block numbers and their hashes.
fn pending_blocks(&self) -> BTreeMap<BlockNumber, HashSet<BlockHash>>;
/// Canonical block number and hashes best known by the tree.
fn canonical_blocks(&self) -> BTreeMap<BlockNumber, BlockHash>;
/// Return BlockchainTree best known canonical chain tip (BlockHash, BlockNumber)
fn canonical_tip(&self) -> BlockNumHash;
}

View File

@ -32,6 +32,9 @@ pub mod provider;
/// Syncing related traits.
pub mod sync;
/// BlockchainTree related traits.
pub mod blockchain_tree;
#[cfg(any(test, feature = "test-utils"))]
/// Common test helpers for mocking out Consensus, Downloaders and Header Clients.
pub mod test_utils;

View File

@ -1,4 +1,7 @@
use crate::{Address, Header, SealedHeader, TransactionSigned, Withdrawal, H256, U64};
use crate::{
Address, BlockHash, BlockNumber, Header, SealedHeader, TransactionSigned, Withdrawal, H256,
};
use ethers_core::types::{BlockNumber as EthersBlockNumber, U64};
use reth_codecs::derive_arbitrary;
use reth_rlp::{Decodable, DecodeError, Encodable, RlpDecodable, RlpEncodable};
use serde::{
@ -225,7 +228,7 @@ impl Decodable for BlockHashOrNumber {
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum BlockId {
/// A block hash and an optional bool that defines if it's canonical
Hash(BlockHash),
Hash(RpcBlockHash),
/// A block number
Number(BlockNumberOrTag),
}
@ -266,18 +269,18 @@ impl From<BlockNumberOrTag> for BlockId {
impl From<H256> for BlockId {
fn from(block_hash: H256) -> Self {
BlockId::Hash(BlockHash { block_hash, require_canonical: None })
BlockId::Hash(RpcBlockHash { block_hash, require_canonical: None })
}
}
impl From<(H256, Option<bool>)> for BlockId {
fn from(hash_can: (H256, Option<bool>)) -> Self {
BlockId::Hash(BlockHash { block_hash: hash_can.0, require_canonical: hash_can.1 })
BlockId::Hash(RpcBlockHash { block_hash: hash_can.0, require_canonical: hash_can.1 })
}
}
impl From<BlockHash> for BlockId {
fn from(hash_can: BlockHash) -> Self {
impl From<RpcBlockHash> for BlockId {
fn from(hash_can: RpcBlockHash) -> Self {
BlockId::Hash(hash_can)
}
}
@ -297,7 +300,7 @@ impl Serialize for BlockId {
S: Serializer,
{
match *self {
BlockId::Hash(BlockHash { ref block_hash, ref require_canonical }) => {
BlockId::Hash(RpcBlockHash { ref block_hash, ref require_canonical }) => {
let mut s = serializer.serialize_struct("BlockIdEip1898", 1)?;
s.serialize_field("blockHash", block_hash)?;
if let Some(require_canonical) = require_canonical {
@ -384,7 +387,7 @@ impl<'de> Deserialize<'de> for BlockId {
if let Some(number) = number {
Ok(BlockId::Number(number))
} else if let Some(block_hash) = block_hash {
Ok(BlockId::Hash(BlockHash { block_hash, require_canonical }))
Ok(BlockId::Hash(RpcBlockHash { block_hash, require_canonical }))
} else {
Err(serde::de::Error::custom(
"Expected `blockNumber` or `blockHash` with `requireCanonical` optionally",
@ -461,22 +464,22 @@ impl From<u64> for BlockNumberOrTag {
}
}
impl From<U64> for BlockNumberOrTag {
fn from(num: U64) -> Self {
num.as_u64().into()
impl From<EthersBlockNumber> for BlockNumberOrTag {
fn from(value: EthersBlockNumber) -> Self {
match value {
EthersBlockNumber::Latest => BlockNumberOrTag::Latest,
EthersBlockNumber::Finalized => BlockNumberOrTag::Finalized,
EthersBlockNumber::Safe => BlockNumberOrTag::Safe,
EthersBlockNumber::Earliest => BlockNumberOrTag::Earliest,
EthersBlockNumber::Pending => BlockNumberOrTag::Pending,
EthersBlockNumber::Number(num) => BlockNumberOrTag::Number(num.as_u64()),
}
}
}
impl From<ethers_core::types::BlockNumber> for BlockNumberOrTag {
fn from(value: ethers_core::types::BlockNumber) -> Self {
match value {
ethers_core::types::BlockNumber::Latest => BlockNumberOrTag::Latest,
ethers_core::types::BlockNumber::Finalized => BlockNumberOrTag::Finalized,
ethers_core::types::BlockNumber::Safe => BlockNumberOrTag::Safe,
ethers_core::types::BlockNumber::Earliest => BlockNumberOrTag::Earliest,
ethers_core::types::BlockNumber::Pending => BlockNumberOrTag::Pending,
ethers_core::types::BlockNumber::Number(num) => BlockNumberOrTag::Number(num.as_u64()),
}
impl From<U64> for BlockNumberOrTag {
fn from(num: U64) -> Self {
num.as_u64().into()
}
}
@ -567,37 +570,65 @@ pub struct HexStringMissingPrefixError;
/// the block is not in the canonical chain.
/// <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1898.md#specification>
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize)]
pub struct BlockHash {
pub struct RpcBlockHash {
/// A block hash
pub block_hash: H256,
/// Whether the block must be a canonical block
pub require_canonical: Option<bool>,
}
impl BlockHash {
impl RpcBlockHash {
pub fn from_hash(block_hash: H256, require_canonical: Option<bool>) -> Self {
BlockHash { block_hash, require_canonical }
RpcBlockHash { block_hash, require_canonical }
}
}
impl From<H256> for BlockHash {
impl From<H256> for RpcBlockHash {
fn from(value: H256) -> Self {
Self::from_hash(value, None)
}
}
impl From<BlockHash> for H256 {
fn from(value: BlockHash) -> Self {
impl From<RpcBlockHash> for H256 {
fn from(value: RpcBlockHash) -> Self {
value.block_hash
}
}
impl AsRef<H256> for BlockHash {
impl AsRef<H256> for RpcBlockHash {
fn as_ref(&self) -> &H256 {
&self.block_hash
}
}
/// Block number and hash.
#[derive(Clone, Copy, Default, PartialEq, Eq)]
pub struct BlockNumHash {
/// Block number
pub number: BlockNumber,
/// Block hash
pub hash: BlockHash,
}
impl std::fmt::Debug for BlockNumHash {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("").field(&self.number).field(&self.hash).finish()
}
}
impl BlockNumHash {
/// Consumes `Self` and returns [`BlockNumber`], [`BlockHash`]
pub fn into_components(self) -> (BlockNumber, BlockHash) {
(self.number, self.hash)
}
}
impl From<(BlockNumber, BlockHash)> for BlockNumHash {
fn from(val: (BlockNumber, BlockHash)) -> Self {
BlockNumHash { number: val.0, hash: val.1 }
}
}
/// A response to `GetBlockBodies`, containing bodies if any bodies were found.
///
/// Withdrawals can be optionally included at the end of the RLP encoded message.
@ -657,7 +688,7 @@ mod test {
let block_hash =
H256::from_str("0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3")
.unwrap();
let block_id = BlockId::Hash(BlockHash::from_hash(block_hash, Some(true)));
let block_id = BlockId::Hash(RpcBlockHash::from_hash(block_hash, Some(true)));
let block_hash_json = serde_json::json!(
{ "blockHash": "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3", "requireCanonical": true }
);

View File

@ -39,7 +39,7 @@ pub mod proofs;
pub use account::{Account, Bytecode};
pub use bits::H512;
pub use block::{
Block, BlockBody, BlockHashOrNumber, BlockId, BlockNumberOrTag, SealedBlock,
Block, BlockBody, BlockHashOrNumber, BlockId, BlockNumHash, BlockNumberOrTag, SealedBlock,
SealedBlockWithSenders,
};
pub use bloom::Bloom;

View File

@ -8,7 +8,7 @@ use async_trait::async_trait;
use reth_interfaces::Result;
use reth_network_api::NetworkInfo;
use reth_primitives::{Address, BlockId, BlockNumberOrTag, ChainInfo, H256, U256, U64};
use reth_provider::{providers::ChainState, BlockProvider, EvmEnvProvider, StateProviderFactory};
use reth_provider::{BlockProvider, EvmEnvProvider, StateProviderBox, StateProviderFactory};
use reth_rpc_types::{FeeHistoryCache, SyncInfo, SyncStatus};
use reth_transaction_pool::TransactionPool;
use std::{num::NonZeroUsize, sync::Arc};
@ -108,9 +108,9 @@ where
}
/// Returns the state at the given [BlockId] enum.
pub fn state_at_block_id(&self, at: BlockId) -> EthResult<ChainState<'_>> {
pub fn state_at_block_id(&self, at: BlockId) -> EthResult<StateProviderBox<'_>> {
match at {
BlockId::Hash(hash) => Ok(self.state_at_hash(hash.into()).map(ChainState::boxed)?),
BlockId::Hash(hash) => Ok(self.state_at_hash(hash.into())?),
BlockId::Number(num) => {
self.state_at_block_number(num)?.ok_or(EthApiError::UnknownBlockNumber)
}
@ -121,18 +121,21 @@ where
pub fn state_at_block_id_or_latest(
&self,
block_id: Option<BlockId>,
) -> EthResult<ChainState<'_>> {
) -> EthResult<StateProviderBox<'_>> {
if let Some(block_id) = block_id {
self.state_at_block_id(block_id)
} else {
Ok(self.latest_state().map(ChainState::boxed)?)
Ok(self.latest_state()?)
}
}
/// Returns the state at the given [BlockNumberOrTag] enum
///
/// Returns `None` if no state available.
pub fn state_at_block_number(&self, num: BlockNumberOrTag) -> Result<Option<ChainState<'_>>> {
pub fn state_at_block_number(
&self,
num: BlockNumberOrTag,
) -> Result<Option<StateProviderBox<'_>>> {
if let Some(number) = self.convert_block_number(num)? {
self.state_at_number(number).map(Some)
} else {
@ -141,23 +144,20 @@ where
}
/// Returns the state at the given block number
pub fn state_at_hash(
&self,
block_hash: H256,
) -> Result<<Client as StateProviderFactory>::HistorySP<'_>> {
pub fn state_at_hash(&self, block_hash: H256) -> Result<StateProviderBox<'_>> {
self.client().history_by_block_hash(block_hash)
}
/// Returns the state at the given block number
pub fn state_at_number(&self, block_number: u64) -> Result<ChainState<'_>> {
pub fn state_at_number(&self, block_number: u64) -> Result<StateProviderBox<'_>> {
match self.convert_block_number(BlockNumberOrTag::Latest)? {
Some(num) if num == block_number => self.latest_state().map(ChainState::boxed),
_ => self.client().history_by_block_number(block_number).map(ChainState::boxed),
Some(num) if num == block_number => self.latest_state(),
_ => self.client().history_by_block_number(block_number),
}
}
/// Returns the _latest_ state
pub fn latest_state(&self) -> Result<<Client as StateProviderFactory>::LatestSP<'_>> {
pub fn latest_state(&self) -> Result<StateProviderBox<'_>> {
self.client().latest()
}
}

View File

@ -14,7 +14,7 @@ use reth_primitives::{
TransactionMeta, TransactionSigned, TransactionSignedEcRecovered, TxEip1559, TxEip2930,
TxLegacy, H256, U128, U256, U64,
};
use reth_provider::{providers::ChainState, BlockProvider, EvmEnvProvider, StateProviderFactory};
use reth_provider::{BlockProvider, EvmEnvProvider, StateProviderBox, StateProviderFactory};
use reth_rpc_types::{
Index, Log, Transaction, TransactionInfo, TransactionReceipt, TransactionRequest,
TypedTransactionRequest,
@ -27,12 +27,12 @@ use revm_primitives::utilities::create_address;
#[async_trait::async_trait]
pub trait EthTransactions: Send + Sync {
/// Returns the state at the given [BlockId]
fn state_at(&self, at: BlockId) -> EthResult<ChainState<'_>>;
fn state_at(&self, at: BlockId) -> EthResult<StateProviderBox<'_>>;
/// Executes the closure with the state that corresponds to the given [BlockId].
fn with_state_at<F, T>(&self, _at: BlockId, _f: F) -> EthResult<T>
where
F: FnOnce(ChainState<'_>) -> EthResult<T>;
F: FnOnce(StateProviderBox<'_>) -> EthResult<T>;
/// Returns the revm evm env for the requested [BlockId]
///
@ -76,13 +76,13 @@ where
Client: BlockProvider + StateProviderFactory + EvmEnvProvider + 'static,
Network: Send + Sync + 'static,
{
fn state_at(&self, at: BlockId) -> EthResult<ChainState<'_>> {
fn state_at(&self, at: BlockId) -> EthResult<StateProviderBox<'_>> {
self.state_at_block_id(at)
}
fn with_state_at<F, T>(&self, at: BlockId, f: F) -> EthResult<T>
where
F: FnOnce(ChainState<'_>) -> EthResult<T>,
F: FnOnce(StateProviderBox<'_>) -> EthResult<T>,
{
let state = self.state_at(at)?;
f(state)

View File

@ -6,7 +6,7 @@ use reth_db::{
tx::Tx,
Env, EnvKind, WriteMap, RW,
},
models::{AccountBeforeTx, BlockNumHash, StoredBlockBody},
models::{AccountBeforeTx, StoredBlockBody},
table::Table,
tables,
transaction::{DbTx, DbTxMut},

View File

@ -56,7 +56,7 @@ impl<T> Value for T where T: Compress + Decompress + Serialize {}
/// [`Decode`] when appropriate. These traits define how the data is stored and read from the
/// database.
///
/// It allows for the use of codecs. See [`crate::models::BlockNumHash`] for a custom
/// It allows for the use of codecs. See [`crate::models::ShardedKey`] for a custom
/// implementation.
pub trait Table: Send + Sync + Debug + 'static {
/// Return table name as it is present inside the MDBX.

View File

@ -86,5 +86,5 @@ macro_rules! impl_fuzzer_value_with_input {
};
}
impl_fuzzer_key!(BlockNumHash, TransitionIdAddress);
impl_fuzzer_key!(TransitionIdAddress);
impl_fuzzer_value_with_input!((IntegerList, IntegerListInput));

View File

@ -1,16 +1,8 @@
//! Block related models and types.
use std::ops::Range;
use crate::{
impl_fixed_arbitrary,
table::{Decode, Encode},
Error,
};
use reth_codecs::{main_codec, Compact};
use reth_primitives::{bytes::Bytes, BlockHash, BlockNumber, Header, TxNumber, Withdrawal, H256};
use serde::{Deserialize, Serialize};
use reth_primitives::{Header, TxNumber, Withdrawal, H256};
use std::ops::Range;
/// Total number of transactions.
pub type NumTransactions = u64;
@ -78,108 +70,11 @@ pub struct StoredBlockWithdrawals {
/// Hash of the block header. Value for [`CanonicalHeaders`][crate::tables::CanonicalHeaders]
pub type HeaderHash = H256;
/// BlockNumber concatenated with BlockHash. Used as a key for multiple tables. Having the first
/// element as BlockNumber, helps out with querying/sorting.
///
/// Since it's used as a key, the `BlockNumber` is not compressed when encoding it.
#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default, Ord, PartialOrd, Hash)]
pub struct BlockNumHash(pub (BlockNumber, BlockHash));
impl std::fmt::Debug for BlockNumHash {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("").field(&self.0 .0).field(&self.0 .1).finish()
}
}
impl BlockNumHash {
/// Consumes `Self` and returns [`BlockNumber`], [`BlockHash`]
pub fn take(self) -> (BlockNumber, BlockHash) {
(self.0 .0, self.0 .1)
}
/// Return the block number
pub fn number(&self) -> BlockNumber {
self.0 .0
}
/// Return the block hash
pub fn hash(&self) -> BlockHash {
self.0 .1
}
}
impl From<(u64, H256)> for BlockNumHash {
fn from(tpl: (u64, H256)) -> Self {
BlockNumHash(tpl)
}
}
impl From<u64> for BlockNumHash {
fn from(tpl: u64) -> Self {
BlockNumHash((tpl, H256::default()))
}
}
impl Encode for BlockNumHash {
type Encoded = [u8; 40];
fn encode(self) -> Self::Encoded {
let number = self.0 .0;
let hash = self.0 .1;
let mut rnum = [0; 40];
rnum[..8].copy_from_slice(&number.to_be_bytes());
rnum[8..].copy_from_slice(hash.as_bytes());
rnum
}
}
impl Decode for BlockNumHash {
fn decode<B: Into<Bytes>>(value: B) -> Result<Self, Error> {
let value: Bytes = value.into();
let num =
u64::from_be_bytes(value.as_ref()[..8].try_into().map_err(|_| Error::DecodeError)?);
let hash = H256::from_slice(&value.slice(8..));
Ok(BlockNumHash((num, hash)))
}
}
impl_fixed_arbitrary!(BlockNumHash, 40);
#[cfg(test)]
mod test {
use crate::table::{Compress, Decompress};
use super::*;
use rand::{thread_rng, Rng};
#[test]
fn test_block_num_hash() {
let num = 1u64;
let hash = H256::from_low_u64_be(2);
let key = BlockNumHash((num, hash));
let mut bytes = [0u8; 40];
bytes[..8].copy_from_slice(&num.to_be_bytes());
bytes[8..].copy_from_slice(&hash.0);
let encoded = Encode::encode(key);
assert_eq!(encoded, bytes);
let decoded: BlockNumHash = Decode::decode(encoded.to_vec()).unwrap();
assert_eq!(decoded, key);
}
#[test]
fn test_block_num_hash_rand() {
let mut bytes = [0u8; 40];
thread_rng().fill(bytes.as_mut_slice());
let key = BlockNumHash::arbitrary(&mut Unstructured::new(&bytes)).unwrap();
assert_eq!(bytes, Encode::encode(key));
}
#[test]
fn test_ommer() {

View File

@ -12,8 +12,9 @@
mod traits;
pub use traits::{
AccountProvider, BlockExecutor, BlockHashProvider, BlockIdProvider, BlockProvider,
EvmEnvProvider, ExecutorFactory, HeaderProvider, ReceiptProvider, StateProvider,
StateProviderFactory, TransactionsProvider, WithdrawalsProvider,
BlockchainTreePendingStateProvider, EvmEnvProvider, ExecutorFactory, HeaderProvider,
PostStateDataProvider, ReceiptProvider, StateProvider, StateProviderBox, StateProviderFactory,
TransactionsProvider, WithdrawalsProvider,
};
/// Provider trait implementations.
@ -28,6 +29,7 @@ pub mod trie;
/// Execution result
pub mod post_state;
pub use post_state::PostState;
/// Helper types for interacting with the database
mod transaction;

View File

@ -1,13 +1,9 @@
use crate::{
BlockHashProvider, BlockIdProvider, BlockProvider, EvmEnvProvider, HeaderProvider,
ProviderError, StateProviderFactory, TransactionsProvider, WithdrawalsProvider,
};
use reth_db::{
cursor::DbCursorRO,
database::{Database, DatabaseGAT},
tables,
transaction::DbTx,
PostStateDataProvider, ProviderError, StateProviderBox, StateProviderFactory,
TransactionsProvider, WithdrawalsProvider,
};
use reth_db::{cursor::DbCursorRO, database::Database, tables, transaction::DbTx};
use reth_interfaces::Result;
use reth_primitives::{
Block, BlockHash, BlockId, BlockNumber, ChainInfo, ChainSpec, Hardfork, Head, Header, Receipt,
@ -23,11 +19,13 @@ use std::{ops::RangeBounds, sync::Arc};
mod state;
use crate::traits::ReceiptProvider;
pub use state::{
chain::ChainState,
historical::{HistoricalStateProvider, HistoricalStateProviderRef},
latest::{LatestStateProvider, LatestStateProviderRef},
};
mod post_state_provider;
pub use post_state_provider::PostStateProvider;
/// A common provider that fetches data from a database.
///
/// This provider implements most provider or provider factory traits.
@ -407,15 +405,12 @@ impl<DB: Database> EvmEnvProvider for ShareableDatabase<DB> {
}
impl<DB: Database> StateProviderFactory for ShareableDatabase<DB> {
type HistorySP<'a> = HistoricalStateProvider<'a,<DB as DatabaseGAT<'a>>::TX> where Self: 'a;
type LatestSP<'a> = LatestStateProvider<'a,<DB as DatabaseGAT<'a>>::TX> where Self: 'a;
/// Storage provider for latest block
fn latest(&self) -> Result<Self::LatestSP<'_>> {
Ok(LatestStateProvider::new(self.db.tx()?))
fn latest(&self) -> Result<StateProviderBox<'_>> {
Ok(Box::new(LatestStateProvider::new(self.db.tx()?)))
}
fn history_by_block_number(&self, block_number: BlockNumber) -> Result<Self::HistorySP<'_>> {
fn history_by_block_number(&self, block_number: BlockNumber) -> Result<StateProviderBox<'_>> {
let tx = self.db.tx()?;
// get transition id
@ -423,10 +418,10 @@ impl<DB: Database> StateProviderFactory for ShareableDatabase<DB> {
.get::<tables::BlockTransitionIndex>(block_number)?
.ok_or(ProviderError::BlockTransition { block_number })?;
Ok(HistoricalStateProvider::new(tx, transition))
Ok(Box::new(HistoricalStateProvider::new(tx, transition)))
}
fn history_by_block_hash(&self, block_hash: BlockHash) -> Result<Self::HistorySP<'_>> {
fn history_by_block_hash(&self, block_hash: BlockHash) -> Result<StateProviderBox<'_>> {
let tx = self.db.tx()?;
// get block number
let block_number = tx
@ -438,7 +433,18 @@ impl<DB: Database> StateProviderFactory for ShareableDatabase<DB> {
.get::<tables::BlockTransitionIndex>(block_number)?
.ok_or(ProviderError::BlockTransition { block_number })?;
Ok(HistoricalStateProvider::new(tx, transition))
Ok(Box::new(HistoricalStateProvider::new(tx, transition)))
}
fn pending(
&self,
post_state_data: Box<dyn PostStateDataProvider>,
) -> Result<StateProviderBox<'_>> {
let canonical_fork = post_state_data.canonical_fork();
let state_provider = self.history_by_block_hash(canonical_fork.hash)?;
let post_state_provider =
PostStateProvider { state_provider, post_state_data_provider: post_state_data };
Ok(Box::new(post_state_provider))
}
}

View File

@ -0,0 +1,86 @@
use crate::{AccountProvider, BlockHashProvider, PostStateDataProvider, StateProvider};
use reth_interfaces::{provider::ProviderError, Result};
use reth_primitives::{Account, Address, BlockNumber, Bytecode, Bytes, H256, U256};
/// A state provider that either resolves to data in a wrapped [`crate::PostState`], or an
/// underlying state provider.
pub struct PostStateProvider<SP: StateProvider, PSDP: PostStateDataProvider> {
/// The inner state provider.
pub state_provider: SP,
/// Post state data,
pub post_state_data_provider: PSDP,
}
impl<SP: StateProvider, PSDP: PostStateDataProvider> PostStateProvider<SP, PSDP> {
/// Create new post-state provider
pub fn new(state_provider: SP, post_state_data_provider: PSDP) -> Self {
Self { state_provider, post_state_data_provider }
}
}
/* Implement StateProvider traits */
impl<SP: StateProvider, PSDP: PostStateDataProvider> BlockHashProvider
for PostStateProvider<SP, PSDP>
{
fn block_hash(&self, block_number: BlockNumber) -> Result<Option<H256>> {
let block_hash = self.post_state_data_provider.block_hash(block_number);
if block_hash.is_some() {
return Ok(block_hash)
}
self.state_provider.block_hash(block_number)
}
fn canonical_hashes_range(&self, _start: BlockNumber, _end: BlockNumber) -> Result<Vec<H256>> {
unimplemented!()
}
}
impl<SP: StateProvider, PSDP: PostStateDataProvider> AccountProvider
for PostStateProvider<SP, PSDP>
{
fn basic_account(&self, address: Address) -> Result<Option<Account>> {
if let Some(account) = self.post_state_data_provider.state().account(&address) {
Ok(*account)
} else {
self.state_provider.basic_account(address)
}
}
}
impl<SP: StateProvider, PSDP: PostStateDataProvider> StateProvider for PostStateProvider<SP, PSDP> {
fn storage(
&self,
account: Address,
storage_key: reth_primitives::StorageKey,
) -> Result<Option<reth_primitives::StorageValue>> {
if let Some(storage) = self.post_state_data_provider.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.state_provider.storage(account, storage_key)
}
fn bytecode_by_hash(&self, code_hash: H256) -> Result<Option<Bytecode>> {
if let Some(bytecode) = self.post_state_data_provider.state().bytecode(&code_hash).cloned()
{
return Ok(Some(bytecode))
}
self.state_provider.bytecode_by_hash(code_hash)
}
fn proof(
&self,
_address: Address,
_keys: &[H256],
) -> Result<(Vec<Bytes>, H256, Vec<Vec<Bytes>>)> {
Err(ProviderError::HistoryStateRoot.into())
}
}

View File

@ -1,54 +0,0 @@
use crate::{
providers::state::macros::delegate_provider_impls, AccountProvider, BlockHashProvider,
StateProvider,
};
/// A type that can access the state at a specific access point (block number or tag)
///
/// Depending on the desired access point, the state must be accessed differently. For example, the
/// "Latest" state is stored in a different location than previous blocks. And the "Pending" state
/// is accessed differently than the "Latest" state.
///
/// This unifies [StateProvider] access when the caller does not know or care where the state is
/// being accessed from, e.g. in RPC where the requested access point may be
/// `Pending|Latest|Number|Hash`.
///
/// Note: The lifetime of this type is limited by the type that created it.
pub struct ChainState<'a> {
inner: Box<dyn StateProvider + 'a>,
}
// == impl ChainState ===
impl<'a> ChainState<'a> {
/// Wraps the given [StateProvider]
pub fn boxed<S: StateProvider + 'a>(inner: S) -> Self {
Self::new(Box::new(inner))
}
/// Wraps the given [StateProvider]
pub fn new(inner: Box<dyn StateProvider + 'a>) -> Self {
Self { inner }
}
/// Returns a new provider that takes the `TX` as reference
#[inline(always)]
fn as_ref(&self) -> impl StateProvider + '_ {
&*self.inner
}
}
// Delegates all provider impls to the boxed [StateProvider]
delegate_provider_impls!(ChainState<'a>);
#[cfg(test)]
mod tests {
use super::*;
fn assert_state_provider<T: StateProvider>() {}
#[allow(unused)]
#[allow(clippy::extra_unused_lifetimes)]
fn assert_chain_state_provider<'txn>() {
assert_state_provider::<ChainState<'txn>>();
}
}

View File

@ -1,5 +1,4 @@
//! [StateProvider](crate::StateProvider) implementations
pub(crate) mod chain;
pub(crate) mod historical;
pub(crate) mod latest;
pub(crate) mod macros;

View File

@ -1,6 +1,7 @@
use crate::{
traits::ReceiptProvider, AccountProvider, BlockHashProvider, BlockIdProvider, BlockProvider,
EvmEnvProvider, HeaderProvider, StateProvider, StateProviderFactory, TransactionsProvider,
EvmEnvProvider, HeaderProvider, PostStateDataProvider, StateProvider, StateProviderBox,
StateProviderFactory, TransactionsProvider,
};
use parking_lot::Mutex;
use reth_interfaces::Result;
@ -321,41 +322,49 @@ impl EvmEnvProvider for MockEthProvider {
}
impl StateProviderFactory for MockEthProvider {
type HistorySP<'a> = &'a MockEthProvider where Self: 'a;
type LatestSP<'a> = &'a MockEthProvider where Self: 'a;
fn latest(&self) -> Result<Self::LatestSP<'_>> {
Ok(self)
fn latest(&self) -> Result<StateProviderBox<'_>> {
Ok(Box::new(self.clone()))
}
fn history_by_block_number(
&self,
_block: reth_primitives::BlockNumber,
) -> Result<Self::HistorySP<'_>> {
) -> Result<StateProviderBox<'_>> {
todo!()
}
fn history_by_block_hash(&self, _block: BlockHash) -> Result<Self::HistorySP<'_>> {
fn history_by_block_hash(&self, _block: BlockHash) -> Result<StateProviderBox<'_>> {
todo!()
}
fn pending<'a>(
&'a self,
_post_state_data: Box<dyn PostStateDataProvider + 'a>,
) -> Result<StateProviderBox<'a>> {
todo!()
}
}
impl StateProviderFactory for Arc<MockEthProvider> {
type HistorySP<'a> = &'a MockEthProvider where Self: 'a;
type LatestSP<'a> = &'a MockEthProvider where Self: 'a;
fn latest(&self) -> Result<Self::LatestSP<'_>> {
Ok(self)
fn latest(&self) -> Result<StateProviderBox<'_>> {
Ok(Box::new(self.clone()))
}
fn history_by_block_number(
&self,
_block: reth_primitives::BlockNumber,
) -> Result<Self::HistorySP<'_>> {
) -> Result<StateProviderBox<'_>> {
todo!()
}
fn history_by_block_hash(&self, _block: BlockHash) -> Result<Self::HistorySP<'_>> {
fn history_by_block_hash(&self, _block: BlockHash) -> Result<StateProviderBox<'_>> {
todo!()
}
fn pending<'a>(
&'a self,
_post_state_data: Box<dyn PostStateDataProvider + 'a>,
) -> Result<StateProviderBox<'a>> {
todo!()
}
}

View File

@ -1,6 +1,7 @@
use crate::{
traits::ReceiptProvider, AccountProvider, BlockHashProvider, BlockIdProvider, BlockProvider,
EvmEnvProvider, HeaderProvider, StateProvider, StateProviderFactory, TransactionsProvider,
EvmEnvProvider, HeaderProvider, StateProvider, StateProviderBox, StateProviderFactory,
TransactionsProvider,
};
use reth_interfaces::Result;
use reth_primitives::{
@ -180,18 +181,22 @@ impl EvmEnvProvider for NoopProvider {
}
impl StateProviderFactory for NoopProvider {
type HistorySP<'a> = NoopProvider where Self: 'a;
type LatestSP<'a> = NoopProvider where Self: 'a;
fn latest(&self) -> Result<Self::LatestSP<'_>> {
Ok(*self)
fn latest(&self) -> Result<StateProviderBox<'_>> {
Ok(Box::new(*self))
}
fn history_by_block_number(&self, _block: BlockNumber) -> Result<Self::HistorySP<'_>> {
Ok(*self)
fn history_by_block_number(&self, _block: BlockNumber) -> Result<StateProviderBox<'_>> {
Ok(Box::new(*self))
}
fn history_by_block_hash(&self, _block: BlockHash) -> Result<Self::HistorySP<'_>> {
Ok(*self)
fn history_by_block_hash(&self, _block: BlockHash) -> Result<StateProviderBox<'_>> {
Ok(Box::new(*self))
}
fn pending<'a>(
&'a self,
_post_state_data: Box<dyn crate::PostStateDataProvider + 'a>,
) -> Result<StateProviderBox<'a>> {
Ok(Box::new(*self))
}
}

View File

@ -3,7 +3,7 @@ use reth_interfaces::Result;
use reth_primitives::{Account, Address};
/// Account provider
#[auto_impl(&,Box)]
#[auto_impl(&, Arc, Box)]
pub trait AccountProvider: Send + Sync {
/// Get basic account information.
fn basic_account(&self, address: Address) -> Result<Option<Account>>;

View File

@ -22,7 +22,10 @@ mod receipts;
pub use receipts::ReceiptProvider;
mod state;
pub use state::{StateProvider, StateProviderFactory};
pub use state::{
BlockchainTreePendingStateProvider, PostStateDataProvider, StateProvider, StateProviderBox,
StateProviderFactory,
};
mod transactions;
pub use transactions::TransactionsProvider;

View File

@ -1,14 +1,17 @@
use super::AccountProvider;
use crate::BlockHashProvider;
use crate::{post_state::PostState, BlockHashProvider};
use auto_impl::auto_impl;
use reth_interfaces::Result;
use reth_primitives::{
Address, BlockHash, BlockNumber, Bytecode, Bytes, StorageKey, StorageValue, H256, KECCAK_EMPTY,
U256,
Address, BlockHash, BlockNumHash, BlockNumber, Bytecode, Bytes, StorageKey, StorageValue, H256,
KECCAK_EMPTY, U256,
};
/// Type alias of boxed [StateProvider].
pub type StateProviderBox<'a> = Box<dyn StateProvider + 'a>;
/// An abstraction for a type that provides state data.
#[auto_impl(&, Box)]
#[auto_impl(&, Arc, Box)]
pub trait StateProvider: BlockHashProvider + AccountProvider + Send + Sync {
/// Get storage of given account.
fn storage(&self, account: Address, storage_key: StorageKey) -> Result<Option<StorageValue>>;
@ -71,21 +74,47 @@ pub trait StateProvider: BlockHashProvider + AccountProvider + Send + Sync {
/// Light wrapper that returns `StateProvider` implementations that correspond to the given
/// `BlockNumber` or the latest state.
pub trait StateProviderFactory: Send + Sync {
/// History State provider.
type HistorySP<'a>: StateProvider
where
Self: 'a;
/// Latest state provider.
type LatestSP<'a>: StateProvider
where
Self: 'a;
/// Storage provider for latest block.
fn latest(&self) -> Result<Self::LatestSP<'_>>;
fn latest(&self) -> Result<StateProviderBox<'_>>;
/// Returns a [StateProvider] indexed by the given block number.
fn history_by_block_number(&self, block: BlockNumber) -> Result<Self::HistorySP<'_>>;
fn history_by_block_number(&self, block: BlockNumber) -> Result<StateProviderBox<'_>>;
/// Returns a [StateProvider] indexed by the given block hash.
fn history_by_block_hash(&self, block: BlockHash) -> Result<Self::HistorySP<'_>>;
fn history_by_block_hash(&self, block: BlockHash) -> Result<StateProviderBox<'_>>;
/// Return a [StateProvider] that contains post state data provider.
/// Used to inspect or execute transaction on the pending state.
fn pending(
&self,
post_state_data: Box<dyn PostStateDataProvider>,
) -> Result<StateProviderBox<'_>>;
}
/// Blockchain trait provider
pub trait BlockchainTreePendingStateProvider: Send + Sync {
/// Return state provider over pending state.
fn pending_state_provider(
&self,
block_hash: BlockHash,
) -> Result<Box<dyn PostStateDataProvider>>;
}
/// Post state data needs for execution on it.
/// This trait is used to create state provider over pending state.
///
/// Pending state contains:
/// * [`PostState`] contains all changed of accounts and storage of pending chain
/// * block hashes of pending chain and canonical blocks.
/// * canonical fork, the block on what pending chain was forked from.
#[auto_impl[Box,&]]
pub trait PostStateDataProvider: Send + Sync {
/// Return post state
fn state(&self) -> &PostState;
/// Return block hash by block number of pending or canonical chain.
fn block_hash(&self, block_number: BlockNumber) -> Option<BlockHash>;
/// return canonical fork, the block on what post state was forked from.
///
/// Needed to create state provider.
fn canonical_fork(&self) -> BlockNumHash;
}