diff --git a/Cargo.lock b/Cargo.lock index d185cfdcc14..83bc439ae0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8163,6 +8163,7 @@ dependencies = [ "reth-payload-primitives", "reth-primitives-traits", "reth-trie-common", + "revm", "serde", "thiserror 2.0.18", "tokio", diff --git a/crates/engine/primitives/Cargo.toml b/crates/engine/primitives/Cargo.toml index aca33f4aff6..ddf68fedae9 100644 --- a/crates/engine/primitives/Cargo.toml +++ b/crates/engine/primitives/Cargo.toml @@ -22,6 +22,9 @@ reth-chain-state.workspace = true reth-errors.workspace = true reth-trie-common.workspace = true +# revm +revm.workspace = true + # alloy alloy-primitives.workspace = true alloy-consensus.workspace = true diff --git a/crates/engine/primitives/src/lib.rs b/crates/engine/primitives/src/lib.rs index c58fda1792e..e3e66cb5c7f 100644 --- a/crates/engine/primitives/src/lib.rs +++ b/crates/engine/primitives/src/lib.rs @@ -12,6 +12,7 @@ extern crate alloc; use alloy_consensus::BlockHeader; +use alloy_primitives::B256; use reth_errors::ConsensusError; use reth_payload_primitives::{ EngineApiMessageVersion, EngineObjectValidationError, InvalidPayloadAttributesError, @@ -19,6 +20,7 @@ use reth_payload_primitives::{ }; use reth_primitives_traits::{Block, RecoveredBlock, SealedBlock}; use reth_trie_common::HashedPostState; +use revm::database::states::BundleState; use serde::{de::DeserializeOwned, Serialize}; // Re-export [`ExecutionPayload`] moved to `reth_payload_primitives` @@ -212,6 +214,14 @@ pub trait PayloadValidator: Send + Sync + Unpin + 'static { sealed_block.try_recover().map_err(|e| NewPayloadError::Other(e.into())) } + /// Computes a custom state root for some networks, bypassing the default trie computation. + /// + /// Returns `None` by default to use th standard Ethereum state root. Returning `Some` will + /// cause the engine to skip trie computation and validate using the returned value instead. + fn compute_state_root(&self, _bundle_state: &BundleState, _parent_hash: B256) -> Option { + None + } + /// Verifies payload post-execution w.r.t. hashed state updates. fn validate_block_post_execution_with_hashed_state( &self, diff --git a/crates/engine/tree/src/tree/payload_validator.rs b/crates/engine/tree/src/tree/payload_validator.rs index d661bf02784..0590ad50de4 100644 --- a/crates/engine/tree/src/tree/payload_validator.rs +++ b/crates/engine/tree/src/tree/payload_validator.rs @@ -655,112 +655,126 @@ where ); let root_time = Instant::now(); + + let custom_state_root = + self.validator.compute_state_root(&output.state, block.header().parent_hash()); + if let Some(custom_root) = custom_state_root { + info!( + target: "engine::tree::payload_validator", + ?custom_root, + elapsed = ?root_time.elapsed(), + "Custom state root computed" + ); + } + let mut maybe_state_root = None; let mut state_root_task_failed = false; #[cfg(feature = "trie-debug")] let mut trie_debug_recorders = Vec::new(); - match strategy { - StateRootStrategy::StateRootTask => { - debug!(target: "engine::tree::payload_validator", "Using sparse trie state root algorithm"); - - let task_result = ensure_ok_post_block!( - self.await_state_root_with_timeout( - &mut handle, - provider_builder.clone(), - &hashed_state, - ), - block - ); + if custom_state_root.is_none() { + match strategy { + StateRootStrategy::StateRootTask => { + debug!(target: "engine::tree::payload_validator", "Using sparse trie state root algorithm"); + + let task_result = ensure_ok_post_block!( + self.await_state_root_with_timeout( + &mut handle, + provider_builder.clone(), + &hashed_state, + ), + block + ); - match task_result { - Ok(StateRootComputeOutcome { - state_root, - trie_updates, - #[cfg(feature = "trie-debug")] - debug_recorders, - }) => { - let elapsed = root_time.elapsed(); - info!(target: "engine::tree::payload_validator", ?state_root, ?elapsed, "State root task finished"); - - #[cfg(feature = "trie-debug")] - { - trie_debug_recorders = debug_recorders; - } + match task_result { + Ok(StateRootComputeOutcome { + state_root, + trie_updates, + #[cfg(feature = "trie-debug")] + debug_recorders, + }) => { + let elapsed = root_time.elapsed(); + info!(target: "engine::tree::payload_validator", ?state_root, ?elapsed, "State root task finished"); - // Compare trie updates with serial computation if configured - if self.config.always_compare_trie_updates() { - let _has_diff = self.compare_trie_updates_with_serial( - provider_builder.clone(), - provider_factory, - overlay_builder, - &hashed_state, - trie_updates.as_ref().clone(), - ); #[cfg(feature = "trie-debug")] - if _has_diff { + { + trie_debug_recorders = debug_recorders; + } + + // Compare trie updates with serial computation if configured + if self.config.always_compare_trie_updates() { + let _has_diff = self.compare_trie_updates_with_serial( + provider_builder.clone(), + provider_factory, + overlay_builder, + &hashed_state, + trie_updates.as_ref().clone(), + ); + #[cfg(feature = "trie-debug")] + if _has_diff { + Self::write_trie_debug_recorders( + block.header().number(), + &trie_debug_recorders, + ); + } + } + + // we double check the state root here for good measure + if state_root == block.header().state_root() { + maybe_state_root = Some((state_root, trie_updates, elapsed)) + } else { + warn!( + target: "engine::tree::payload_validator", + ?state_root, + block_state_root = ?block.header().state_root(), + "State root task returned incorrect state root" + ); + #[cfg(feature = "trie-debug")] Self::write_trie_debug_recorders( block.header().number(), &trie_debug_recorders, ); + state_root_task_failed = true; } } - - // we double check the state root here for good measure - if state_root == block.header().state_root() { - maybe_state_root = Some((state_root, trie_updates, elapsed)) - } else { - warn!( - target: "engine::tree::payload_validator", - ?state_root, - block_state_root = ?block.header().state_root(), - "State root task returned incorrect state root" - ); - #[cfg(feature = "trie-debug")] - Self::write_trie_debug_recorders( - block.header().number(), - &trie_debug_recorders, - ); + Err(error) => { + debug!(target: "engine::tree::payload_validator", %error, "State root task failed"); state_root_task_failed = true; } } - Err(error) => { - debug!(target: "engine::tree::payload_validator", %error, "State root task failed"); - state_root_task_failed = true; - } } - } - StateRootStrategy::Parallel => { - debug!(target: "engine::tree::payload_validator", "Using parallel state root algorithm"); - match self.compute_state_root_parallel( - provider_factory, - overlay_builder, - &hashed_state, - ) { - Ok(result) => { - let elapsed = root_time.elapsed(); - info!( - target: "engine::tree::payload_validator", - regular_state_root = ?result.0, - ?elapsed, - "Regular root task finished" - ); - maybe_state_root = Some((result.0, Arc::new(result.1), elapsed)); - } - Err(error) => { - debug!(target: "engine::tree::payload_validator", %error, "Parallel state root computation failed"); + StateRootStrategy::Parallel => { + debug!(target: "engine::tree::payload_validator", "Using parallel state root algorithm"); + match self.compute_state_root_parallel( + provider_factory, + overlay_builder, + &hashed_state, + ) { + Ok(result) => { + let elapsed = root_time.elapsed(); + info!( + target: "engine::tree::payload_validator", + regular_state_root = ?result.0, + ?elapsed, + "Regular root task finished" + ); + maybe_state_root = Some((result.0, Arc::new(result.1), elapsed)); + } + Err(error) => { + debug!(target: "engine::tree::payload_validator", %error, "Parallel state root computation failed"); + } } } + StateRootStrategy::Synchronous => {} } - StateRootStrategy::Synchronous => {} } // Determine the state root. // If the state root was computed in parallel, we use it. // Otherwise, we fall back to computing it synchronously. - let (state_root, trie_output, root_elapsed) = if let Some(maybe_state_root) = - maybe_state_root - { + let (state_root, trie_output, root_elapsed) = if let Some(custom_root) = custom_state_root { + (custom_root, Arc::new(TrieUpdates::default()), root_time.elapsed()) + } else if let Some(maybe_state_root) = maybe_state_root { maybe_state_root } else { // fallback is to compute the state root regularly in sync