feat: add state crate (#9701)

Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
Federico Gimenez
2024-07-22 15:20:24 +02:00
committed by GitHub
parent 35edcd4ecd
commit 059233327e
15 changed files with 300 additions and 138 deletions

16
Cargo.lock generated
View File

@ -6471,6 +6471,20 @@ dependencies = [
"thiserror",
]
[[package]]
name = "reth-chain-state"
version = "1.0.3"
dependencies = [
"parking_lot 0.12.3",
"rand 0.8.5",
"reth-chainspec",
"reth-execution-types",
"reth-primitives",
"reth-trie",
"revm",
"tokio",
]
[[package]]
name = "reth-chainspec"
version = "1.0.3"
@ -6968,6 +6982,7 @@ dependencies = [
"reth-beacon-consensus",
"reth-blockchain-tree",
"reth-blockchain-tree-api",
"reth-chain-state",
"reth-chainspec",
"reth-consensus",
"reth-db",
@ -8089,6 +8104,7 @@ dependencies = [
"rand 0.8.5",
"rayon",
"reth-blockchain-tree-api",
"reth-chain-state",
"reth-chainspec",
"reth-codecs",
"reth-db",

View File

@ -14,6 +14,7 @@ members = [
"crates/blockchain-tree/",
"crates/blockchain-tree-api/",
"crates/chainspec/",
"crates/chain-state/",
"crates/cli/cli/",
"crates/cli/commands/",
"crates/cli/runner/",
@ -273,6 +274,7 @@ reth-beacon-consensus = { path = "crates/consensus/beacon" }
reth-blockchain-tree = { path = "crates/blockchain-tree" }
reth-blockchain-tree-api = { path = "crates/blockchain-tree-api" }
reth-chainspec = { path = "crates/chainspec" }
reth-chain-state = { path = "crates/chain-state" }
reth-cli = { path = "crates/cli/cli" }
reth-cli-commands = { path = "crates/cli/commands" }
reth-cli-runner = { path = "crates/cli/runner" }

View File

@ -0,0 +1,38 @@
[package]
name = "reth-chain-state"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
description = "Reth state related types and functionality."
[lints]
workspace = true
[dependencies]
# reth
reth-chainspec.workspace = true
reth-execution-types.workspace = true
reth-primitives.workspace = true
reth-trie.workspace = true
revm = { workspace = true, optional = true}
# async
tokio = { workspace = true, features = ["sync", "macros", "rt-multi-thread"] }
# misc
parking_lot.workspace = true
rand = { workspace = true, optional = true }
[dev-dependencies]
rand.workspace = true
revm.workspace = true
[features]
test-utils = [
"rand",
"revm"
]

View File

@ -1,14 +1,16 @@
//! Types for tracking the canonical chain state in memory.
use crate::tree::ExecutedBlock;
use crate::ChainInfoTracker;
use parking_lot::RwLock;
use reth_primitives::{Receipts, SealedHeader, B256};
use reth_provider::providers::ChainInfoTracker;
use std::{collections::HashMap, sync::Arc};
use reth_chainspec::ChainInfo;
use reth_execution_types::ExecutionOutcome;
use reth_primitives::{Address, BlockNumHash, Receipts, SealedBlock, SealedHeader, B256};
use reth_trie::{updates::TrieUpdates, HashedPostState};
use std::{collections::HashMap, sync::Arc, time::Instant};
/// Container type for in memory state data.
#[derive(Debug, Default)]
pub struct InMemoryState {
pub(crate) struct InMemoryState {
blocks: RwLock<HashMap<B256, Arc<BlockState>>>,
numbers: RwLock<HashMap<u64, B256>>,
pending: RwLock<Option<BlockState>>,
@ -97,29 +99,113 @@ impl CanonicalInMemoryState {
Self { inner: Arc::new(inner) }
}
fn state_by_hash(&self, hash: B256) -> Option<Arc<BlockState>> {
/// Returns in memory state corresponding the given hash.
pub fn state_by_hash(&self, hash: B256) -> Option<Arc<BlockState>> {
self.inner.in_memory_state.state_by_hash(hash)
}
fn state_by_number(&self, number: u64) -> Option<Arc<BlockState>> {
/// Returns in memory state corresponding the block number.
pub fn state_by_number(&self, number: u64) -> Option<Arc<BlockState>> {
self.inner.in_memory_state.state_by_number(number)
}
fn head_state(&self) -> Option<Arc<BlockState>> {
/// Returns the in memory head state.
pub fn head_state(&self) -> Option<Arc<BlockState>> {
self.inner.in_memory_state.head_state()
}
fn pending_state(&self) -> Option<Arc<BlockState>> {
/// Returns the in memory pending state.
pub fn pending_state(&self) -> Option<Arc<BlockState>> {
self.inner.in_memory_state.pending_state()
}
/// Returns the in memory pending `BlockNumHash`.
pub fn pending_block_num_hash(&self) -> Option<BlockNumHash> {
self.inner
.in_memory_state
.pending_state()
.map(|state| BlockNumHash { number: state.number(), hash: state.hash() })
}
/// Returns the current `ChainInfo`.
pub fn chain_info(&self) -> ChainInfo {
self.inner.chain_info_tracker.chain_info()
}
/// Returns the latest canonical block number.
pub fn get_canonical_block_number(&self) -> u64 {
self.inner.chain_info_tracker.get_canonical_block_number()
}
/// Returns the `BlockNumHash` of the safe head.
pub fn get_safe_num_hash(&self) -> Option<BlockNumHash> {
self.inner.chain_info_tracker.get_safe_num_hash()
}
/// Returns the `BlockNumHash` of the finalized head.
pub fn get_finalized_num_hash(&self) -> Option<BlockNumHash> {
self.inner.chain_info_tracker.get_finalized_num_hash()
}
/// Hook for new fork choice update.
pub fn on_forkchoice_update_received(&self) {
self.inner.chain_info_tracker.on_forkchoice_update_received();
}
/// Returns the timestamp of the last received update.
pub fn last_received_update_timestamp(&self) -> Option<Instant> {
self.inner.chain_info_tracker.last_forkchoice_update_received_at()
}
/// Hook for transition configuration exchanged.
pub fn on_transition_configuration_exchanged(&self) {
self.inner.chain_info_tracker.on_transition_configuration_exchanged();
}
/// Returns the timepstamp of the last transition configuration exchanged,
pub fn last_exchanged_transition_configuration_timestamp(&self) -> Option<Instant> {
self.inner.chain_info_tracker.last_transition_configuration_exchanged_at()
}
/// Canonical head setter.
pub fn set_canonical_head(&self, header: SealedHeader) {
self.inner.chain_info_tracker.set_canonical_head(header);
}
/// Safe head setter.
pub fn set_safe(&self, header: SealedHeader) {
self.inner.chain_info_tracker.set_safe(header);
}
/// Finalized head setter.
pub fn set_finalized(&self, header: SealedHeader) {
self.inner.chain_info_tracker.set_finalized(header);
}
/// Canonical head getter.
pub fn get_canonical_head(&self) -> SealedHeader {
self.inner.chain_info_tracker.get_canonical_head()
}
/// Finalized header getter.
pub fn get_finalized_header(&self) -> Option<SealedHeader> {
self.inner.chain_info_tracker.get_finalized_header()
}
/// Safe header getter.
pub fn get_safe_header(&self) -> Option<SealedHeader> {
self.inner.chain_info_tracker.get_safe_header()
}
}
/// State after applying the given block.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct BlockState(pub(crate) ExecutedBlock);
pub struct BlockState(pub ExecutedBlock);
#[allow(dead_code)]
impl BlockState {
pub(crate) const fn new(executed_block: ExecutedBlock) -> Self {
/// `BlockState` constructor.
pub const fn new(executed_block: ExecutedBlock) -> Self {
Self(executed_block)
}
@ -144,6 +230,59 @@ impl BlockState {
}
}
/// Represents an executed block stored in-memory.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ExecutedBlock {
/// Sealed block the rest of fields refer to.
pub block: Arc<SealedBlock>,
/// Block's senders.
pub senders: Arc<Vec<Address>>,
/// Block's execution outcome.
pub execution_output: Arc<ExecutionOutcome>,
/// Block's hashedst state.
pub hashed_state: Arc<HashedPostState>,
/// Trie updates that result of applying the block.
pub trie: Arc<TrieUpdates>,
}
impl ExecutedBlock {
/// `ExecutedBlock` constructor.
pub const fn new(
block: Arc<SealedBlock>,
senders: Arc<Vec<Address>>,
execution_output: Arc<ExecutionOutcome>,
hashed_state: Arc<HashedPostState>,
trie: Arc<TrieUpdates>,
) -> Self {
Self { block, senders, execution_output, hashed_state, trie }
}
/// Returns a reference to the executed block.
pub fn block(&self) -> &SealedBlock {
&self.block
}
/// Returns a reference to the block's senders
pub fn senders(&self) -> &Vec<Address> {
&self.senders
}
/// Returns a reference to the block's execution outcome
pub fn execution_outcome(&self) -> &ExecutionOutcome {
&self.execution_output
}
/// Returns a reference to the hashed state result of the execution outcome
pub fn hashed_state(&self) -> &HashedPostState {
&self.hashed_state
}
/// Returns a reference to the trie updates for the block
pub fn trie_updates(&self) -> &TrieUpdates {
&self.trie
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -0,0 +1,19 @@
//! Reth state related types and functionality.
#![doc(
html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png",
html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256",
issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/"
)]
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
mod in_memory;
pub use in_memory::*;
mod chain_info;
pub use chain_info::ChainInfoTracker;
#[cfg(any(test, feature = "test-utils"))]
/// Common test helpers
pub mod test_utils;

View File

@ -0,0 +1,52 @@
use crate::in_memory::ExecutedBlock;
use rand::Rng;
use reth_execution_types::ExecutionOutcome;
use reth_primitives::{
Address, Block, BlockNumber, Receipts, Requests, SealedBlockWithSenders, TransactionSigned,
};
use reth_trie::{updates::TrieUpdates, HashedPostState};
use revm::db::BundleState;
use std::{ops::Range, sync::Arc};
fn get_executed_block(block_number: BlockNumber, receipts: Receipts) -> ExecutedBlock {
let mut block = Block::default();
let mut header = block.header.clone();
header.number = block_number;
block.header = header;
let sender = Address::random();
let tx = TransactionSigned::default();
block.body.push(tx);
let sealed = block.seal_slow();
let sealed_with_senders = SealedBlockWithSenders::new(sealed.clone(), vec![sender]).unwrap();
ExecutedBlock::new(
Arc::new(sealed),
Arc::new(sealed_with_senders.senders),
Arc::new(ExecutionOutcome::new(
BundleState::default(),
receipts,
block_number,
vec![Requests::default()],
)),
Arc::new(HashedPostState::default()),
Arc::new(TrieUpdates::default()),
)
}
/// Generates an `ExecutedBlock` that includes the given `Receipts`.
pub fn get_executed_block_with_receipts(receipts: Receipts) -> ExecutedBlock {
let number = rand::thread_rng().gen::<u64>();
get_executed_block(number, receipts)
}
/// Generates an `ExecutedBlock` with the given `BlockNumber`.
pub fn get_executed_block_with_number(block_number: BlockNumber) -> ExecutedBlock {
get_executed_block(block_number, Receipts { receipt_vec: vec![vec![]] })
}
/// Generates a range of executed blocks with ascending block numbers.
pub fn get_executed_blocks(range: Range<u64>) -> impl Iterator<Item = ExecutedBlock> {
range.map(get_executed_block_with_number)
}

View File

@ -16,6 +16,7 @@ reth-beacon-consensus.workspace = true
reth-blockchain-tree.workspace = true
reth-blockchain-tree-api.workspace = true
reth-chainspec.workspace = true
reth-chain-state.workspace = true
reth-consensus.workspace = true
reth-db.workspace = true
reth-db-api.workspace = true
@ -33,8 +34,8 @@ reth-prune.workspace = true
reth-prune-types.workspace = true
reth-revm.workspace = true
reth-rpc-types.workspace = true
reth-stages-types.workspace = true
reth-stages-api.workspace = true
reth-stages-types.workspace = true
reth-static-file.workspace = true
reth-tasks.workspace = true
reth-tokio-util.workspace = true
@ -71,6 +72,7 @@ reth-network-p2p = { workspace = true, features = ["test-utils"] }
reth-prune.workspace = true
reth-prune-types.workspace = true
reth-stages = { workspace = true, features = ["test-utils"] }
reth-chain-state = { workspace = true, features = ["test-utils"] }
reth-tracing.workspace = true
assert_matches.workspace = true
@ -81,6 +83,7 @@ test-utils = [
"reth-db/test-utils",
"reth-network-p2p/test-utils",
"reth-stages/test-utils",
"reth-chain-state/test-utils",
"reth-tracing",
"rand"
]

View File

@ -1,9 +1,7 @@
#![allow(dead_code)]
use crate::{
static_files::{StaticFileAction, StaticFileServiceHandle},
tree::ExecutedBlock,
};
use crate::static_files::{StaticFileAction, StaticFileServiceHandle};
use reth_chain_state::ExecutedBlock;
use reth_db::database::Database;
use reth_errors::ProviderResult;
use reth_primitives::B256;

View File

@ -3,8 +3,8 @@
use crate::{
database::{DatabaseAction, DatabaseService, DatabaseServiceHandle},
static_files::{StaticFileAction, StaticFileService, StaticFileServiceHandle},
tree::ExecutedBlock,
};
use reth_chain_state::ExecutedBlock;
use reth_db::Database;
use reth_primitives::{SealedBlock, B256, U256};
use reth_provider::ProviderFactory;
@ -181,7 +181,7 @@ impl PersistenceHandle {
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::{get_executed_block_with_number, get_executed_blocks};
use reth_chain_state::test_utils::{get_executed_block_with_number, get_executed_blocks};
use reth_exex_types::FinishedExExHeight;
use reth_primitives::B256;
use reth_provider::{test_utils::create_test_provider_factory, ProviderFactory};

View File

@ -1,5 +1,7 @@
#![allow(dead_code)]
use crate::database::{DatabaseAction, DatabaseServiceHandle};
use reth_chain_state::ExecutedBlock;
use reth_db::database::Database;
use reth_errors::ProviderResult;
use reth_primitives::{SealedBlock, StaticFileSegment, TransactionSignedNoHash, B256, U256};
@ -12,11 +14,6 @@ use std::sync::{
};
use tokio::sync::oneshot;
use crate::{
database::{DatabaseAction, DatabaseServiceHandle},
tree::ExecutedBlock,
};
/// Writes finalized blocks to reth's static files.
///
/// This is meant to be a spawned service that listens for various incoming finalization operations,

View File

@ -1,19 +1,12 @@
use crate::tree::ExecutedBlock;
use rand::Rng;
use reth_chainspec::ChainSpec;
use reth_db::{mdbx::DatabaseEnv, test_utils::TempDatabase};
use reth_network_p2p::test_utils::TestFullBlockClient;
use reth_primitives::{
Address, Block, BlockBody, BlockNumber, Receipts, Requests, SealedBlockWithSenders,
SealedHeader, TransactionSigned, B256,
};
use reth_primitives::{BlockBody, SealedHeader, B256};
use reth_provider::{test_utils::create_test_provider_factory_with_chain_spec, ExecutionOutcome};
use reth_prune_types::PruneModes;
use reth_stages::{test_utils::TestStages, ExecOutput, StageError};
use reth_stages_api::Pipeline;
use reth_static_file::StaticFileProducer;
use reth_trie::{updates::TrieUpdates, HashedPostState};
use revm::db::BundleState;
use std::{collections::VecDeque, ops::Range, sync::Arc};
use tokio::sync::watch;
@ -82,43 +75,3 @@ pub(crate) fn insert_headers_into_client(
client.insert(sealed_header.clone(), body.clone());
}
}
fn get_executed_block(block_number: BlockNumber, receipts: Receipts) -> ExecutedBlock {
let mut block = Block::default();
let mut header = block.header.clone();
header.number = block_number;
block.header = header;
let sender = Address::random();
let tx = TransactionSigned::default();
block.body.push(tx);
let sealed = block.seal_slow();
let sealed_with_senders = SealedBlockWithSenders::new(sealed.clone(), vec![sender]).unwrap();
ExecutedBlock::new(
Arc::new(sealed),
Arc::new(sealed_with_senders.senders),
Arc::new(ExecutionOutcome::new(
BundleState::default(),
receipts,
block_number,
vec![Requests::default()],
)),
Arc::new(HashedPostState::default()),
Arc::new(TrieUpdates::default()),
)
}
pub(crate) fn get_executed_block_with_receipts(receipts: Receipts) -> ExecutedBlock {
let number = rand::thread_rng().gen::<u64>();
get_executed_block(number, receipts)
}
pub(crate) fn get_executed_block_with_number(block_number: BlockNumber) -> ExecutedBlock {
get_executed_block(block_number, Receipts { receipt_vec: vec![vec![]] })
}
pub(crate) fn get_executed_blocks(range: Range<u64>) -> impl Iterator<Item = ExecutedBlock> {
range.map(get_executed_block_with_number)
}

View File

@ -13,6 +13,7 @@ use reth_blockchain_tree::{
error::InsertBlockErrorKind, BlockAttachment, BlockBuffer, BlockStatus,
};
use reth_blockchain_tree_api::{error::InsertBlockError, InsertPayloadOk};
use reth_chain_state::{BlockState, CanonicalInMemoryState, ExecutedBlock};
use reth_consensus::{Consensus, PostExecutionInput};
use reth_engine_primitives::EngineTypes;
use reth_errors::{ConsensusError, ProviderResult};
@ -20,7 +21,7 @@ use reth_evm::execute::{BlockExecutorProvider, Executor};
use reth_payload_primitives::PayloadTypes;
use reth_payload_validator::ExecutionPayloadValidator;
use reth_primitives::{
Address, Block, BlockNumHash, BlockNumber, GotExpected, Receipts, Requests, SealedBlock,
Block, BlockNumHash, BlockNumber, GotExpected, Receipts, Requests, SealedBlock,
SealedBlockWithSenders, SealedHeader, B256, U256,
};
use reth_provider::{
@ -34,8 +35,7 @@ use reth_rpc_types::{
},
ExecutionPayload,
};
use reth_trie::{updates::TrieUpdates, HashedPostState};
pub use state::{BlockState, CanonicalInMemoryState, InMemoryState};
use reth_trie::HashedPostState;
use std::{
collections::{BTreeMap, HashMap, HashSet},
marker::PhantomData,
@ -48,7 +48,7 @@ use tokio::sync::{
use tracing::*;
mod memory_overlay;
mod state;
/// Maximum number of blocks to be kept only in memory without triggering persistence.
const PERSISTENCE_THRESHOLD: u64 = 256;
/// Number of pending blocks that cannot be executed due to missing parent and
@ -57,53 +57,6 @@ const DEFAULT_BLOCK_BUFFER_LIMIT: u32 = 256;
/// Number of invalid headers to keep in cache.
const DEFAULT_MAX_INVALID_HEADER_CACHE_LENGTH: u32 = 256;
/// Represents an executed block stored in-memory.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ExecutedBlock {
pub(crate) block: Arc<SealedBlock>,
pub(crate) senders: Arc<Vec<Address>>,
pub(crate) execution_output: Arc<ExecutionOutcome>,
pub(crate) hashed_state: Arc<HashedPostState>,
pub(crate) trie: Arc<TrieUpdates>,
}
impl ExecutedBlock {
pub(crate) const fn new(
block: Arc<SealedBlock>,
senders: Arc<Vec<Address>>,
execution_output: Arc<ExecutionOutcome>,
hashed_state: Arc<HashedPostState>,
trie: Arc<TrieUpdates>,
) -> Self {
Self { block, senders, execution_output, hashed_state, trie }
}
/// Returns a reference to the executed block.
pub(crate) fn block(&self) -> &SealedBlock {
&self.block
}
/// Returns a reference to the block's senders
pub(crate) fn senders(&self) -> &Vec<Address> {
&self.senders
}
/// Returns a reference to the block's execution outcome
pub(crate) fn execution_outcome(&self) -> &ExecutionOutcome {
&self.execution_output
}
/// Returns a reference to the hashed state result of the execution outcome
pub(crate) fn hashed_state(&self) -> &HashedPostState {
&self.hashed_state
}
/// Returns a reference to the trie updates for the block
pub(crate) fn trie_updates(&self) -> &TrieUpdates {
&self.trie
}
}
/// Keeps track of the state of the tree.
#[derive(Debug, Default)]
pub struct TreeState {
@ -1164,8 +1117,9 @@ impl PersistenceState {
#[cfg(test)]
mod tests {
use super::*;
use crate::{static_files::StaticFileAction, test_utils::get_executed_blocks};
use crate::static_files::StaticFileAction;
use reth_beacon_consensus::EthBeaconConsensus;
use reth_chain_state::test_utils::get_executed_blocks;
use reth_chainspec::{ChainSpecBuilder, MAINNET};
use reth_ethereum_engine_primitives::EthEngineTypes;
use reth_evm::test_utils::MockExecutorProvider;
@ -1281,20 +1235,12 @@ mod tests {
let expected_state = BlockState::new(executed_block.clone());
let actual_state_by_hash = tree
.canonical_in_memory_state
.inner
.in_memory_state
.state_by_hash(sealed_block.hash())
.unwrap();
let actual_state_by_hash =
tree.canonical_in_memory_state.state_by_hash(sealed_block.hash()).unwrap();
assert_eq!(expected_state, *actual_state_by_hash);
let actual_state_by_number = tree
.canonical_in_memory_state
.inner
.in_memory_state
.state_by_number(sealed_block.number)
.unwrap();
let actual_state_by_number =
tree.canonical_in_memory_state.state_by_number(sealed_block.number).unwrap();
assert_eq!(expected_state, *actual_state_by_number);
}
}

View File

@ -31,6 +31,7 @@ reth-trie-db = { workspace = true, features = ["metrics"] }
reth-nippy-jar.workspace = true
reth-codecs.workspace = true
reth-evm.workspace = true
reth-chain-state.workspace = true
# ethereum
alloy-rpc-types-engine.workspace = true

View File

@ -12,6 +12,7 @@ use reth_blockchain_tree_api::{
BlockValidationKind, BlockchainTreeEngine, BlockchainTreeViewer, CanonicalOutcome,
InsertPayloadOk,
};
use reth_chain_state::ChainInfoTracker;
use reth_chainspec::{ChainInfo, ChainSpec};
use reth_db_api::{
database::Database,
@ -54,9 +55,6 @@ pub use state::{
mod bundle_state_provider;
pub use bundle_state_provider::BundleStateProvider;
mod chain_info;
pub use chain_info::ChainInfoTracker;
mod consistent_view;
use alloy_rpc_types_engine::ForkchoiceState;
pub use consistent_view::{ConsistentDbView, ConsistentViewError};