mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
feat(BlockchainTree): ShareableBlockchainTree and pending state (#2007)
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -4705,7 +4705,6 @@ name = "reth-executor"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"aquamarine",
|
||||
"async-trait",
|
||||
"auto_impl",
|
||||
"hash-db",
|
||||
"parking_lot 0.12.1",
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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" }
|
||||
|
||||
|
||||
@ -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();
|
||||
(
|
||||
|
||||
@ -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 = []
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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)),
|
||||
|
||||
@ -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 }
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
// | /
|
||||
|
||||
65
crates/executor/src/blockchain_tree/post_state_data.rs
Normal file
65
crates/executor/src/blockchain_tree/post_state_data.rs
Normal 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
|
||||
}
|
||||
}
|
||||
84
crates/executor/src/blockchain_tree/shareable.rs
Normal file
84
crates/executor/src/blockchain_tree/shareable.rs
Normal 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))
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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())
|
||||
}
|
||||
}
|
||||
83
crates/interfaces/src/blockchain_tree.rs
Normal file
83
crates/interfaces/src/blockchain_tree.rs
Normal 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;
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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 }
|
||||
);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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},
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
86
crates/storage/provider/src/providers/post_state_provider.rs
Normal file
86
crates/storage/provider/src/providers/post_state_provider.rs
Normal 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())
|
||||
}
|
||||
}
|
||||
@ -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>>();
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,4 @@
|
||||
//! [StateProvider](crate::StateProvider) implementations
|
||||
pub(crate) mod chain;
|
||||
pub(crate) mod historical;
|
||||
pub(crate) mod latest;
|
||||
pub(crate) mod macros;
|
||||
|
||||
@ -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!()
|
||||
}
|
||||
}
|
||||
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>>;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user