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

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

@@ -1,292 +0,0 @@
//! Types for tracking the canonical chain state in memory.
use crate::tree::ExecutedBlock;
use parking_lot::RwLock;
use reth_primitives::{Receipts, SealedHeader, B256};
use reth_provider::providers::ChainInfoTracker;
use std::{collections::HashMap, sync::Arc};
/// Container type for in memory state data.
#[derive(Debug, Default)]
pub struct InMemoryState {
blocks: RwLock<HashMap<B256, Arc<BlockState>>>,
numbers: RwLock<HashMap<u64, B256>>,
pending: RwLock<Option<BlockState>>,
}
impl InMemoryState {
pub(crate) const fn new(
blocks: HashMap<B256, Arc<BlockState>>,
numbers: HashMap<u64, B256>,
pending: Option<BlockState>,
) -> Self {
Self {
blocks: RwLock::new(blocks),
numbers: RwLock::new(numbers),
pending: RwLock::new(pending),
}
}
/// Returns the state for a given block hash.
pub(crate) fn state_by_hash(&self, hash: B256) -> Option<Arc<BlockState>> {
self.blocks.read().get(&hash).cloned()
}
/// Returns the state for a given block number.
pub(crate) fn state_by_number(&self, number: u64) -> Option<Arc<BlockState>> {
self.numbers.read().get(&number).and_then(|hash| self.blocks.read().get(hash).cloned())
}
/// Returns the current chain head state.
pub(crate) fn head_state(&self) -> Option<Arc<BlockState>> {
self.numbers
.read()
.iter()
.max_by_key(|(&number, _)| number)
.and_then(|(_, hash)| self.blocks.read().get(hash).cloned())
}
/// Returns the pending state corresponding to the current head plus one,
/// from the payload received in newPayload that does not have a FCU yet.
pub(crate) fn pending_state(&self) -> Option<Arc<BlockState>> {
self.pending.read().as_ref().map(|state| Arc::new(BlockState(state.0.clone())))
}
}
/// Inner type to provide in memory state. It includes a chain tracker to be
/// advanced internally by the tree.
#[derive(Debug)]
pub(crate) struct CanonicalInMemoryStateInner {
pub(crate) chain_info_tracker: ChainInfoTracker,
pub(crate) in_memory_state: InMemoryState,
}
/// This type is responsible for providing the blocks, receipts, and state for
/// all canonical blocks not on disk yet and keeps track of the block range that
/// is in memory.
#[derive(Debug, Clone)]
pub struct CanonicalInMemoryState {
pub(crate) inner: Arc<CanonicalInMemoryStateInner>,
}
impl CanonicalInMemoryState {
/// Create a new in memory state with the given blocks, numbers, and pending state.
pub fn new(
blocks: HashMap<B256, Arc<BlockState>>,
numbers: HashMap<u64, B256>,
pending: Option<BlockState>,
) -> Self {
let in_memory_state = InMemoryState::new(blocks, numbers, pending);
let head_state = in_memory_state.head_state();
let header = match head_state {
Some(state) => state.block().block().header.clone(),
None => SealedHeader::default(),
};
let chain_info_tracker = ChainInfoTracker::new(header);
let inner = CanonicalInMemoryStateInner { chain_info_tracker, in_memory_state };
Self { inner: Arc::new(inner) }
}
/// Create a new in memory state with the given local head.
pub fn with_head(head: SealedHeader) -> Self {
let chain_info_tracker = ChainInfoTracker::new(head);
let in_memory_state = InMemoryState::default();
let inner = CanonicalInMemoryStateInner { chain_info_tracker, in_memory_state };
Self { inner: Arc::new(inner) }
}
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>> {
self.inner.in_memory_state.state_by_number(number)
}
fn head_state(&self) -> Option<Arc<BlockState>> {
self.inner.in_memory_state.head_state()
}
fn pending_state(&self) -> Option<Arc<BlockState>> {
self.inner.in_memory_state.pending_state()
}
}
/// State after applying the given block.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct BlockState(pub(crate) ExecutedBlock);
impl BlockState {
pub(crate) const fn new(executed_block: ExecutedBlock) -> Self {
Self(executed_block)
}
pub(crate) fn block(&self) -> ExecutedBlock {
self.0.clone()
}
pub(crate) fn hash(&self) -> B256 {
self.0.block().hash()
}
pub(crate) fn number(&self) -> u64 {
self.0.block().number
}
pub(crate) fn state_root(&self) -> B256 {
self.0.block().header.state_root
}
pub(crate) fn receipts(&self) -> &Receipts {
&self.0.execution_outcome().receipts
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::{get_executed_block_with_number, get_executed_block_with_receipts};
use rand::Rng;
use reth_primitives::Receipt;
fn create_mock_state(block_number: u64) -> BlockState {
BlockState::new(get_executed_block_with_number(block_number))
}
#[tokio::test]
async fn test_in_memory_state_impl_state_by_hash() {
let mut state_by_hash = HashMap::new();
let number = rand::thread_rng().gen::<u64>();
let state = Arc::new(create_mock_state(number));
state_by_hash.insert(state.hash(), state.clone());
let in_memory_state = InMemoryState::new(state_by_hash, HashMap::new(), None);
assert_eq!(in_memory_state.state_by_hash(state.hash()), Some(state));
assert_eq!(in_memory_state.state_by_hash(B256::random()), None);
}
#[tokio::test]
async fn test_in_memory_state_impl_state_by_number() {
let mut state_by_hash = HashMap::new();
let mut hash_by_number = HashMap::new();
let number = rand::thread_rng().gen::<u64>();
let state = Arc::new(create_mock_state(number));
let hash = state.hash();
state_by_hash.insert(hash, state.clone());
hash_by_number.insert(number, hash);
let in_memory_state = InMemoryState::new(state_by_hash, hash_by_number, None);
assert_eq!(in_memory_state.state_by_number(number), Some(state));
assert_eq!(in_memory_state.state_by_number(number + 1), None);
}
#[tokio::test]
async fn test_in_memory_state_impl_head_state() {
let mut state_by_hash = HashMap::new();
let mut hash_by_number = HashMap::new();
let state1 = Arc::new(create_mock_state(1));
let state2 = Arc::new(create_mock_state(2));
let hash1 = state1.hash();
let hash2 = state2.hash();
hash_by_number.insert(1, hash1);
hash_by_number.insert(2, hash2);
state_by_hash.insert(hash1, state1);
state_by_hash.insert(hash2, state2);
let in_memory_state = InMemoryState::new(state_by_hash, hash_by_number, None);
let head_state = in_memory_state.head_state().unwrap();
assert_eq!(head_state.hash(), hash2);
assert_eq!(head_state.number(), 2);
}
#[tokio::test]
async fn test_in_memory_state_impl_pending_state() {
let pending_number = rand::thread_rng().gen::<u64>();
let pending_state = create_mock_state(pending_number);
let pending_hash = pending_state.hash();
let in_memory_state =
InMemoryState::new(HashMap::new(), HashMap::new(), Some(pending_state));
let result = in_memory_state.pending_state();
assert!(result.is_some());
let actual_pending_state = result.unwrap();
assert_eq!(actual_pending_state.0.block().hash(), pending_hash);
assert_eq!(actual_pending_state.0.block().number, pending_number);
}
#[tokio::test]
async fn test_in_memory_state_impl_no_pending_state() {
let in_memory_state = InMemoryState::new(HashMap::new(), HashMap::new(), None);
assert_eq!(in_memory_state.pending_state(), None);
}
#[tokio::test]
async fn test_state_new() {
let number = rand::thread_rng().gen::<u64>();
let block = get_executed_block_with_number(number);
let state = BlockState::new(block.clone());
assert_eq!(state.block(), block);
}
#[tokio::test]
async fn test_state_block() {
let number = rand::thread_rng().gen::<u64>();
let block = get_executed_block_with_number(number);
let state = BlockState::new(block.clone());
assert_eq!(state.block(), block);
}
#[tokio::test]
async fn test_state_hash() {
let number = rand::thread_rng().gen::<u64>();
let block = get_executed_block_with_number(number);
let state = BlockState::new(block.clone());
assert_eq!(state.hash(), block.block().hash());
}
#[tokio::test]
async fn test_state_number() {
let number = rand::thread_rng().gen::<u64>();
let block = get_executed_block_with_number(number);
let state = BlockState::new(block);
assert_eq!(state.number(), number);
}
#[tokio::test]
async fn test_state_state_root() {
let number = rand::thread_rng().gen::<u64>();
let block = get_executed_block_with_number(number);
let state = BlockState::new(block.clone());
assert_eq!(state.state_root(), block.block().state_root);
}
#[tokio::test]
async fn test_state_receipts() {
let receipts = Receipts { receipt_vec: vec![vec![Some(Receipt::default())]] };
let block = get_executed_block_with_receipts(receipts.clone());
let state = BlockState::new(block);
assert_eq!(state.receipts(), &receipts);
}
}