revert: fix(engine): recompute trie updates for forked blocks (#16500) (#16565)

This commit is contained in:
Alexey Shekhirin
2025-05-30 12:58:40 +01:00
committed by GitHub
parent 91d8ee287b
commit 6c8559775e
15 changed files with 115 additions and 318 deletions

View File

@@ -798,71 +798,19 @@ impl<N: NodePrimitives> ExecutedBlock<N> {
}
}
/// Trie updates that result from calculating the state root for the block.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ExecutedTrieUpdates {
/// Trie updates present. State root was calculated, and the trie updates can be applied to the
/// database.
Present(Arc<TrieUpdates>),
/// Trie updates missing. State root was calculated, but the trie updates cannot be applied to
/// the current database state. To apply the updates, the state root must be recalculated, and
/// new trie updates must be generated.
///
/// This can happen when processing fork chain blocks that are building on top of the
/// historical database state. Since we don't store the historical trie state, we cannot
/// generate the trie updates for it.
Missing,
}
impl ExecutedTrieUpdates {
/// Creates a [`ExecutedTrieUpdates`] with present but empty trie updates.
pub fn empty() -> Self {
Self::Present(Arc::default())
}
/// Sets the trie updates to the provided value as present.
pub fn set_present(&mut self, updates: Arc<TrieUpdates>) {
*self = Self::Present(updates);
}
/// Takes the present trie updates, leaving the state as missing.
pub fn take_present(&mut self) -> Option<Arc<TrieUpdates>> {
match self {
Self::Present(updates) => {
let updates = core::mem::take(updates);
*self = Self::Missing;
Some(updates)
}
Self::Missing => None,
}
}
/// Returns a reference to the trie updates if present.
#[allow(clippy::missing_const_for_fn)]
pub fn as_ref(&self) -> Option<&TrieUpdates> {
match self {
Self::Present(updates) => Some(updates),
Self::Missing => None,
}
}
/// Returns `true` if the trie updates are present.
pub const fn is_present(&self) -> bool {
matches!(self, Self::Present(_))
}
/// Returns `true` if the trie updates are missing.
pub const fn is_missing(&self) -> bool {
matches!(self, Self::Missing)
}
}
/// An [`ExecutedBlock`] with its [`TrieUpdates`].
///
/// We store it as separate type because [`TrieUpdates`] are only available for blocks stored in
/// memory and can't be obtained for canonical persisted blocks.
#[derive(
Clone, Debug, PartialEq, Eq, derive_more::Deref, derive_more::DerefMut, derive_more::Into,
Clone,
Debug,
PartialEq,
Eq,
Default,
derive_more::Deref,
derive_more::DerefMut,
derive_more::Into,
)]
pub struct ExecutedBlockWithTrieUpdates<N: NodePrimitives = EthPrimitives> {
/// Inner [`ExecutedBlock`].
@@ -870,11 +818,8 @@ pub struct ExecutedBlockWithTrieUpdates<N: NodePrimitives = EthPrimitives> {
#[deref_mut]
#[into]
pub block: ExecutedBlock<N>,
/// Trie updates that result from calculating the state root for the block.
///
/// If [`ExecutedTrieUpdates::Missing`], the trie updates should be computed when persisting
/// the block **on top of the canonical parent**.
pub trie: ExecutedTrieUpdates,
/// Trie updates that result of applying the block.
pub trie: Arc<TrieUpdates>,
}
impl<N: NodePrimitives> ExecutedBlockWithTrieUpdates<N> {
@@ -883,15 +828,15 @@ impl<N: NodePrimitives> ExecutedBlockWithTrieUpdates<N> {
recovered_block: Arc<RecoveredBlock<N::Block>>,
execution_output: Arc<ExecutionOutcome<N::Receipt>>,
hashed_state: Arc<HashedPostState>,
trie: ExecutedTrieUpdates,
trie: Arc<TrieUpdates>,
) -> Self {
Self { block: ExecutedBlock { recovered_block, execution_output, hashed_state }, trie }
}
/// Returns a reference to the trie updates for the block, if present.
/// Returns a reference to the trie updates for the block
#[inline]
pub fn trie_updates(&self) -> Option<&TrieUpdates> {
self.trie.as_ref()
pub fn trie_updates(&self) -> &TrieUpdates {
&self.trie
}
/// Converts the value into [`SealedBlock`].

View File

@@ -26,7 +26,7 @@ pub struct MemoryOverlayStateProviderRef<
/// The collection of executed parent blocks. Expected order is newest to oldest.
pub(crate) in_memory: Vec<ExecutedBlockWithTrieUpdates<N>>,
/// Lazy-loaded in-memory trie data.
pub(crate) trie_input: OnceLock<TrieInput>,
pub(crate) trie_state: OnceLock<MemoryOverlayTrieState>,
}
/// A state provider that stores references to in-memory blocks along with their state as well as
@@ -45,7 +45,7 @@ impl<'a, N: NodePrimitives> MemoryOverlayStateProviderRef<'a, N> {
historical: Box<dyn StateProvider + 'a>,
in_memory: Vec<ExecutedBlockWithTrieUpdates<N>>,
) -> Self {
Self { historical, in_memory, trie_input: OnceLock::new() }
Self { historical, in_memory, trie_state: OnceLock::new() }
}
/// Turn this state provider into a state provider
@@ -54,14 +54,14 @@ impl<'a, N: NodePrimitives> MemoryOverlayStateProviderRef<'a, N> {
}
/// Return lazy-loaded trie state aggregated from in-memory blocks.
fn trie_input(&self) -> &TrieInput {
self.trie_input.get_or_init(|| {
TrieInput::from_blocks(
self.in_memory
.iter()
.rev()
.map(|block| (block.hashed_state.as_ref(), block.trie.as_ref())),
)
fn trie_state(&self) -> &MemoryOverlayTrieState {
self.trie_state.get_or_init(|| {
let mut trie_state = MemoryOverlayTrieState::default();
for block in self.in_memory.iter().rev() {
trie_state.state.extend_ref(block.hashed_state.as_ref());
trie_state.nodes.extend_ref(block.trie.as_ref());
}
trie_state
})
}
}
@@ -117,7 +117,8 @@ impl<N: NodePrimitives> StateRootProvider for MemoryOverlayStateProviderRef<'_,
}
fn state_root_from_nodes(&self, mut input: TrieInput) -> ProviderResult<B256> {
input.prepend_self(self.trie_input().clone());
let MemoryOverlayTrieState { nodes, state } = self.trie_state().clone();
input.prepend_cached(nodes, state);
self.historical.state_root_from_nodes(input)
}
@@ -132,7 +133,8 @@ impl<N: NodePrimitives> StateRootProvider for MemoryOverlayStateProviderRef<'_,
&self,
mut input: TrieInput,
) -> ProviderResult<(B256, TrieUpdates)> {
input.prepend_self(self.trie_input().clone());
let MemoryOverlayTrieState { nodes, state } = self.trie_state().clone();
input.prepend_cached(nodes, state);
self.historical.state_root_from_nodes_with_updates(input)
}
}
@@ -140,7 +142,7 @@ impl<N: NodePrimitives> StateRootProvider for MemoryOverlayStateProviderRef<'_,
impl<N: NodePrimitives> StorageRootProvider for MemoryOverlayStateProviderRef<'_, N> {
// TODO: Currently this does not reuse available in-memory trie nodes.
fn storage_root(&self, address: Address, storage: HashedStorage) -> ProviderResult<B256> {
let state = &self.trie_input().state;
let state = &self.trie_state().state;
let mut hashed_storage =
state.storages.get(&keccak256(address)).cloned().unwrap_or_default();
hashed_storage.extend(&storage);
@@ -154,7 +156,7 @@ impl<N: NodePrimitives> StorageRootProvider for MemoryOverlayStateProviderRef<'_
slot: B256,
storage: HashedStorage,
) -> ProviderResult<reth_trie::StorageProof> {
let state = &self.trie_input().state;
let state = &self.trie_state().state;
let mut hashed_storage =
state.storages.get(&keccak256(address)).cloned().unwrap_or_default();
hashed_storage.extend(&storage);
@@ -168,7 +170,7 @@ impl<N: NodePrimitives> StorageRootProvider for MemoryOverlayStateProviderRef<'_
slots: &[B256],
storage: HashedStorage,
) -> ProviderResult<StorageMultiProof> {
let state = &self.trie_input().state;
let state = &self.trie_state().state;
let mut hashed_storage =
state.storages.get(&keccak256(address)).cloned().unwrap_or_default();
hashed_storage.extend(&storage);
@@ -183,7 +185,8 @@ impl<N: NodePrimitives> StateProofProvider for MemoryOverlayStateProviderRef<'_,
address: Address,
slots: &[B256],
) -> ProviderResult<AccountProof> {
input.prepend_self(self.trie_input().clone());
let MemoryOverlayTrieState { nodes, state } = self.trie_state().clone();
input.prepend_cached(nodes, state);
self.historical.proof(input, address, slots)
}
@@ -192,12 +195,14 @@ impl<N: NodePrimitives> StateProofProvider for MemoryOverlayStateProviderRef<'_,
mut input: TrieInput,
targets: MultiProofTargets,
) -> ProviderResult<MultiProof> {
input.prepend_self(self.trie_input().clone());
let MemoryOverlayTrieState { nodes, state } = self.trie_state().clone();
input.prepend_cached(nodes, state);
self.historical.multiproof(input, targets)
}
fn witness(&self, mut input: TrieInput, target: HashedPostState) -> ProviderResult<Vec<Bytes>> {
input.prepend_self(self.trie_input().clone());
let MemoryOverlayTrieState { nodes, state } = self.trie_state().clone();
input.prepend_cached(nodes, state);
self.historical.witness(input, target)
}
}
@@ -233,3 +238,12 @@ impl<N: NodePrimitives> StateProvider for MemoryOverlayStateProviderRef<'_, N> {
self.historical.bytecode_by_hash(code_hash)
}
}
/// The collection of data necessary for trie-related operations for [`MemoryOverlayStateProvider`].
#[derive(Clone, Default, Debug)]
pub(crate) struct MemoryOverlayTrieState {
/// The collection of aggregated in-memory trie updates.
pub(crate) nodes: TrieUpdates,
/// The collection of hashed state from in-memory blocks.
pub(crate) state: HashedPostState,
}

View File

@@ -1,6 +1,6 @@
use crate::{
in_memory::ExecutedBlockWithTrieUpdates, CanonStateNotification, CanonStateNotifications,
CanonStateSubscriptions, ExecutedTrieUpdates,
CanonStateSubscriptions,
};
use alloy_consensus::{
Header, SignableTransaction, Transaction as _, TxEip1559, TxReceipt, EMPTY_ROOT_HASH,
@@ -25,7 +25,7 @@ use reth_primitives_traits::{
SignedTransaction,
};
use reth_storage_api::NodePrimitivesProvider;
use reth_trie::{root::state_root_unhashed, HashedPostState};
use reth_trie::{root::state_root_unhashed, updates::TrieUpdates, HashedPostState};
use revm_database::BundleState;
use revm_state::AccountInfo;
use std::{
@@ -222,7 +222,7 @@ impl<N: NodePrimitives> TestBlockBuilder<N> {
vec![Requests::default()],
)),
Arc::new(HashedPostState::default()),
ExecutedTrieUpdates::empty(),
Arc::new(TrieUpdates::default()),
)
}

View File

@@ -1,7 +1,6 @@
//! Internal errors for the tree module.
use alloy_consensus::BlockHeader;
use alloy_primitives::B256;
use reth_consensus::ConsensusError;
use reth_errors::{BlockExecutionError, BlockValidationError, ProviderError};
use reth_evm::execute::InternalBlockExecutionError;
@@ -18,20 +17,6 @@ pub enum AdvancePersistenceError {
/// A provider error
#[error(transparent)]
Provider(#[from] ProviderError),
/// Missing ancestor.
///
/// This error occurs when we need to compute the state root for a block with missing trie
/// updates, but the ancestor block is not available. State root computation requires the state
/// from the parent block as a starting point.
///
/// A block may be missing the trie updates when it's a fork chain block building on top of the
/// historical database state. Since we don't store the historical trie state, we cannot
/// generate the trie updates for it until the moment when database is unwound to the canonical
/// chain.
///
/// Also see [`reth_chain_state::ExecutedTrieUpdates::Missing`].
#[error("Missing ancestor with hash {0}")]
MissingAncestor(B256),
}
#[derive(thiserror::Error)]

View File

@@ -20,7 +20,7 @@ use payload_processor::sparse_trie::StateRootComputeOutcome;
use persistence_state::CurrentPersistenceAction;
use precompile_cache::{CachedPrecompile, PrecompileCacheMap};
use reth_chain_state::{
CanonicalInMemoryState, ExecutedBlock, ExecutedBlockWithTrieUpdates, ExecutedTrieUpdates,
CanonicalInMemoryState, ExecutedBlock, ExecutedBlockWithTrieUpdates,
MemoryOverlayStateProvider, NewCanonicalChain,
};
use reth_consensus::{Consensus, FullConsensus};
@@ -49,7 +49,6 @@ use reth_trie_db::{DatabaseHashedPostState, StateCommitment};
use reth_trie_parallel::root::{ParallelStateRoot, ParallelStateRootError};
use state::TreeState;
use std::{
borrow::Cow,
fmt::Debug,
sync::{
mpsc::{Receiver, RecvError, RecvTimeoutError, Sender},
@@ -728,16 +727,11 @@ where
/// extension of the canonical chain.
/// * walking back from the current head to verify that the target hash is not already part of
/// the canonical chain.
///
/// The header is required as an arg, because we might be checking that the header is a fork
/// block before it's in the tree state and before it's in the database.
fn is_fork(&self, target_header: &SealedHeader<N::BlockHeader>) -> ProviderResult<bool> {
let target_hash = target_header.hash();
fn is_fork(&self, target_hash: B256) -> ProviderResult<bool> {
// verify that the given hash is not part of an extension of the canon chain.
let canonical_head = self.state.tree_state.canonical_head();
let mut current_hash;
let mut current_block = Cow::Borrowed(target_header);
loop {
let mut current_hash = target_hash;
while let Some(current_block) = self.sealed_header_by_hash(current_hash)? {
if current_block.hash() == canonical_head.hash {
return Ok(false)
}
@@ -746,9 +740,6 @@ where
break
}
current_hash = current_block.parent_hash();
let Some(next_block) = self.sealed_header_by_hash(current_hash)? else { break };
current_block = Cow::Owned(next_block);
}
// verify that the given hash is not already part of canonical chain stored in memory
@@ -764,26 +755,6 @@ where
Ok(true)
}
/// Check if the given block has any ancestors with missing trie updates.
fn has_ancestors_with_missing_trie_updates(
&self,
target_header: &SealedHeader<N::BlockHeader>,
) -> bool {
// Walk back through the chain starting from the parent of the target block
let mut current_hash = target_header.parent_hash();
while let Some(block) = self.state.tree_state.blocks_by_hash.get(&current_hash) {
// Check if this block is missing trie updates
if block.trie.is_missing() {
return true;
}
// Move to the parent block
current_hash = block.recovered_block().parent_hash();
}
false
}
/// Returns the persisting kind for the input block.
fn persisting_kind_for(&self, block: &N::BlockHeader) -> PersistingKind {
// Check that we're currently persisting.
@@ -1050,7 +1021,7 @@ where
if let Some(new_tip_num) = self.find_disk_reorg()? {
self.remove_blocks(new_tip_num)
} else if self.should_persist() {
let blocks_to_persist = self.get_canonical_blocks_to_persist()?;
let blocks_to_persist = self.get_canonical_blocks_to_persist();
self.persist_blocks(blocks_to_persist);
}
}
@@ -1377,20 +1348,9 @@ where
}
/// Returns a batch of consecutive canonical blocks to persist in the range
/// `(last_persisted_number .. canonical_head - threshold]`. The expected
/// `(last_persisted_number .. canonical_head - threshold]` . The expected
/// order is oldest -> newest.
///
/// For those blocks that didn't have the trie updates calculated, runs the state root
/// calculation, and saves the trie updates.
///
/// Returns an error if the state root calculation fails.
fn get_canonical_blocks_to_persist(
&mut self,
) -> Result<Vec<ExecutedBlockWithTrieUpdates<N>>, AdvancePersistenceError> {
// We will calculate the state root using the database, so we need to be sure there are no
// changes
debug_assert!(!self.persistence_state.in_progress());
fn get_canonical_blocks_to_persist(&self) -> Vec<ExecutedBlockWithTrieUpdates<N>> {
let mut blocks_to_persist = Vec::new();
let mut current_hash = self.state.tree_state.canonical_block_hash();
let last_persisted_number = self.persistence_state.last_persisted_block.number;
@@ -1413,51 +1373,10 @@ where
current_hash = block.recovered_block().parent_hash();
}
// Reverse the order so that the oldest block comes first
// reverse the order so that the oldest block comes first
blocks_to_persist.reverse();
// Calculate missing trie updates
for block in &mut blocks_to_persist {
if block.trie.is_present() {
continue
}
debug!(
target: "engine::tree",
block = ?block.recovered_block().num_hash(),
"Calculating trie updates before persisting"
);
let provider = self
.state_provider_builder(block.recovered_block().parent_hash())?
.ok_or(AdvancePersistenceError::MissingAncestor(
block.recovered_block().parent_hash(),
))?
.build()?;
let mut trie_input = self.compute_trie_input(
self.persisting_kind_for(block.recovered_block().header()),
self.provider.database_provider_ro()?,
block.recovered_block().parent_hash(),
)?;
// Extend with block we are generating trie updates for.
trie_input.append_ref(block.hashed_state());
let (_root, updates) = provider.state_root_from_nodes_with_updates(trie_input)?;
debug_assert_eq!(_root, block.recovered_block().state_root());
// Update trie updates in both tree state and blocks to persist that we return
let trie_updates = Arc::new(updates);
let tree_state_block = self
.state
.tree_state
.blocks_by_hash
.get_mut(&block.recovered_block().hash())
.expect("blocks to persist are constructed from tree state blocks");
tree_state_block.trie.set_present(trie_updates.clone());
block.trie.set_present(trie_updates);
}
Ok(blocks_to_persist)
blocks_to_persist
}
/// This clears the blocks from the in-memory tree state that have been persisted to the
@@ -1909,10 +1828,7 @@ where
.persisted_trie_updates
.get(&block.recovered_block.hash())
.cloned()?;
Some(ExecutedBlockWithTrieUpdates {
block: block.clone(),
trie: ExecutedTrieUpdates::Present(trie),
})
Some(ExecutedBlockWithTrieUpdates { block: block.clone(), trie })
})
.collect::<Vec<_>>();
self.reinsert_reorged_blocks(old);
@@ -2175,7 +2091,7 @@ where
let trie_input_start = Instant::now();
let res = self.compute_trie_input(
persisting_kind,
ensure_ok!(consistent_view.provider_ro()),
consistent_view.clone(),
block.header().parent_hash(),
);
let trie_input = match res {
@@ -2325,25 +2241,13 @@ where
// terminate prewarming task with good state output
handle.terminate_caching(Some(output.state.clone()));
let is_fork = ensure_ok!(self.is_fork(block.sealed_header()));
let missing_trie_updates =
self.has_ancestors_with_missing_trie_updates(block.sealed_header());
// If the block is a fork or has ancestors with missing trie updates, we don't save the trie
// updates, because they may be incorrect. Instead, they will be recomputed on persistence.
let save_trie_updates = !(is_fork || missing_trie_updates);
let trie_updates = if save_trie_updates {
ExecutedTrieUpdates::Present(Arc::new(trie_output))
} else {
ExecutedTrieUpdates::Missing
};
let executed: ExecutedBlockWithTrieUpdates<N> = ExecutedBlockWithTrieUpdates {
block: ExecutedBlock {
recovered_block: Arc::new(block),
execution_output: Arc::new(ExecutionOutcome::from((output, block_num_hash.number))),
hashed_state: Arc::new(hashed_state),
},
trie: trie_updates,
trie: Arc::new(trie_output),
};
// if the parent is the canonical head, we can insert the block as the pending block
@@ -2358,6 +2262,10 @@ where
// emit insert event
let elapsed = start.elapsed();
let is_fork = match self.is_fork(block_num_hash.hash) {
Ok(val) => val,
Err(e) => return Err((e.into(), executed.block.recovered_block().clone())),
};
let engine_event = if is_fork {
BeaconConsensusEngineEvent::ForkBlockAdded(executed, elapsed)
} else {
@@ -2423,7 +2331,7 @@ where
let consistent_view = ConsistentDbView::new_with_latest_tip(self.provider.clone())?;
let mut input =
self.compute_trie_input(persisting_kind, consistent_view.provider_ro()?, parent_hash)?;
self.compute_trie_input(persisting_kind, consistent_view.clone(), parent_hash)?;
// Extend with block we are validating root for.
input.append_ref(hashed_state);
@@ -2445,14 +2353,15 @@ where
/// block.
/// 3. Once in-memory blocks are collected and optionally filtered, we compute the
/// [`HashedPostState`] from them.
fn compute_trie_input<TP: DBProvider + BlockNumReader>(
fn compute_trie_input(
&self,
persisting_kind: PersistingKind,
provider: TP,
consistent_view: ConsistentDbView<P>,
parent_hash: B256,
) -> ProviderResult<TrieInput> {
) -> Result<TrieInput, ParallelStateRootError> {
let mut input = TrieInput::default();
let provider = consistent_view.provider_ro()?;
let best_block_number = provider.best_block_number()?;
let (mut historical, mut blocks) = self
@@ -2523,9 +2432,9 @@ where
input.append(revert_state);
// Extend with contents of parent in-memory blocks.
input.extend_with_blocks(
blocks.iter().rev().map(|block| (block.hashed_state(), block.trie_updates())),
);
for block in blocks.iter().rev() {
input.append_cached_ref(block.trie_updates(), block.hashed_state())
}
Ok(input)
}
@@ -2893,7 +2802,7 @@ mod tests {
use reth_node_ethereum::EthereumEngineValidator;
use reth_primitives_traits::Block as _;
use reth_provider::test_utils::MockEthProvider;
use reth_trie::HashedPostState;
use reth_trie::{updates::TrieUpdates, HashedPostState};
use std::{
collections::BTreeMap,
str::FromStr,
@@ -3673,7 +3582,7 @@ mod tests {
execution_output: Arc::new(ExecutionOutcome::default()),
hashed_state: Arc::new(HashedPostState::default()),
},
trie: ExecutedTrieUpdates::empty(),
trie: Arc::new(TrieUpdates::default()),
});
}
test_harness.tree.state.tree_state.set_canonical_head(chain_a.last().unwrap().num_hash());
@@ -3685,7 +3594,7 @@ mod tests {
execution_output: Arc::new(ExecutionOutcome::default()),
hashed_state: Arc::new(HashedPostState::default()),
},
trie: ExecutedTrieUpdates::empty(),
trie: Arc::new(TrieUpdates::default()),
});
}
@@ -3735,7 +3644,7 @@ mod tests {
.with_persistence_threshold(persistence_threshold)
.with_memory_block_buffer_target(memory_block_buffer_target);
let blocks_to_persist = test_harness.tree.get_canonical_blocks_to_persist().unwrap();
let blocks_to_persist = test_harness.tree.get_canonical_blocks_to_persist();
let expected_blocks_to_persist_length: usize =
(canonical_head_number - memory_block_buffer_target - last_persisted_block_number)
@@ -3756,7 +3665,7 @@ mod tests {
assert!(test_harness.tree.state.tree_state.block_by_hash(fork_block_hash).is_some());
let blocks_to_persist = test_harness.tree.get_canonical_blocks_to_persist().unwrap();
let blocks_to_persist = test_harness.tree.get_canonical_blocks_to_persist();
assert_eq!(blocks_to_persist.len(), expected_blocks_to_persist_length);
// check that the fork block is not included in the blocks to persist
@@ -4141,7 +4050,7 @@ mod tests {
test_harness.check_canon_head(chain_b_tip_hash);
// verify that chain A is now considered a fork
assert!(test_harness.tree.is_fork(chain_a.last().unwrap().sealed_header()).unwrap());
assert!(test_harness.tree.is_fork(chain_a.last().unwrap().hash()).unwrap());
}
#[tokio::test]

View File

@@ -210,20 +210,13 @@ impl<N: NodePrimitives> TreeState<N> {
while let Some(executed) = self.blocks_by_hash.get(&current_block) {
current_block = executed.recovered_block().parent_hash();
if executed.recovered_block().number() <= upper_bound {
let num_hash = executed.recovered_block().num_hash();
debug!(target: "engine::tree", ?num_hash, "Attempting to remove block walking back from the head");
if let Some((mut removed, _)) =
self.remove_by_hash(executed.recovered_block().hash())
{
debug!(target: "engine::tree", ?num_hash, "Removed block walking back from the head");
debug!(target: "engine::tree", num_hash=?executed.recovered_block().num_hash(), "Attempting to remove block walking back from the head");
if let Some((removed, _)) = self.remove_by_hash(executed.recovered_block().hash()) {
debug!(target: "engine::tree", num_hash=?removed.recovered_block().num_hash(), "Removed block walking back from the head");
// finally, move the trie updates
let Some(trie_updates) = removed.trie.take_present() else {
debug!(target: "engine::tree", ?num_hash, "No trie updates found for persisted block");
continue;
};
self.persisted_trie_updates.insert(
removed.recovered_block().hash(),
(removed.recovered_block().number(), trie_updates),
(removed.recovered_block().number(), removed.trie),
);
}
}

View File

@@ -12,7 +12,7 @@ use alloy_rpc_types_debug::ExecutionWitness;
use alloy_rpc_types_engine::PayloadId;
use op_alloy_rpc_types_engine::OpPayloadAttributes;
use reth_basic_payload_builder::*;
use reth_chain_state::{ExecutedBlock, ExecutedBlockWithTrieUpdates, ExecutedTrieUpdates};
use reth_chain_state::{ExecutedBlock, ExecutedBlockWithTrieUpdates};
use reth_chainspec::{ChainSpecProvider, EthChainSpec};
use reth_evm::{
execute::{
@@ -341,7 +341,7 @@ impl<Txs> OpBuilder<'_, Txs> {
execution_output: Arc::new(execution_outcome),
hashed_state: Arc::new(hashed_state),
},
trie: ExecutedTrieUpdates::Present(Arc::new(trie_updates)),
trie: Arc::new(trie_updates),
};
let no_tx_pool = ctx.attributes().no_tx_pool;

View File

@@ -11,9 +11,7 @@
use alloy_consensus::BlockHeader as _;
use alloy_primitives::{Bytes, B256};
use parking_lot::Mutex;
use reth_chain_state::{
ExecutedBlock, ExecutedBlockWithTrieUpdates, ExecutedTrieUpdates, MemoryOverlayStateProvider,
};
use reth_chain_state::{ExecutedBlock, ExecutedBlockWithTrieUpdates, MemoryOverlayStateProvider};
use reth_errors::{ProviderError, ProviderResult};
use reth_ethereum_primitives::{Block, BlockBody, EthPrimitives};
use reth_evm::{execute::Executor, ConfigureEvm};
@@ -130,7 +128,7 @@ where
recovered_block: invalid,
..Default::default()
},
trie: ExecutedTrieUpdates::empty(),
..Default::default()
});
}
}
@@ -166,7 +164,7 @@ where
let witness_state_provider = self.provider.state_by_block_hash(ancestor_hash)?;
let mut trie_input = TrieInput::default();
for block in executed_ancestors.into_iter().rev() {
trie_input.append_cached_ref(block.trie.as_ref().unwrap(), &block.hashed_state);
trie_input.append_cached_ref(&block.trie, &block.hashed_state);
}
let mut hashed_state = db.into_state();
hashed_state.extend(record.hashed_state);

View File

@@ -134,9 +134,6 @@ pub enum ProviderError {
/// Received invalid output from configured storage implementation.
#[error("received invalid output from storage")]
InvalidStorageOutput,
/// Missing trie updates.
#[error("missing trie updates for block {0}")]
MissingTrieUpdates(B256),
/// Any other error type wrapped into a cloneable [`AnyError`].
#[error(transparent)]
Other(#[from] AnyError),

View File

@@ -785,8 +785,7 @@ mod tests {
use rand::Rng;
use reth_chain_state::{
test_utils::TestBlockBuilder, CanonStateNotification, CanonStateSubscriptions,
CanonicalInMemoryState, ExecutedBlock, ExecutedBlockWithTrieUpdates, ExecutedTrieUpdates,
NewCanonicalChain,
CanonicalInMemoryState, ExecutedBlock, ExecutedBlockWithTrieUpdates, NewCanonicalChain,
};
use reth_chainspec::{
ChainSpec, ChainSpecBuilder, ChainSpecProvider, EthereumHardfork, MAINNET,
@@ -937,7 +936,7 @@ mod tests {
Arc::new(RecoveredBlock::new_sealed(block.clone(), senders)),
execution_outcome.into(),
Default::default(),
ExecutedTrieUpdates::empty(),
Default::default(),
)
})
.collect(),
@@ -1069,7 +1068,7 @@ mod tests {
)),
Default::default(),
Default::default(),
ExecutedTrieUpdates::empty(),
Default::default(),
)],
};
provider.canonical_in_memory_state.update_chain(chain);
@@ -1107,7 +1106,7 @@ mod tests {
execution_output: Default::default(),
hashed_state: Default::default(),
},
trie: ExecutedTrieUpdates::empty(),
trie: Default::default(),
});
// Now the last block should be found in memory
@@ -1165,7 +1164,7 @@ mod tests {
)),
Default::default(),
Default::default(),
ExecutedTrieUpdates::empty(),
Default::default(),
)],
};
provider.canonical_in_memory_state.update_chain(chain);
@@ -1221,7 +1220,7 @@ mod tests {
execution_output: Default::default(),
hashed_state: Default::default(),
},
trie: ExecutedTrieUpdates::empty(),
trie: Default::default(),
});
// Assertions related to the pending block
@@ -1301,7 +1300,7 @@ mod tests {
)),
Default::default(),
Default::default(),
ExecutedTrieUpdates::empty(),
Default::default(),
)],
};
provider.canonical_in_memory_state.update_chain(chain);
@@ -1875,7 +1874,7 @@ mod tests {
..Default::default()
}),
Default::default(),
ExecutedTrieUpdates::empty(),
Default::default(),
)
})
.unwrap()],
@@ -2004,7 +2003,7 @@ mod tests {
execution_output: Default::default(),
hashed_state: Default::default(),
},
trie: ExecutedTrieUpdates::empty(),
trie: Default::default(),
},
);
@@ -2101,7 +2100,7 @@ mod tests {
execution_output: Default::default(),
hashed_state: Default::default(),
},
trie: ExecutedTrieUpdates::empty(),
trie: Default::default(),
});
// Set the safe block in memory

View File

@@ -1491,9 +1491,7 @@ mod tests {
use alloy_primitives::B256;
use itertools::Itertools;
use rand::Rng;
use reth_chain_state::{
ExecutedBlock, ExecutedBlockWithTrieUpdates, ExecutedTrieUpdates, NewCanonicalChain,
};
use reth_chain_state::{ExecutedBlock, ExecutedBlockWithTrieUpdates, NewCanonicalChain};
use reth_db_api::models::AccountBeforeTx;
use reth_ethereum_primitives::Block;
use reth_execution_types::ExecutionOutcome;
@@ -1603,7 +1601,7 @@ mod tests {
)),
Default::default(),
Default::default(),
ExecutedTrieUpdates::empty(),
Default::default(),
)],
};
consistent_provider.canonical_in_memory_state.update_chain(chain);
@@ -1647,7 +1645,7 @@ mod tests {
execution_output: Default::default(),
hashed_state: Default::default(),
},
trie: ExecutedTrieUpdates::empty(),
trie: Default::default(),
});
// Now the last block should be found in memory
@@ -1713,7 +1711,7 @@ mod tests {
)),
Default::default(),
Default::default(),
ExecutedTrieUpdates::empty(),
Default::default(),
)],
};
consistent_provider.canonical_in_memory_state.update_chain(chain);
@@ -1828,7 +1826,7 @@ mod tests {
..Default::default()
}),
Default::default(),
ExecutedTrieUpdates::empty(),
Default::default(),
)
})
.unwrap()],

View File

@@ -6,7 +6,7 @@ use crate::{
use alloy_consensus::BlockHeader;
use reth_chain_state::{ExecutedBlock, ExecutedBlockWithTrieUpdates};
use reth_db_api::transaction::{DbTx, DbTxMut};
use reth_errors::{ProviderError, ProviderResult};
use reth_errors::ProviderResult;
use reth_primitives_traits::{NodePrimitives, SignedTransaction};
use reth_static_file_types::StaticFileSegment;
use reth_storage_api::{DBProvider, StageCheckpointWriter, TransactionsProviderExt};
@@ -165,7 +165,6 @@ where
trie,
} in blocks
{
let block_hash = recovered_block.hash();
self.database()
.insert_block(Arc::unwrap_or_clone(recovered_block), StorageLocation::Both)?;
@@ -180,9 +179,7 @@ where
// insert hashes and intermediate merkle nodes
self.database()
.write_hashed_state(&Arc::unwrap_or_clone(hashed_state).into_sorted())?;
self.database().write_trie_updates(
trie.as_ref().ok_or(ProviderError::MissingTrieUpdates(block_hash))?,
)?;
self.database().write_trie_updates(&trie)?;
}
// update history indices

View File

@@ -84,7 +84,10 @@ std = [
"revm-database/std",
"revm-state/std",
]
eip1186 = ["alloy-rpc-types-eth/serde", "dep:alloy-serde"]
eip1186 = [
"alloy-rpc-types-eth/serde",
"dep:alloy-serde",
]
serde = [
"dep:serde",
"bytes?/serde",
@@ -98,7 +101,10 @@ serde = [
"revm-database/serde",
"revm-state/serde",
]
reth-codec = ["dep:reth-codecs", "dep:bytes"]
reth-codec = [
"dep:reth-codecs",
"dep:bytes",
]
serde-bincode-compat = [
"serde",
"reth-primitives-traits/serde-bincode-compat",

View File

@@ -31,47 +31,6 @@ impl TrieInput {
Self { nodes: TrieUpdates::default(), state, prefix_sets }
}
/// Create new trie input from the provided blocks, from oldest to newest. See the documentation
/// for [`Self::extend_with_blocks`] for details.
pub fn from_blocks<'a>(
blocks: impl IntoIterator<Item = (&'a HashedPostState, Option<&'a TrieUpdates>)>,
) -> Self {
let mut input = Self::default();
input.extend_with_blocks(blocks);
input
}
/// Extend the trie input with the provided blocks, from oldest to newest.
///
/// When encountering the first block with missing trie updates, the trie input will be extended
/// with prefix sets constructed from the state of this block and the state itself, **without**
/// trie updates. Subsequent blocks will be appended in the same way, i.e. only prefix sets
/// constructed from the state and the state itself.
pub fn extend_with_blocks<'a>(
&mut self,
blocks: impl IntoIterator<Item = (&'a HashedPostState, Option<&'a TrieUpdates>)>,
) {
let mut extend_trie_updates = true;
for (hashed_state, trie_updates) in blocks {
if let Some(nodes) = trie_updates.as_ref().filter(|_| extend_trie_updates) {
self.append_cached_ref(nodes, hashed_state);
} else {
self.append_ref(hashed_state);
extend_trie_updates = false;
}
}
}
/// Prepend another trie input to the current one.
pub fn prepend_self(&mut self, mut other: Self) {
core::mem::swap(&mut self.nodes, &mut other.nodes);
self.nodes.extend(other.nodes);
core::mem::swap(&mut self.state, &mut other.state);
self.state.extend(other.state);
// No need to swap prefix sets, as they will be sorted and deduplicated.
self.prefix_sets.extend(other.prefix_sets);
}
/// Prepend state to the input and extend the prefix sets.
pub fn prepend(&mut self, mut state: HashedPostState) {
self.prefix_sets.extend(state.construct_prefix_sets());

View File

@@ -13,7 +13,7 @@ use alloy_consensus::EMPTY_ROOT_HASH;
use alloy_primitives::{keccak256, Address, B256};
use alloy_rlp::{BufMut, Encodable};
use reth_execution_errors::{StateRootError, StorageRootError};
use tracing::{trace, trace_span};
use tracing::trace;
#[cfg(feature = "metrics")]
use crate::metrics::{StateRootMetrics, TrieRootMetrics};
@@ -396,10 +396,7 @@ where
self,
retain_updates: bool,
) -> Result<(B256, usize, StorageTrieUpdates), StorageRootError> {
let span = trace_span!(target: "trie::storage_root", "Storage trie", hashed_address = ?self.hashed_address);
let _enter = span.enter();
trace!(target: "trie::storage_root", "calculating storage root");
trace!(target: "trie::storage_root", hashed_address = ?self.hashed_address, "calculating storage root");
let mut hashed_storage_cursor =
self.hashed_cursor_factory.hashed_storage_cursor(self.hashed_address)?;