diff --git a/Cargo.lock b/Cargo.lock
index 47d0a328b00..bd4d89ae6ac 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -10463,7 +10463,6 @@ dependencies = [
"alloy-trie",
"arbitrary",
"assert_matches",
- "auto_impl",
"itertools 0.14.0",
"metrics",
"pretty_assertions",
diff --git a/crates/engine/tree/src/tree/payload_processor/mod.rs b/crates/engine/tree/src/tree/payload_processor/mod.rs
index 92ff14890b5..bc274525a81 100644
--- a/crates/engine/tree/src/tree/payload_processor/mod.rs
+++ b/crates/engine/tree/src/tree/payload_processor/mod.rs
@@ -597,6 +597,7 @@ where
proof_worker_handle,
trie_metrics.clone(),
sparse_state_trie,
+ parent_state_root,
chunk_size,
);
diff --git a/crates/engine/tree/src/tree/payload_processor/sparse_trie.rs b/crates/engine/tree/src/tree/payload_processor/sparse_trie.rs
index 46d9b2c3f5c..668002a0824 100644
--- a/crates/engine/tree/src/tree/payload_processor/sparse_trie.rs
+++ b/crates/engine/tree/src/tree/payload_processor/sparse_trie.rs
@@ -27,8 +27,9 @@ use reth_trie_parallel::{
root::ParallelStateRootError,
};
use reth_trie_sparse::{
- errors::SparseTrieResult, ConfigurableSparseTrie, DeferredDrops, LeafUpdate,
- RevealableSparseTrie, SparseStateTrie, SparseTrie,
+ errors::{SparseStateTrieErrorKind, SparseTrieErrorKind, SparseTrieResult},
+ ConfigurableSparseTrie, DeferredDrops, LeafUpdate, RevealableSparseTrie, SparseStateTrie,
+ SparseTrie,
};
use revm_primitives::{hash_map::Entry, B256Map};
use tracing::{debug, debug_span, error, instrument, trace_span};
@@ -46,6 +47,8 @@ pub(super) struct SparseTrieCacheTask,
/// `SparseStateTrie` used for computing the state root.
trie: SparseStateTrie,
+ /// The parent block's state root.
+ parent_state_root: B256,
/// Handle to the proof worker pools (storage and account).
proof_worker_handle: ProofWorkerHandle,
@@ -120,6 +123,7 @@ where
proof_worker_handle: ProofWorkerHandle,
metrics: MultiProofTaskMetrics,
trie: SparseStateTrie,
+ parent_state_root: B256,
chunk_size: usize,
) -> Self {
let (proof_result_tx, proof_result_rx) = crossbeam_channel::unbounded();
@@ -138,6 +142,7 @@ where
updates: hashed_state_rx,
proof_worker_handle,
trie,
+ parent_state_root,
chunk_size,
max_targets_for_chunking: DEFAULT_MAX_TARGETS_FOR_CHUNKING,
account_updates: Default::default(),
@@ -359,10 +364,25 @@ where
debug!(target: "engine::root", "All proofs processed, ending calculation");
let start = Instant::now();
- let (state_root, trie_updates) =
- self.trie.root_with_updates(&self.proof_worker_handle).map_err(|e| {
- ParallelStateRootError::Other(format!("could not calculate state root: {e:?}"))
- })?;
+ let (state_root, trie_updates) = match self.trie.root_with_updates() {
+ Ok(result) => result,
+ Err(err)
+ if matches!(
+ err.kind(),
+ SparseStateTrieErrorKind::Sparse(SparseTrieErrorKind::Blind)
+ ) =>
+ {
+ // A still-blind account trie means this block never changed state, so preserve
+ // the cached parent root instead of fetching and revealing
+ // the unchanged root node.
+ (self.parent_state_root, TrieUpdates::default())
+ }
+ Err(err) => {
+ return Err(ParallelStateRootError::Other(format!(
+ "could not calculate state root: {err:?}"
+ )))
+ }
+ };
#[cfg(feature = "trie-debug")]
let debug_recorders = self.trie.take_debug_recorders();
@@ -873,6 +893,11 @@ enum SparseTrieTaskMessage {
mod tests {
use super::*;
use alloy_primitives::{keccak256, Address, B256, U256};
+ use reth_provider::{
+ providers::OverlayStateProviderFactory, test_utils::create_test_provider_factory,
+ };
+ use reth_trie_db::ChangesetCache;
+ use reth_trie_parallel::proof_task::ProofTaskCtx;
use reth_trie_sparse::ArenaParallelSparseTrie;
#[test]
@@ -953,4 +978,43 @@ mod tests {
assert_eq!(decoded.storage_root, storage_root);
assert_eq!(account_rlp_buf, encoded);
}
+
+ #[test]
+ fn run_returns_parent_root_without_revealing_blind_trie_when_no_state_updates() {
+ let runtime = reth_tasks::Runtime::test();
+ let provider_factory = create_test_provider_factory();
+ let overlay_factory =
+ OverlayStateProviderFactory::new(provider_factory, ChangesetCache::new());
+ let proof_worker_handle =
+ ProofWorkerHandle::new(&runtime, ProofTaskCtx::new(overlay_factory), false);
+
+ let default_trie = RevealableSparseTrie::blind_from(ConfigurableSparseTrie::Arena(
+ ArenaParallelSparseTrie::default(),
+ ));
+ let trie = SparseStateTrie::default()
+ .with_accounts_trie(default_trie.clone())
+ .with_default_storage_trie(default_trie)
+ .with_updates(true);
+
+ let parent_state_root = B256::from([0x55; 32]);
+ let (updates_tx, updates_rx) = crossbeam_channel::unbounded();
+ let mut task = SparseTrieCacheTask::new_with_trie(
+ &runtime,
+ updates_rx,
+ proof_worker_handle,
+ MultiProofTaskMetrics::default(),
+ trie,
+ parent_state_root,
+ 1,
+ );
+
+ updates_tx.send(StateRootMessage::FinishedStateUpdates).unwrap();
+ drop(updates_tx);
+
+ let outcome = task.run().expect("state root computation should succeed");
+
+ assert_eq!(outcome.state_root, parent_state_root);
+ assert!(outcome.trie_updates.is_empty());
+ assert!(task.trie.state_trie_ref().is_none(), "blind trie should not be revealed");
+ }
}
diff --git a/crates/trie/parallel/Cargo.toml b/crates/trie/parallel/Cargo.toml
index 637e98d3c09..4e82f55165d 100644
--- a/crates/trie/parallel/Cargo.toml
+++ b/crates/trie/parallel/Cargo.toml
@@ -17,7 +17,6 @@ reth-primitives-traits = { workspace = true, features = ["dashmap", "std"] }
reth-execution-errors.workspace = true
reth-provider.workspace = true
reth-storage-errors.workspace = true
-reth-trie-sparse = { workspace = true, features = ["std"] }
reth-tasks = { workspace = true, features = ["rayon"] }
reth-trie.workspace = true
@@ -46,6 +45,7 @@ reth-metrics = { workspace = true, optional = true }
metrics = { workspace = true, optional = true }
# `trie-debug` feature
+reth-trie-sparse = { workspace = true, optional = true, features = ["trie-debug"] }
rand = { workspace = true, optional = true }
[dev-dependencies]
@@ -63,13 +63,17 @@ tokio = { workspace = true, features = ["rt", "rt-multi-thread", "macros"] }
[features]
default = ["metrics"]
-metrics = ["reth-metrics", "dep:metrics", "reth-trie/metrics", "reth-trie-sparse/metrics"]
-trie-debug = ["dep:rand", "reth-trie-sparse/trie-debug"]
+metrics = ["reth-metrics", "dep:metrics", "reth-trie/metrics"]
+trie-debug = [
+ "dep:rand",
+ "dep:reth-trie-sparse",
+ "reth-trie-sparse?/trie-debug",
+]
test-utils = [
"reth-primitives-traits/test-utils",
"reth-provider/test-utils",
"reth-trie-db/test-utils",
- "reth-trie-sparse/test-utils",
"reth-trie/test-utils",
"reth-tasks/test-utils",
+ "reth-trie-sparse?/test-utils",
]
diff --git a/crates/trie/parallel/src/proof_task.rs b/crates/trie/parallel/src/proof_task.rs
index a7546c9cd3e..2e48bbbaff1 100644
--- a/crates/trie/parallel/src/proof_task.rs
+++ b/crates/trie/parallel/src/proof_task.rs
@@ -4,8 +4,8 @@
//! # Architecture
//!
//! - **Worker Pools**: Pre-spawned workers with dedicated database transactions
-//! - Storage pool: Handles storage proofs and blinded storage node requests
-//! - Account pool: Handles account multiproofs and blinded account node requests
+//! - Storage pool: Handles storage proofs
+//! - Account pool: Handles account multiproofs
//! - **Direct Channel Access**: `ProofWorkerHandle` provides type-safe queue methods with direct
//! access to worker channels, eliminating routing overhead
//! - **Automatic Shutdown**: Workers terminate gracefully when all handles are dropped
@@ -38,26 +38,22 @@ use alloy_primitives::{
B256, U256,
};
use crossbeam_channel::{unbounded, Receiver as CrossbeamReceiver, Sender as CrossbeamSender};
-use reth_execution_errors::{SparseTrieError, SparseTrieErrorKind, StateProofError};
+use reth_execution_errors::StateProofError;
use reth_primitives_traits::{dashmap::DashMap, FastInstant as Instant};
use reth_provider::{DatabaseProviderROFactory, ProviderError, ProviderResult};
use reth_storage_errors::db::DatabaseError;
use reth_tasks::Runtime;
use reth_trie::{
hashed_cursor::{HashedCursorFactory, HashedStorageCursor, InstrumentedHashedCursor},
- proof::{ProofBlindedAccountProvider, ProofBlindedStorageProvider},
proof_v2,
trie_cursor::{InstrumentedTrieCursor, TrieCursorFactory, TrieStorageCursor},
- DecodedMultiProofV2, HashedPostState, MultiProofTargetsV2, Nibbles, ProofTrieNodeV2,
- ProofV2Target,
+ DecodedMultiProofV2, HashedPostState, MultiProofTargetsV2, ProofTrieNodeV2, ProofV2Target,
};
-use reth_trie_sparse::provider::{RevealedNode, TrieNodeProvider, TrieNodeProviderFactory};
use std::{
cell::RefCell,
rc::Rc,
sync::{
atomic::{AtomicBool, AtomicUsize, Ordering},
- mpsc::{channel, Receiver, Sender},
Arc,
},
time::Duration,
@@ -69,8 +65,6 @@ use crate::proof_task_metrics::{
ProofTaskCursorMetrics, ProofTaskCursorMetricsCache, ProofTaskTrieMetrics,
};
-type TrieNodeProviderResult = Result