diff --git a/Cargo.lock b/Cargo.lock index 51f1e86bd6..8e318ceab6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4705,7 +4705,6 @@ name = "reth-executor" version = "0.1.0" dependencies = [ "aquamarine", - "async-trait", "auto_impl", "hash-db", "parking_lot 0.12.1", diff --git a/bin/reth/src/node/mod.rs b/bin/reth/src/node/mod.rs index e24295d98d..4b944f07f6 100644 --- a/bin/reth/src/node/mod.rs +++ b/bin/reth/src/node/mod.rs @@ -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( &self, db: Arc, @@ -331,7 +335,9 @@ impl Command { consensus: C, pipeline: Pipeline, message_rx: UnboundedReceiver, - ) -> eyre::Result> + ) -> eyre::Result< + BeaconConsensusEngine, 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, diff --git a/crates/consensus/beacon/Cargo.toml b/crates/consensus/beacon/Cargo.toml index 1c977d6def..b98311d9eb 100644 --- a/crates/consensus/beacon/Cargo.toml +++ b/crates/consensus/beacon/Cargo.toml @@ -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" } diff --git a/crates/consensus/beacon/src/engine/mod.rs b/crates/consensus/beacon/src/engine/mod.rs index 344d796a78..97f1c1041d 100644 --- a/crates/consensus/beacon/src/engine/mod.rs +++ b/crates/consensus/beacon/src/engine/mod.rs @@ -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 +pub struct BeaconConsensusEngine where DB: Database, TS: TaskSpawner, U: SyncStateUpdater, - C: Consensus, - EF: ExecutorFactory, + BT: BlockchainTreeEngine, { /// The database handle. db: Arc, @@ -66,7 +64,7 @@ where /// The pipeline is used for historical sync by setting the current forkchoice head. pipeline_state: Option>, /// The blockchain tree used for live sync and reorg tracking. - blockchain_tree: BlockchainTree, + blockchain_tree: BT, /// The Engine API message receiver. message_rx: UnboundedReceiverStream, /// Current forkchoice state. The engine must receive the initial state in order to start @@ -79,13 +77,12 @@ where max_block: Option, } -impl BeaconConsensusEngine +impl BeaconConsensusEngine 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, task_spawner: TS, pipeline: Pipeline, - blockchain_tree: BlockchainTree, + blockchain_tree: BT, message_rx: UnboundedReceiver, max_block: Option, ) -> 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) -> 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 Future for BeaconConsensusEngine +impl Future for BeaconConsensusEngine 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, TokioTaskExecutor, NoopSyncStateUpdate, - TestConsensus, - TestExecutorFactory, + ShareableBlockchainTree>, TestConsensus, TestExecutorFactory>, >; struct TestEnv { @@ -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(); ( diff --git a/crates/executor/Cargo.toml b/crates/executor/Cargo.toml index c57347abf5..8731435414 100644 --- a/crates/executor/Cargo.toml +++ b/crates/executor/Cargo.toml @@ -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 = [] diff --git a/crates/executor/src/blockchain_tree/block_indices.rs b/crates/executor/src/blockchain_tree/block_indices.rs index ba5c63fa9f..6743beea10 100644 --- a/crates/executor/src/blockchain_tree/block_indices.rs +++ b/crates/executor/src/blockchain_tree/block_indices.rs @@ -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, /// 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>, + index_number_to_block: BTreeMap>, } 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> { + &self.index_number_to_block + } + /// Return fork to child indices pub fn fork_to_child(&self) -> &HashMap> { &self.fork_to_child @@ -169,7 +174,9 @@ impl BlockIndices { block_hash: BlockHash, ) -> BTreeSet { // 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 { &self.canonical_chain } diff --git a/crates/executor/src/blockchain_tree/chain.rs b/crates/executor/src/blockchain_tree/chain.rs index 30505c5862..5aec1fbbd5 100644 --- a/crates/executor/src/blockchain_tree/chain.rs +++ b/crates/executor/src/blockchain_tree/chain.rs @@ -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, } -/// 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 { + 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 { + 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, 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( + pub fn new_canonical_fork( block: &SealedBlockWithSenders, parent_header: &SealedHeader, canonical_block_hashes: &BTreeMap, - provider: &SP, - consensus: &C, - factory: &EF, - ) -> Result { + canonical_fork: ForkBlock, + externals: &TreeExternals, + ) -> Result + 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( + pub fn new_chain_fork( &self, block: SealedBlockWithSenders, side_chain_block_hashes: BTreeMap, canonical_block_hashes: &BTreeMap, - provider: &SP, - consensus: &C, - factory: &EF, - ) -> Result { + canonical_fork: ForkBlock, + externals: &TreeExternals, + ) -> Result + 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( + fn validate_and_execute( block: SealedBlockWithSenders, parent_block: &SealedHeader, - state_provider: PostStateProvider<'_, SP>, - consensus: &C, - factory: &EF, - ) -> Result { - 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, + ) -> Result + 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( + pub fn append_block( &mut self, block: SealedBlockWithSenders, side_chain_block_hashes: BTreeMap, canonical_block_hashes: &BTreeMap, - provider: &SP, - consensus: &C, - factory: &EF, - ) -> Result<(), Error> { + canonical_fork: ForkBlock, + externals: &TreeExternals, + ) -> 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)), diff --git a/crates/executor/src/blockchain_tree/externals.rs b/crates/executor/src/blockchain_tree/externals.rs index 874760ff42..8df3f590f9 100644 --- a/crates/executor/src/blockchain_tree/externals.rs +++ b/crates/executor/src/blockchain_tree/externals.rs @@ -17,7 +17,7 @@ use std::sync::Arc; #[derive(Debug)] pub struct TreeExternals { /// The database, used to commit the canonical chain, or unwind it. - pub db: Arc, + pub db: DB, /// The consensus engine. pub consensus: C, /// The executor factory to execute blocks with. @@ -28,12 +28,7 @@ pub struct TreeExternals { impl TreeExternals { /// Create new tree externals. - pub fn new( - db: Arc, - consensus: C, - executor_factory: EF, - chain_spec: Arc, - ) -> Self { + pub fn new(db: DB, consensus: C, executor_factory: EF, chain_spec: Arc) -> Self { Self { db, consensus, executor_factory, chain_spec } } } diff --git a/crates/executor/src/blockchain_tree/mod.rs b/crates/executor/src/blockchain_tree/mod.rs index 6ae964007e..fe25987601 100644 --- a/crates/executor/src/blockchain_tree/mod.rs +++ b/crates/executor/src/blockchain_tree/mod.rs @@ -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 { 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 BlockchainTree }) } - /// Return the tip of the canonical chain - pub fn canonical_tip_number(&self) -> Option { - 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` 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 { + // 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::>(); + 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 { - 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 { - 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 BlockchainTree /// This recovers transaction signers (unlike [`BlockchainTree::insert_block_with_senders`]). pub fn insert_block(&mut self, block: SealedBlock) -> Result { 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 BlockchainTree /// instead. pub fn insert_block_with_senders( &mut self, - block: &SealedBlockWithSenders, + block: SealedBlockWithSenders, ) -> Result { // check if block number is inside pending block slide let last_finalized_block = self.block_indices.last_finalized_block(); @@ -389,24 +419,7 @@ impl BlockchainTree 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 BlockchainTree 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, - ) -> TreeExternals, Arc, TestExecutorFactory> { + ) -> TreeExternals>, Arc, 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) // | / diff --git a/crates/executor/src/blockchain_tree/post_state_data.rs b/crates/executor/src/blockchain_tree/post_state_data.rs new file mode 100644 index 0000000000..a14bef5b47 --- /dev/null +++ b/crates/executor/src/blockchain_tree/post_state_data.rs @@ -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, + /// The blocks in the canonical chain. + pub canonical_block_hashes: &'a BTreeMap, + /// 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 { + 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, + /// 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 { + self.parent_block_hashed.get(&block_number).cloned() + } + + fn canonical_fork(&self) -> ForkBlock { + self.canonical_fork + } +} diff --git a/crates/executor/src/blockchain_tree/shareable.rs b/crates/executor/src/blockchain_tree/shareable.rs new file mode 100644 index 0000000000..0f46a9cd9d --- /dev/null +++ b/crates/executor/src/blockchain_tree/shareable.rs @@ -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 { + /// BlockchainTree + pub tree: Arc>>, +} + +impl ShareableBlockchainTree { + /// Create New sharable database. + pub fn new(tree: BlockchainTree) -> Self { + Self { tree: Arc::new(RwLock::new(tree)) } + } +} + +impl BlockchainTreeEngine + for ShareableBlockchainTree +{ + fn insert_block_with_senders( + &self, + block: SealedBlockWithSenders, + ) -> Result { + 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 BlockchainTreeViewer + for ShareableBlockchainTree +{ + fn pending_blocks(&self) -> BTreeMap> { + self.tree.read().block_indices().index_of_number_to_pending_blocks().clone() + } + + fn canonical_blocks(&self) -> BTreeMap { + self.tree.read().block_indices().canonical_chain().clone() + } + + fn canonical_tip(&self) -> BlockNumHash { + self.tree.read().canonical_tip() + } +} + +impl BlockchainTreePendingStateProvider + for ShareableBlockchainTree +{ + fn pending_state_provider( + &self, + block_hash: BlockHash, + ) -> Result, Error> { + let Some(post_state) = self.tree.read().post_state_data(block_hash) else { panic!("")}; + Ok(Box::new(post_state)) + } +} diff --git a/crates/executor/src/lib.rs b/crates/executor/src/lib.rs index fbdb1cc173..7aa05d0574 100644 --- a/crates/executor/src/lib.rs +++ b/crates/executor/src/lib.rs @@ -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; diff --git a/crates/executor/src/substate.rs b/crates/executor/src/substate.rs deleted file mode 100644 index b11a39f03f..0000000000 --- a/crates/executor/src/substate.rs +++ /dev/null @@ -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, - /// The blocks in the canonical chain. - canonical_block_hashes: &'a BTreeMap, -} - -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, - canonical_block_hashes: &'a BTreeMap, - ) -> 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> { - 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> { - unimplemented!() - } -} - -impl<'a, SP: StateProvider> AccountProvider for PostStateProvider<'a, SP> { - fn basic_account(&self, address: Address) -> Result> { - 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> { - 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> { - 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, H256, Vec>)> { - Err(ProviderError::HistoryStateRoot.into()) - } -} diff --git a/crates/interfaces/src/blockchain_tree.rs b/crates/interfaces/src/blockchain_tree.rs new file mode 100644 index 0000000000..d3966bdba6 --- /dev/null +++ b/crates/interfaces/src/blockchain_tree.rs @@ -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 { + 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; + + /// 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>; + + /// Canonical block number and hashes best known by the tree. + fn canonical_blocks(&self) -> BTreeMap; + + /// Return BlockchainTree best known canonical chain tip (BlockHash, BlockNumber) + fn canonical_tip(&self) -> BlockNumHash; +} diff --git a/crates/interfaces/src/lib.rs b/crates/interfaces/src/lib.rs index 1a29eff82d..f512f7ff76 100644 --- a/crates/interfaces/src/lib.rs +++ b/crates/interfaces/src/lib.rs @@ -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; diff --git a/crates/primitives/src/block.rs b/crates/primitives/src/block.rs index 2a17046291..730155361d 100644 --- a/crates/primitives/src/block.rs +++ b/crates/primitives/src/block.rs @@ -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 for BlockId { impl From 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)> for BlockId { fn from(hash_can: (H256, Option)) -> 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 for BlockId { - fn from(hash_can: BlockHash) -> Self { +impl From 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 for BlockNumberOrTag { } } -impl From for BlockNumberOrTag { - fn from(num: U64) -> Self { - num.as_u64().into() +impl From 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 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 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. /// #[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, } -impl BlockHash { +impl RpcBlockHash { pub fn from_hash(block_hash: H256, require_canonical: Option) -> Self { - BlockHash { block_hash, require_canonical } + RpcBlockHash { block_hash, require_canonical } } } -impl From for BlockHash { +impl From for RpcBlockHash { fn from(value: H256) -> Self { Self::from_hash(value, None) } } -impl From for H256 { - fn from(value: BlockHash) -> Self { +impl From for H256 { + fn from(value: RpcBlockHash) -> Self { value.block_hash } } -impl AsRef for BlockHash { +impl AsRef 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 } ); diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index a344530c90..81ad1189e3 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -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; diff --git a/crates/rpc/rpc/src/eth/api/mod.rs b/crates/rpc/rpc/src/eth/api/mod.rs index daa40db306..1941e9995b 100644 --- a/crates/rpc/rpc/src/eth/api/mod.rs +++ b/crates/rpc/rpc/src/eth/api/mod.rs @@ -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> { + pub fn state_at_block_id(&self, at: BlockId) -> EthResult> { 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, - ) -> EthResult> { + ) -> EthResult> { 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>> { + pub fn state_at_block_number( + &self, + num: BlockNumberOrTag, + ) -> Result>> { 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<::HistorySP<'_>> { + pub fn state_at_hash(&self, block_hash: H256) -> Result> { 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> { + pub fn state_at_number(&self, block_number: u64) -> Result> { 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<::LatestSP<'_>> { + pub fn latest_state(&self) -> Result> { self.client().latest() } } diff --git a/crates/rpc/rpc/src/eth/api/transactions.rs b/crates/rpc/rpc/src/eth/api/transactions.rs index fe4c342aae..fd80a1b7d6 100644 --- a/crates/rpc/rpc/src/eth/api/transactions.rs +++ b/crates/rpc/rpc/src/eth/api/transactions.rs @@ -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>; + fn state_at(&self, at: BlockId) -> EthResult>; /// Executes the closure with the state that corresponds to the given [BlockId]. fn with_state_at(&self, _at: BlockId, _f: F) -> EthResult where - F: FnOnce(ChainState<'_>) -> EthResult; + F: FnOnce(StateProviderBox<'_>) -> EthResult; /// 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> { + fn state_at(&self, at: BlockId) -> EthResult> { self.state_at_block_id(at) } fn with_state_at(&self, at: BlockId, f: F) -> EthResult where - F: FnOnce(ChainState<'_>) -> EthResult, + F: FnOnce(StateProviderBox<'_>) -> EthResult, { let state = self.state_at(at)?; f(state) diff --git a/crates/stages/src/test_utils/test_db.rs b/crates/stages/src/test_utils/test_db.rs index 3d0f299f89..8b36d65257 100644 --- a/crates/stages/src/test_utils/test_db.rs +++ b/crates/stages/src/test_utils/test_db.rs @@ -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}, diff --git a/crates/storage/db/src/abstraction/table.rs b/crates/storage/db/src/abstraction/table.rs index 202ee6b58a..bf9da9f5a4 100644 --- a/crates/storage/db/src/abstraction/table.rs +++ b/crates/storage/db/src/abstraction/table.rs @@ -56,7 +56,7 @@ impl 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. diff --git a/crates/storage/db/src/tables/codecs/fuzz/mod.rs b/crates/storage/db/src/tables/codecs/fuzz/mod.rs index e46345742d..8c89155a7f 100644 --- a/crates/storage/db/src/tables/codecs/fuzz/mod.rs +++ b/crates/storage/db/src/tables/codecs/fuzz/mod.rs @@ -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)); diff --git a/crates/storage/db/src/tables/models/blocks.rs b/crates/storage/db/src/tables/models/blocks.rs index 9d2e48b938..988f6244c3 100644 --- a/crates/storage/db/src/tables/models/blocks.rs +++ b/crates/storage/db/src/tables/models/blocks.rs @@ -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 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>(value: B) -> Result { - 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() { diff --git a/crates/storage/provider/src/lib.rs b/crates/storage/provider/src/lib.rs index 6acc130603..05123fb3fe 100644 --- a/crates/storage/provider/src/lib.rs +++ b/crates/storage/provider/src/lib.rs @@ -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; diff --git a/crates/storage/provider/src/providers/mod.rs b/crates/storage/provider/src/providers/mod.rs index e34d2456a0..9db82f73b3 100644 --- a/crates/storage/provider/src/providers/mod.rs +++ b/crates/storage/provider/src/providers/mod.rs @@ -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 EvmEnvProvider for ShareableDatabase { } impl StateProviderFactory for ShareableDatabase { - type HistorySP<'a> = HistoricalStateProvider<'a,>::TX> where Self: 'a; - type LatestSP<'a> = LatestStateProvider<'a,>::TX> where Self: 'a; - /// Storage provider for latest block - fn latest(&self) -> Result> { - Ok(LatestStateProvider::new(self.db.tx()?)) + fn latest(&self) -> Result> { + Ok(Box::new(LatestStateProvider::new(self.db.tx()?))) } - fn history_by_block_number(&self, block_number: BlockNumber) -> Result> { + fn history_by_block_number(&self, block_number: BlockNumber) -> Result> { let tx = self.db.tx()?; // get transition id @@ -423,10 +418,10 @@ impl StateProviderFactory for ShareableDatabase { .get::(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> { + fn history_by_block_hash(&self, block_hash: BlockHash) -> Result> { let tx = self.db.tx()?; // get block number let block_number = tx @@ -438,7 +433,18 @@ impl StateProviderFactory for ShareableDatabase { .get::(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, + ) -> Result> { + 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)) } } diff --git a/crates/storage/provider/src/providers/post_state_provider.rs b/crates/storage/provider/src/providers/post_state_provider.rs new file mode 100644 index 0000000000..033f6ae8e3 --- /dev/null +++ b/crates/storage/provider/src/providers/post_state_provider.rs @@ -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 { + /// The inner state provider. + pub state_provider: SP, + /// Post state data, + pub post_state_data_provider: PSDP, +} + +impl PostStateProvider { + /// 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 BlockHashProvider + for PostStateProvider +{ + fn block_hash(&self, block_number: BlockNumber) -> Result> { + 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> { + unimplemented!() + } +} + +impl AccountProvider + for PostStateProvider +{ + fn basic_account(&self, address: Address) -> Result> { + if let Some(account) = self.post_state_data_provider.state().account(&address) { + Ok(*account) + } else { + self.state_provider.basic_account(address) + } + } +} + +impl StateProvider for PostStateProvider { + fn storage( + &self, + account: Address, + storage_key: reth_primitives::StorageKey, + ) -> Result> { + 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> { + 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, H256, Vec>)> { + Err(ProviderError::HistoryStateRoot.into()) + } +} diff --git a/crates/storage/provider/src/providers/state/chain.rs b/crates/storage/provider/src/providers/state/chain.rs deleted file mode 100644 index 697d6b1fe8..0000000000 --- a/crates/storage/provider/src/providers/state/chain.rs +++ /dev/null @@ -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, -} - -// == impl ChainState === - -impl<'a> ChainState<'a> { - /// Wraps the given [StateProvider] - pub fn boxed(inner: S) -> Self { - Self::new(Box::new(inner)) - } - - /// Wraps the given [StateProvider] - pub fn new(inner: Box) -> 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() {} - #[allow(unused)] - #[allow(clippy::extra_unused_lifetimes)] - fn assert_chain_state_provider<'txn>() { - assert_state_provider::>(); - } -} diff --git a/crates/storage/provider/src/providers/state/mod.rs b/crates/storage/provider/src/providers/state/mod.rs index c6118d1aeb..f045236470 100644 --- a/crates/storage/provider/src/providers/state/mod.rs +++ b/crates/storage/provider/src/providers/state/mod.rs @@ -1,5 +1,4 @@ //! [StateProvider](crate::StateProvider) implementations -pub(crate) mod chain; pub(crate) mod historical; pub(crate) mod latest; pub(crate) mod macros; diff --git a/crates/storage/provider/src/test_utils/mock.rs b/crates/storage/provider/src/test_utils/mock.rs index 2c4f74386a..058b2fc63c 100644 --- a/crates/storage/provider/src/test_utils/mock.rs +++ b/crates/storage/provider/src/test_utils/mock.rs @@ -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> { - Ok(self) + fn latest(&self) -> Result> { + Ok(Box::new(self.clone())) } fn history_by_block_number( &self, _block: reth_primitives::BlockNumber, - ) -> Result> { + ) -> Result> { todo!() } - fn history_by_block_hash(&self, _block: BlockHash) -> Result> { + fn history_by_block_hash(&self, _block: BlockHash) -> Result> { + todo!() + } + + fn pending<'a>( + &'a self, + _post_state_data: Box, + ) -> Result> { todo!() } } impl StateProviderFactory for Arc { - type HistorySP<'a> = &'a MockEthProvider where Self: 'a; - type LatestSP<'a> = &'a MockEthProvider where Self: 'a; - - fn latest(&self) -> Result> { - Ok(self) + fn latest(&self) -> Result> { + Ok(Box::new(self.clone())) } fn history_by_block_number( &self, _block: reth_primitives::BlockNumber, - ) -> Result> { + ) -> Result> { todo!() } - fn history_by_block_hash(&self, _block: BlockHash) -> Result> { + fn history_by_block_hash(&self, _block: BlockHash) -> Result> { + todo!() + } + + fn pending<'a>( + &'a self, + _post_state_data: Box, + ) -> Result> { todo!() } } diff --git a/crates/storage/provider/src/test_utils/noop.rs b/crates/storage/provider/src/test_utils/noop.rs index 9f70f78124..d61f37c269 100644 --- a/crates/storage/provider/src/test_utils/noop.rs +++ b/crates/storage/provider/src/test_utils/noop.rs @@ -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> { - Ok(*self) + fn latest(&self) -> Result> { + Ok(Box::new(*self)) } - fn history_by_block_number(&self, _block: BlockNumber) -> Result> { - Ok(*self) + fn history_by_block_number(&self, _block: BlockNumber) -> Result> { + Ok(Box::new(*self)) } - fn history_by_block_hash(&self, _block: BlockHash) -> Result> { - Ok(*self) + fn history_by_block_hash(&self, _block: BlockHash) -> Result> { + Ok(Box::new(*self)) + } + + fn pending<'a>( + &'a self, + _post_state_data: Box, + ) -> Result> { + Ok(Box::new(*self)) } } diff --git a/crates/storage/provider/src/traits/account.rs b/crates/storage/provider/src/traits/account.rs index fb2ba3943b..a62b3e8f1a 100644 --- a/crates/storage/provider/src/traits/account.rs +++ b/crates/storage/provider/src/traits/account.rs @@ -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>; diff --git a/crates/storage/provider/src/traits/mod.rs b/crates/storage/provider/src/traits/mod.rs index 93b3ae303c..7f10ae4acf 100644 --- a/crates/storage/provider/src/traits/mod.rs +++ b/crates/storage/provider/src/traits/mod.rs @@ -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; diff --git a/crates/storage/provider/src/traits/state.rs b/crates/storage/provider/src/traits/state.rs index e2f502593c..43449dc205 100644 --- a/crates/storage/provider/src/traits/state.rs +++ b/crates/storage/provider/src/traits/state.rs @@ -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; + /// 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>; @@ -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>; + fn latest(&self) -> Result>; /// Returns a [StateProvider] indexed by the given block number. - fn history_by_block_number(&self, block: BlockNumber) -> Result>; + fn history_by_block_number(&self, block: BlockNumber) -> Result>; /// Returns a [StateProvider] indexed by the given block hash. - fn history_by_block_hash(&self, block: BlockHash) -> Result>; + fn history_by_block_hash(&self, block: BlockHash) -> Result>; + + /// 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, + ) -> Result>; +} + +/// Blockchain trait provider +pub trait BlockchainTreePendingStateProvider: Send + Sync { + /// Return state provider over pending state. + fn pending_state_provider( + &self, + block_hash: BlockHash, + ) -> Result>; +} + +/// 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; + /// return canonical fork, the block on what post state was forked from. + /// + /// Needed to create state provider. + fn canonical_fork(&self) -> BlockNumHash; }