From eb337a4d3e95edc1489c60ea91469d91a9be70bc Mon Sep 17 00:00:00 2001 From: yongkangc Date: Mon, 16 Mar 2026 06:42:59 +0000 Subject: [PATCH 01/10] feat(engine): introduce `EngineSharedCaches` facade for launcher-owned cache lifecycle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a Reth-owned `EngineSharedCaches` type that groups execution cache, sparse trie cache, and precompile cache under a single facade created by the node launcher and threaded through the engine validator path — following the same ownership pattern as `ChangesetCache`. This gives a stable public ownership boundary for later downstream consumption without changing internal hot-path behavior. --- .../tree/src/tree/payload_processor/mod.rs | 70 ++++++++++++++++--- .../preserved_sparse_trie.rs | 43 ++++++++++-- .../engine/tree/src/tree/payload_validator.rs | 13 ++-- crates/engine/tree/src/tree/tests.rs | 2 + crates/node/builder/src/launch/engine.rs | 25 +++++-- crates/node/builder/src/rpc.rs | 35 +++++++++- 6 files changed, 161 insertions(+), 27 deletions(-) diff --git a/crates/engine/tree/src/tree/payload_processor/mod.rs b/crates/engine/tree/src/tree/payload_processor/mod.rs index 8510606be18..31f86863579 100644 --- a/crates/engine/tree/src/tree/payload_processor/mod.rs +++ b/crates/engine/tree/src/tree/payload_processor/mod.rs @@ -60,6 +60,7 @@ pub mod prewarm; pub mod receipt_root_task; pub mod sparse_trie; +pub use preserved_sparse_trie::PayloadSparseTrieCache; use preserved_sparse_trie::{PreservedSparseTrie, SharedPreservedSparseTrie}; /// Default parallelism thresholds to use with the [`ParallelSparseTrie`]. @@ -154,16 +155,20 @@ where &self.executor } - /// Creates a new payload processor. + /// Creates a new payload processor from the launcher-owned [`EngineSharedCaches`]. + /// + /// The facade is destructured here — individual cache handles become private fields. pub fn new( executor: Runtime, evm_config: Evm, config: &TreeConfig, - precompile_cache_map: PrecompileCacheMap>, + shared_caches: EngineSharedCaches, ) -> Self { + let EngineSharedCaches { execution_cache, sparse_trie_cache, precompile_cache_map } = + shared_caches; Self { executor, - execution_cache: Default::default(), + execution_cache, trie_metrics: Default::default(), cross_block_cache_size: config.cross_block_cache_size(), disable_transaction_prewarming: config.disable_prewarming(), @@ -171,7 +176,7 @@ where disable_state_cache: config.disable_state_cache(), precompile_cache_disabled: config.precompile_cache_disabled(), precompile_cache_map, - sparse_state_trie: SharedPreservedSparseTrie::default(), + sparse_state_trie: SharedPreservedSparseTrie::new(sparse_trie_cache), sparse_trie_max_hot_slots: config.sparse_trie_max_hot_slots(), sparse_trie_max_hot_accounts: config.sparse_trie_max_hot_accounts(), disable_sparse_trie_cache_pruning: config.disable_sparse_trie_cache_pruning(), @@ -744,6 +749,52 @@ where } } +/// Launcher-owned facade that groups the caches shared across engine payload processing. +/// +/// This type is created once by [`EngineNodeLauncher`] and threaded through the engine validator +/// build path, following the same ownership pattern as [`ChangesetCache`]. The facade is +/// destructured by [`PayloadProcessor::new()`] — the individual cache handles become private +/// fields and internal access sites remain unchanged. +/// +/// [`EngineNodeLauncher`]: reth_node_builder::EngineNodeLauncher +/// [`ChangesetCache`]: reth_trie_db::ChangesetCache +#[derive(Debug, Clone)] +pub struct EngineSharedCaches { + /// Execution cache handle. + execution_cache: PayloadExecutionCache, + /// Sparse trie cache handle. + sparse_trie_cache: PayloadSparseTrieCache, + /// Precompile cache map. + precompile_cache_map: PrecompileCacheMap>, +} + +impl EngineSharedCaches { + /// Creates a new `EngineSharedCaches` with default caches. + pub fn new() -> Self { + Self { + execution_cache: PayloadExecutionCache::default(), + sparse_trie_cache: PayloadSparseTrieCache::default(), + precompile_cache_map: PrecompileCacheMap::default(), + } + } + + /// Returns a clone of the sparse trie cache handle. + pub fn sparse_trie_cache(&self) -> PayloadSparseTrieCache { + self.sparse_trie_cache.clone() + } + + /// Returns a clone of the precompile cache map. + pub fn precompile_cache_map(&self) -> PrecompileCacheMap> { + self.precompile_cache_map.clone() + } +} + +impl Default for EngineSharedCaches { + fn default() -> Self { + Self::new() + } +} + /// Converts transactions sequentially and sends them to the prewarm and execute channels. fn convert_serial( iter: impl Iterator, @@ -1175,8 +1226,9 @@ mod tests { use super::PayloadExecutionCache; use crate::tree::{ cached_state::{CachedStateMetrics, ExecutionCache, SavedCache}, - payload_processor::{evm_state_to_hashed_post_state, ExecutionEnv, PayloadProcessor}, - precompile_cache::PrecompileCacheMap, + payload_processor::{ + evm_state_to_hashed_post_state, EngineSharedCaches, ExecutionEnv, PayloadProcessor, + }, StateProviderBuilder, TreeConfig, }; use alloy_eips::eip1898::{BlockNumHash, BlockWithParent}; @@ -1288,7 +1340,7 @@ mod tests { reth_tasks::Runtime::test(), EthEvmConfig::new(Arc::new(ChainSpec::default())), &TreeConfig::default(), - PrecompileCacheMap::default(), + EngineSharedCaches::default(), ); let parent_hash = B256::from([1u8; 32]); @@ -1317,7 +1369,7 @@ mod tests { reth_tasks::Runtime::test(), EthEvmConfig::new(Arc::new(ChainSpec::default())), &TreeConfig::default(), - PrecompileCacheMap::default(), + EngineSharedCaches::default(), ); // Setup: populate cache with block 1 @@ -1452,7 +1504,7 @@ mod tests { reth_tasks::Runtime::test(), EthEvmConfig::new(factory.chain_spec()), &TreeConfig::default(), - PrecompileCacheMap::default(), + EngineSharedCaches::default(), ); let provider_factory = BlockchainProvider::new(factory).unwrap(); diff --git a/crates/engine/tree/src/tree/payload_processor/preserved_sparse_trie.rs b/crates/engine/tree/src/tree/payload_processor/preserved_sparse_trie.rs index bfc3430fc61..d7ee15a83e3 100644 --- a/crates/engine/tree/src/tree/payload_processor/preserved_sparse_trie.rs +++ b/crates/engine/tree/src/tree/payload_processor/preserved_sparse_trie.rs @@ -9,14 +9,16 @@ use tracing::debug; /// Type alias for the sparse trie type used in preservation. pub(super) type SparseTrie = SparseStateTrie; -/// Shared handle to a preserved sparse trie that can be reused across payload validations. +/// Public handle to the preserved sparse trie cache. /// -/// This is stored in [`PayloadProcessor`](super::PayloadProcessor) and cloned to pass to -/// [`SparseTrieCacheTask`](super::sparse_trie::SparseTrieCacheTask) for trie reuse. +/// This is a lightweight, cloneable handle that provides access to the sparse trie +/// preserved across payload validations. It is owned by +/// [`EngineSharedCaches`](super::EngineSharedCaches) and consumed by +/// [`PayloadProcessor`](super::PayloadProcessor) during construction. #[derive(Debug, Default, Clone)] -pub(super) struct SharedPreservedSparseTrie(Arc>>); +pub struct PayloadSparseTrieCache(Arc>>); -impl SharedPreservedSparseTrie { +impl PayloadSparseTrieCache { /// Takes the preserved trie if present, leaving `None` in its place. pub(super) fn take(&self) -> Option { self.0.lock().take() @@ -51,6 +53,37 @@ impl SharedPreservedSparseTrie { } } +/// Shared handle to a preserved sparse trie that can be reused across payload validations. +/// +/// This is stored in [`PayloadProcessor`](super::PayloadProcessor) and cloned to pass to +/// [`SparseTrieCacheTask`](super::sparse_trie::SparseTrieCacheTask) for trie reuse. +#[derive(Debug, Default, Clone)] +pub(super) struct SharedPreservedSparseTrie(PayloadSparseTrieCache); + +impl SharedPreservedSparseTrie { + /// Creates a new `SharedPreservedSparseTrie` from a [`PayloadSparseTrieCache`]. + pub(super) fn new(cache: PayloadSparseTrieCache) -> Self { + Self(cache) + } + + /// Takes the preserved trie if present, leaving `None` in its place. + pub(super) fn take(&self) -> Option { + self.0.take() + } + + /// Acquires a guard that blocks `take()` until dropped. + /// Use this before sending the state root result to ensure the next block + /// waits for the trie to be stored. + pub(super) fn lock(&self) -> PreservedTrieGuard<'_> { + self.0.lock() + } + + /// Waits until the sparse trie lock becomes available. + pub(super) fn wait_for_availability(&self) -> std::time::Duration { + self.0.wait_for_availability() + } +} + /// Guard that holds the lock on the preserved trie. /// While held, `take()` will block. Call `store()` to save the trie before dropping. pub(super) struct PreservedTrieGuard<'a>(parking_lot::MutexGuard<'a, Option>); diff --git a/crates/engine/tree/src/tree/payload_validator.rs b/crates/engine/tree/src/tree/payload_validator.rs index d246d2c1496..942edcf8a5c 100644 --- a/crates/engine/tree/src/tree/payload_validator.rs +++ b/crates/engine/tree/src/tree/payload_validator.rs @@ -4,7 +4,7 @@ use crate::tree::{ cached_state::{CacheStats, CachedStateProvider}, error::{InsertBlockError, InsertBlockErrorKind, InsertPayloadError}, instrumented_state::{InstrumentedStateProvider, StateProviderStats}, - payload_processor::PayloadProcessor, + payload_processor::{EngineSharedCaches, PayloadProcessor}, precompile_cache::{CachedPrecompile, CachedPrecompileMetrics, PrecompileCacheMap}, sparse_trie::StateRootComputeOutcome, CacheWaitDurations, EngineApiMetrics, EngineApiTreeState, ExecutionEnv, PayloadHandle, @@ -192,14 +192,11 @@ where invalid_block_hook: Box>, changeset_cache: ChangesetCache, runtime: reth_tasks::Runtime, + shared_caches: EngineSharedCaches, ) -> Self { - let precompile_cache_map = PrecompileCacheMap::default(); - let payload_processor = PayloadProcessor::new( - runtime.clone(), - evm_config.clone(), - &config, - precompile_cache_map.clone(), - ); + let precompile_cache_map = shared_caches.precompile_cache_map(); + let payload_processor = + PayloadProcessor::new(runtime.clone(), evm_config.clone(), &config, shared_caches); Self { provider, consensus, diff --git a/crates/engine/tree/src/tree/tests.rs b/crates/engine/tree/src/tree/tests.rs index 5dba727ec23..c436bf7a1cd 100644 --- a/crates/engine/tree/src/tree/tests.rs +++ b/crates/engine/tree/src/tree/tests.rs @@ -205,6 +205,7 @@ impl TestHarness { Box::new(NoopInvalidBlockHook::default()), changeset_cache.clone(), reth_tasks::Runtime::test(), + EngineSharedCaches::default(), ); let tree = EngineApiTreeHandler::new( @@ -409,6 +410,7 @@ impl ValidatorTestHarness { Box::new(NoopInvalidBlockHook::default()), changeset_cache, reth_tasks::Runtime::test(), + EngineSharedCaches::default(), ); Self { harness, validator, metrics: TestMetrics::default() } diff --git a/crates/node/builder/src/launch/engine.rs b/crates/node/builder/src/launch/engine.rs index afad49e73e1..189f20b27db 100644 --- a/crates/node/builder/src/launch/engine.rs +++ b/crates/node/builder/src/launch/engine.rs @@ -3,7 +3,10 @@ use crate::{ common::{Attached, LaunchContextWith, WithConfigs}, hooks::NodeHooks, - rpc::{EngineShutdown, EngineValidatorAddOn, EngineValidatorBuilder, RethRpcAddOns, RpcHandle}, + rpc::{ + EngineSharedCaches, EngineShutdown, EngineValidatorAddOn, EngineValidatorBuilder, + RethRpcAddOns, RpcHandle, + }, setup::build_networked_pipeline, AddOns, AddOnsContext, FullNode, LaunchContext, LaunchNode, NodeAdapter, NodeBuilderWithComponents, NodeComponents, NodeComponentsBuilder, NodeHandle, NodeTypesAdapter, @@ -191,10 +194,18 @@ impl EngineNodeLauncher { }; let validator_builder = add_ons.engine_validator_builder(); + // Create launcher-owned engine shared caches (following ChangesetCache pattern) + let shared_caches = EngineSharedCaches::new(); + // Build the engine validator with all required components let engine_validator = validator_builder .clone() - .build_tree_validator(&add_ons_ctx, engine_tree_config.clone(), changeset_cache.clone()) + .build_tree_validator_with_caches( + &add_ons_ctx, + engine_tree_config.clone(), + changeset_cache.clone(), + shared_caches, + ) .await?; // Create the consensus engine stream with optional reorg @@ -205,10 +216,16 @@ impl EngineNodeLauncher { ctx.blockchain_db().clone(), ctx.components().evm_config().clone(), || async { - // Create a separate cache for reorg validator (not shared with main engine) + // Create separate caches for reorg validator (not shared with main engine) let reorg_cache = ChangesetCache::new(); + let reorg_shared_caches = EngineSharedCaches::new(); validator_builder - .build_tree_validator(&add_ons_ctx, engine_tree_config.clone(), reorg_cache) + .build_tree_validator_with_caches( + &add_ons_ctx, + engine_tree_config.clone(), + reorg_cache, + reorg_shared_caches, + ) .await }, node_config.debug.reorg_frequency, diff --git a/crates/node/builder/src/rpc.rs b/crates/node/builder/src/rpc.rs index 0949e768139..ee1fe00843d 100644 --- a/crates/node/builder/src/rpc.rs +++ b/crates/node/builder/src/rpc.rs @@ -2,7 +2,7 @@ pub use jsonrpsee::server::middleware::rpc::{RpcService, RpcServiceBuilder}; use reth_engine_tree::tree::WaitForCaches; -pub use reth_engine_tree::tree::{BasicEngineValidator, EngineValidator}; +pub use reth_engine_tree::tree::{BasicEngineValidator, EngineSharedCaches, EngineValidator}; pub use reth_rpc_builder::{middleware::RethRpcMiddleware, Identity, Stack}; pub use reth_trie_db::ChangesetCache; @@ -1294,6 +1294,22 @@ pub trait EngineValidatorBuilder: Send + Sync + Clone tree_config: TreeConfig, changeset_cache: ChangesetCache, ) -> impl Future> + Send; + + /// Builds the tree validator with explicit launcher-owned cache facade. + /// + /// This is the cache-aware entrypoint that receives an [`EngineSharedCaches`] constructed + /// by the launcher. The default implementation ignores the shared caches and falls back + /// to [`Self::build_tree_validator`]. + fn build_tree_validator_with_caches( + self, + ctx: &AddOnsContext<'_, Node>, + tree_config: TreeConfig, + changeset_cache: ChangesetCache, + shared_caches: EngineSharedCaches, + ) -> impl Future> + Send { + let _ = shared_caches; + self.build_tree_validator(ctx, tree_config, changeset_cache) + } } /// Basic implementation of [`EngineValidatorBuilder`]. @@ -1341,6 +1357,22 @@ where ctx: &AddOnsContext<'_, Node>, tree_config: TreeConfig, changeset_cache: ChangesetCache, + ) -> eyre::Result { + self.build_tree_validator_with_caches( + ctx, + tree_config, + changeset_cache, + EngineSharedCaches::default(), + ) + .await + } + + async fn build_tree_validator_with_caches( + self, + ctx: &AddOnsContext<'_, Node>, + tree_config: TreeConfig, + changeset_cache: ChangesetCache, + shared_caches: EngineSharedCaches, ) -> eyre::Result { let validator = self.payload_validator_builder.build(ctx).await?; let data_dir = ctx.config.datadir.clone().resolve_datadir(ctx.config.chain.chain()); @@ -1355,6 +1387,7 @@ where invalid_block_hook, changeset_cache, ctx.node.task_executor().clone(), + shared_caches, )) } } From 68a7f9b4b98edd46414225838ac0e92ccec9835e Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Mon, 16 Mar 2026 07:06:48 +0000 Subject: [PATCH 02/10] refactor: simplify EngineSharedCaches facade - Remove SharedPreservedSparseTrie wrapper; use PayloadSparseTrieCache directly in PayloadProcessor (zero-value delegation layer) - Make EngineSharedCaches fields pub, remove getter methods - Remove Clone derive from EngineSharedCaches (facade is created and moved, not cloned) - Fix trait inversion: make build_tree_validator_with_caches the required method, provide default for build_tree_validator that forwards with EngineSharedCaches::default() (eliminates silent cache-drop footgun and infinite-recursion trap) - Remove redundant build_tree_validator override from BasicEngineValidatorBuilder (trait default handles it) - Clean up doc comments and rustdoc links --- .../tree/src/tree/payload_processor/mod.rs | 48 +++++------------- .../preserved_sparse_trie.rs | 50 ++++--------------- .../engine/tree/src/tree/payload_validator.rs | 2 +- crates/node/builder/src/launch/engine.rs | 4 +- crates/node/builder/src/rpc.rs | 41 ++++++--------- 5 files changed, 40 insertions(+), 105 deletions(-) diff --git a/crates/engine/tree/src/tree/payload_processor/mod.rs b/crates/engine/tree/src/tree/payload_processor/mod.rs index 31f86863579..d071bed9ab4 100644 --- a/crates/engine/tree/src/tree/payload_processor/mod.rs +++ b/crates/engine/tree/src/tree/payload_processor/mod.rs @@ -61,7 +61,7 @@ pub mod receipt_root_task; pub mod sparse_trie; pub use preserved_sparse_trie::PayloadSparseTrieCache; -use preserved_sparse_trie::{PreservedSparseTrie, SharedPreservedSparseTrie}; +use preserved_sparse_trie::PreservedSparseTrie; /// Default parallelism thresholds to use with the [`ParallelSparseTrie`]. /// @@ -132,7 +132,7 @@ where /// A pruned `SparseStateTrie`, kept around as a cache of already revealed trie nodes and to /// re-use allocated memory. Stored with the block hash it was computed for to enable trie /// preservation across sequential payload validations. - sparse_state_trie: SharedPreservedSparseTrie, + sparse_state_trie: PayloadSparseTrieCache, /// LFU hot-slot capacity: max storage slots retained across prune cycles. sparse_trie_max_hot_slots: usize, /// LFU hot-account capacity: max account addresses retained across prune cycles. @@ -156,8 +156,6 @@ where } /// Creates a new payload processor from the launcher-owned [`EngineSharedCaches`]. - /// - /// The facade is destructured here — individual cache handles become private fields. pub fn new( executor: Runtime, evm_config: Evm, @@ -176,7 +174,7 @@ where disable_state_cache: config.disable_state_cache(), precompile_cache_disabled: config.precompile_cache_disabled(), precompile_cache_map, - sparse_state_trie: SharedPreservedSparseTrie::new(sparse_trie_cache), + sparse_state_trie: sparse_trie_cache, sparse_trie_max_hot_slots: config.sparse_trie_max_hot_slots(), sparse_trie_max_hot_accounts: config.sparse_trie_max_hot_accounts(), disable_sparse_trie_cache_pruning: config.disable_sparse_trie_cache_pruning(), @@ -751,48 +749,28 @@ where /// Launcher-owned facade that groups the caches shared across engine payload processing. /// -/// This type is created once by [`EngineNodeLauncher`] and threaded through the engine validator -/// build path, following the same ownership pattern as [`ChangesetCache`]. The facade is -/// destructured by [`PayloadProcessor::new()`] — the individual cache handles become private -/// fields and internal access sites remain unchanged. -/// -/// [`EngineNodeLauncher`]: reth_node_builder::EngineNodeLauncher -/// [`ChangesetCache`]: reth_trie_db::ChangesetCache -#[derive(Debug, Clone)] +/// Created once by the node launcher and threaded through the engine validator build path, +/// following the same ownership pattern as `ChangesetCache`. The facade is destructured by +/// [`PayloadProcessor::new()`] -- the individual cache handles become private fields and +/// internal access sites remain unchanged. +#[derive(Debug)] pub struct EngineSharedCaches { /// Execution cache handle. - execution_cache: PayloadExecutionCache, + pub execution_cache: PayloadExecutionCache, /// Sparse trie cache handle. - sparse_trie_cache: PayloadSparseTrieCache, + pub sparse_trie_cache: PayloadSparseTrieCache, /// Precompile cache map. - precompile_cache_map: PrecompileCacheMap>, + pub precompile_cache_map: PrecompileCacheMap>, } -impl EngineSharedCaches { - /// Creates a new `EngineSharedCaches` with default caches. - pub fn new() -> Self { +impl Default for EngineSharedCaches { + fn default() -> Self { Self { execution_cache: PayloadExecutionCache::default(), sparse_trie_cache: PayloadSparseTrieCache::default(), precompile_cache_map: PrecompileCacheMap::default(), } } - - /// Returns a clone of the sparse trie cache handle. - pub fn sparse_trie_cache(&self) -> PayloadSparseTrieCache { - self.sparse_trie_cache.clone() - } - - /// Returns a clone of the precompile cache map. - pub fn precompile_cache_map(&self) -> PrecompileCacheMap> { - self.precompile_cache_map.clone() - } -} - -impl Default for EngineSharedCaches { - fn default() -> Self { - Self::new() - } } /// Converts transactions sequentially and sends them to the prewarm and execute channels. diff --git a/crates/engine/tree/src/tree/payload_processor/preserved_sparse_trie.rs b/crates/engine/tree/src/tree/payload_processor/preserved_sparse_trie.rs index d7ee15a83e3..bebf2c80aa7 100644 --- a/crates/engine/tree/src/tree/payload_processor/preserved_sparse_trie.rs +++ b/crates/engine/tree/src/tree/payload_processor/preserved_sparse_trie.rs @@ -9,12 +9,14 @@ use tracing::debug; /// Type alias for the sparse trie type used in preservation. pub(super) type SparseTrie = SparseStateTrie; -/// Public handle to the preserved sparse trie cache. +/// Shared handle to the preserved sparse trie cache. /// -/// This is a lightweight, cloneable handle that provides access to the sparse trie -/// preserved across payload validations. It is owned by -/// [`EngineSharedCaches`](super::EngineSharedCaches) and consumed by -/// [`PayloadProcessor`](super::PayloadProcessor) during construction. +/// Lightweight, cloneable handle backed by `Arc>`. Stored in +/// [`EngineSharedCaches`](super::EngineSharedCaches) and used directly by +/// [`PayloadProcessor`](super::PayloadProcessor). +/// +/// All mutating methods are crate-internal; external consumers only hold the handle +/// for threading it through the builder path. #[derive(Debug, Default, Clone)] pub struct PayloadSparseTrieCache(Arc>>); @@ -33,11 +35,8 @@ impl PayloadSparseTrieCache { /// Waits until the sparse trie lock becomes available. /// - /// This acquires and immediately releases the lock, ensuring that any - /// ongoing operations complete before returning. Useful for synchronization - /// before starting payload processing. - /// - /// Returns the time spent waiting for the lock. + /// Acquires and immediately releases the lock, ensuring any ongoing operations + /// complete before returning. Returns the time spent waiting. pub(super) fn wait_for_availability(&self) -> std::time::Duration { let start = Instant::now(); let _guard = self.0.lock(); @@ -53,37 +52,6 @@ impl PayloadSparseTrieCache { } } -/// Shared handle to a preserved sparse trie that can be reused across payload validations. -/// -/// This is stored in [`PayloadProcessor`](super::PayloadProcessor) and cloned to pass to -/// [`SparseTrieCacheTask`](super::sparse_trie::SparseTrieCacheTask) for trie reuse. -#[derive(Debug, Default, Clone)] -pub(super) struct SharedPreservedSparseTrie(PayloadSparseTrieCache); - -impl SharedPreservedSparseTrie { - /// Creates a new `SharedPreservedSparseTrie` from a [`PayloadSparseTrieCache`]. - pub(super) fn new(cache: PayloadSparseTrieCache) -> Self { - Self(cache) - } - - /// Takes the preserved trie if present, leaving `None` in its place. - pub(super) fn take(&self) -> Option { - self.0.take() - } - - /// Acquires a guard that blocks `take()` until dropped. - /// Use this before sending the state root result to ensure the next block - /// waits for the trie to be stored. - pub(super) fn lock(&self) -> PreservedTrieGuard<'_> { - self.0.lock() - } - - /// Waits until the sparse trie lock becomes available. - pub(super) fn wait_for_availability(&self) -> std::time::Duration { - self.0.wait_for_availability() - } -} - /// Guard that holds the lock on the preserved trie. /// While held, `take()` will block. Call `store()` to save the trie before dropping. pub(super) struct PreservedTrieGuard<'a>(parking_lot::MutexGuard<'a, Option>); diff --git a/crates/engine/tree/src/tree/payload_validator.rs b/crates/engine/tree/src/tree/payload_validator.rs index 942edcf8a5c..26af5cff3fb 100644 --- a/crates/engine/tree/src/tree/payload_validator.rs +++ b/crates/engine/tree/src/tree/payload_validator.rs @@ -194,7 +194,7 @@ where runtime: reth_tasks::Runtime, shared_caches: EngineSharedCaches, ) -> Self { - let precompile_cache_map = shared_caches.precompile_cache_map(); + let precompile_cache_map = shared_caches.precompile_cache_map.clone(); let payload_processor = PayloadProcessor::new(runtime.clone(), evm_config.clone(), &config, shared_caches); Self { diff --git a/crates/node/builder/src/launch/engine.rs b/crates/node/builder/src/launch/engine.rs index 189f20b27db..70815ed4b05 100644 --- a/crates/node/builder/src/launch/engine.rs +++ b/crates/node/builder/src/launch/engine.rs @@ -195,7 +195,7 @@ impl EngineNodeLauncher { let validator_builder = add_ons.engine_validator_builder(); // Create launcher-owned engine shared caches (following ChangesetCache pattern) - let shared_caches = EngineSharedCaches::new(); + let shared_caches = EngineSharedCaches::default(); // Build the engine validator with all required components let engine_validator = validator_builder @@ -218,7 +218,7 @@ impl EngineNodeLauncher { || async { // Create separate caches for reorg validator (not shared with main engine) let reorg_cache = ChangesetCache::new(); - let reorg_shared_caches = EngineSharedCaches::new(); + let reorg_shared_caches = EngineSharedCaches::default(); validator_builder .build_tree_validator_with_caches( &add_ons_ctx, diff --git a/crates/node/builder/src/rpc.rs b/crates/node/builder/src/rpc.rs index ee1fe00843d..0fd2195d879 100644 --- a/crates/node/builder/src/rpc.rs +++ b/crates/node/builder/src/rpc.rs @@ -1285,30 +1285,34 @@ pub trait EngineValidatorBuilder: Send + Sync + Clone type EngineValidator: EngineValidator<::Payload, ::Primitives> + WaitForCaches; - /// Builds the tree validator for the consensus engine. + /// Builds the tree validator with launcher-owned cache facade. /// - /// Returns a validator that handles block execution, state validation, and fork handling. - fn build_tree_validator( + /// This is the primary entrypoint called by the launcher. It receives an + /// [`EngineSharedCaches`] so the launcher retains ownership of cache handles. + fn build_tree_validator_with_caches( self, ctx: &AddOnsContext<'_, Node>, tree_config: TreeConfig, changeset_cache: ChangesetCache, + shared_caches: EngineSharedCaches, ) -> impl Future> + Send; - /// Builds the tree validator with explicit launcher-owned cache facade. + /// Convenience entrypoint that creates default shared caches. /// - /// This is the cache-aware entrypoint that receives an [`EngineSharedCaches`] constructed - /// by the launcher. The default implementation ignores the shared caches and falls back - /// to [`Self::build_tree_validator`]. - fn build_tree_validator_with_caches( + /// Delegates to [`Self::build_tree_validator_with_caches`] with + /// [`EngineSharedCaches::default()`]. + fn build_tree_validator( self, ctx: &AddOnsContext<'_, Node>, tree_config: TreeConfig, changeset_cache: ChangesetCache, - shared_caches: EngineSharedCaches, ) -> impl Future> + Send { - let _ = shared_caches; - self.build_tree_validator(ctx, tree_config, changeset_cache) + self.build_tree_validator_with_caches( + ctx, + tree_config, + changeset_cache, + EngineSharedCaches::default(), + ) } } @@ -1352,21 +1356,6 @@ where { type EngineValidator = BasicEngineValidator; - async fn build_tree_validator( - self, - ctx: &AddOnsContext<'_, Node>, - tree_config: TreeConfig, - changeset_cache: ChangesetCache, - ) -> eyre::Result { - self.build_tree_validator_with_caches( - ctx, - tree_config, - changeset_cache, - EngineSharedCaches::default(), - ) - .await - } - async fn build_tree_validator_with_caches( self, ctx: &AddOnsContext<'_, Node>, From 3537f874b487fad651295c8d2cf33525f889af37 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Mon, 16 Mar 2026 07:13:42 +0000 Subject: [PATCH 03/10] chore: replace 'facade' with 'cache bundle' in doc comments --- crates/engine/tree/src/tree/payload_processor/mod.rs | 7 +++---- crates/node/builder/src/rpc.rs | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/crates/engine/tree/src/tree/payload_processor/mod.rs b/crates/engine/tree/src/tree/payload_processor/mod.rs index d071bed9ab4..89b82c07caf 100644 --- a/crates/engine/tree/src/tree/payload_processor/mod.rs +++ b/crates/engine/tree/src/tree/payload_processor/mod.rs @@ -747,12 +747,11 @@ where } } -/// Launcher-owned facade that groups the caches shared across engine payload processing. +/// Launcher-owned cache bundle shared across engine payload processing. /// /// Created once by the node launcher and threaded through the engine validator build path, -/// following the same ownership pattern as `ChangesetCache`. The facade is destructured by -/// [`PayloadProcessor::new()`] -- the individual cache handles become private fields and -/// internal access sites remain unchanged. +/// following the same ownership pattern as `ChangesetCache`. Destructured by +/// [`PayloadProcessor::new()`] into private fields. #[derive(Debug)] pub struct EngineSharedCaches { /// Execution cache handle. diff --git a/crates/node/builder/src/rpc.rs b/crates/node/builder/src/rpc.rs index 0fd2195d879..7294d9baf55 100644 --- a/crates/node/builder/src/rpc.rs +++ b/crates/node/builder/src/rpc.rs @@ -1285,7 +1285,7 @@ pub trait EngineValidatorBuilder: Send + Sync + Clone type EngineValidator: EngineValidator<::Payload, ::Primitives> + WaitForCaches; - /// Builds the tree validator with launcher-owned cache facade. + /// Builds the tree validator with launcher-owned shared caches. /// /// This is the primary entrypoint called by the launcher. It receives an /// [`EngineSharedCaches`] so the launcher retains ownership of cache handles. From 4f3f2c91909cf9b25ec7b478e2973908bccc1f09 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Mon, 16 Mar 2026 07:14:43 +0000 Subject: [PATCH 04/10] chore: drop ChangesetCache reference from doc comment --- crates/engine/tree/src/tree/payload_processor/mod.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/engine/tree/src/tree/payload_processor/mod.rs b/crates/engine/tree/src/tree/payload_processor/mod.rs index 89b82c07caf..11d9726648b 100644 --- a/crates/engine/tree/src/tree/payload_processor/mod.rs +++ b/crates/engine/tree/src/tree/payload_processor/mod.rs @@ -749,9 +749,8 @@ where /// Launcher-owned cache bundle shared across engine payload processing. /// -/// Created once by the node launcher and threaded through the engine validator build path, -/// following the same ownership pattern as `ChangesetCache`. Destructured by -/// [`PayloadProcessor::new()`] into private fields. +/// Created once by the node launcher and threaded through the engine validator build path. +/// Destructured by [`PayloadProcessor::new()`] into private fields. #[derive(Debug)] pub struct EngineSharedCaches { /// Execution cache handle. From cbf5bb1b3f972b54457ca87d23d541cf4923c26c Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Mon, 16 Mar 2026 07:19:34 +0000 Subject: [PATCH 05/10] revert: keep SharedPreservedSparseTrie name --- crates/engine/tree/src/tree/payload_processor/mod.rs | 8 ++++---- .../src/tree/payload_processor/preserved_sparse_trie.rs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/engine/tree/src/tree/payload_processor/mod.rs b/crates/engine/tree/src/tree/payload_processor/mod.rs index 11d9726648b..3de33653456 100644 --- a/crates/engine/tree/src/tree/payload_processor/mod.rs +++ b/crates/engine/tree/src/tree/payload_processor/mod.rs @@ -60,7 +60,7 @@ pub mod prewarm; pub mod receipt_root_task; pub mod sparse_trie; -pub use preserved_sparse_trie::PayloadSparseTrieCache; +pub use preserved_sparse_trie::SharedPreservedSparseTrie; use preserved_sparse_trie::PreservedSparseTrie; /// Default parallelism thresholds to use with the [`ParallelSparseTrie`]. @@ -132,7 +132,7 @@ where /// A pruned `SparseStateTrie`, kept around as a cache of already revealed trie nodes and to /// re-use allocated memory. Stored with the block hash it was computed for to enable trie /// preservation across sequential payload validations. - sparse_state_trie: PayloadSparseTrieCache, + sparse_state_trie: SharedPreservedSparseTrie, /// LFU hot-slot capacity: max storage slots retained across prune cycles. sparse_trie_max_hot_slots: usize, /// LFU hot-account capacity: max account addresses retained across prune cycles. @@ -756,7 +756,7 @@ pub struct EngineSharedCaches { /// Execution cache handle. pub execution_cache: PayloadExecutionCache, /// Sparse trie cache handle. - pub sparse_trie_cache: PayloadSparseTrieCache, + pub sparse_trie_cache: SharedPreservedSparseTrie, /// Precompile cache map. pub precompile_cache_map: PrecompileCacheMap>, } @@ -765,7 +765,7 @@ impl Default for EngineSharedCaches { fn default() -> Self { Self { execution_cache: PayloadExecutionCache::default(), - sparse_trie_cache: PayloadSparseTrieCache::default(), + sparse_trie_cache: SharedPreservedSparseTrie::default(), precompile_cache_map: PrecompileCacheMap::default(), } } diff --git a/crates/engine/tree/src/tree/payload_processor/preserved_sparse_trie.rs b/crates/engine/tree/src/tree/payload_processor/preserved_sparse_trie.rs index bebf2c80aa7..ebf9340b168 100644 --- a/crates/engine/tree/src/tree/payload_processor/preserved_sparse_trie.rs +++ b/crates/engine/tree/src/tree/payload_processor/preserved_sparse_trie.rs @@ -18,9 +18,9 @@ pub(super) type SparseTrie = SparseStateTrie>>); +pub struct SharedPreservedSparseTrie(Arc>>); -impl PayloadSparseTrieCache { +impl SharedPreservedSparseTrie { /// Takes the preserved trie if present, leaving `None` in its place. pub(super) fn take(&self) -> Option { self.0.lock().take() From 28dbd1c4de2634244952f1c62b5c4d628ffc45aa Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Mon, 16 Mar 2026 07:34:11 +0000 Subject: [PATCH 06/10] fix: import ordering for cargo fmt --- crates/engine/tree/src/tree/payload_processor/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/engine/tree/src/tree/payload_processor/mod.rs b/crates/engine/tree/src/tree/payload_processor/mod.rs index 3de33653456..bd97b8adad5 100644 --- a/crates/engine/tree/src/tree/payload_processor/mod.rs +++ b/crates/engine/tree/src/tree/payload_processor/mod.rs @@ -60,8 +60,8 @@ pub mod prewarm; pub mod receipt_root_task; pub mod sparse_trie; -pub use preserved_sparse_trie::SharedPreservedSparseTrie; use preserved_sparse_trie::PreservedSparseTrie; +pub use preserved_sparse_trie::SharedPreservedSparseTrie; /// Default parallelism thresholds to use with the [`ParallelSparseTrie`]. /// From 83f92ea8b12fcdd84a71bf584a880fcde6c218e3 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Mon, 16 Mar 2026 07:38:45 +0000 Subject: [PATCH 07/10] refactor: address review feedback - Revert trait inversion: build_tree_validator stays required, build_tree_validator_with_caches is a default that drops caches and forwards (no semver break for external impls) - Add #[non_exhaustive] to EngineSharedCaches (adding fields later won't break external destructuring/construction) - Add Clone derive (pub fields make no-Clone unenforceable) --- .../tree/src/tree/payload_processor/mod.rs | 3 +- crates/node/builder/src/rpc.rs | 39 +++++++++++-------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/crates/engine/tree/src/tree/payload_processor/mod.rs b/crates/engine/tree/src/tree/payload_processor/mod.rs index bd97b8adad5..a3e12cad9b2 100644 --- a/crates/engine/tree/src/tree/payload_processor/mod.rs +++ b/crates/engine/tree/src/tree/payload_processor/mod.rs @@ -751,7 +751,8 @@ where /// /// Created once by the node launcher and threaded through the engine validator build path. /// Destructured by [`PayloadProcessor::new()`] into private fields. -#[derive(Debug)] +#[derive(Debug, Clone)] +#[non_exhaustive] pub struct EngineSharedCaches { /// Execution cache handle. pub execution_cache: PayloadExecutionCache, diff --git a/crates/node/builder/src/rpc.rs b/crates/node/builder/src/rpc.rs index 7294d9baf55..91f32c598b8 100644 --- a/crates/node/builder/src/rpc.rs +++ b/crates/node/builder/src/rpc.rs @@ -1285,34 +1285,26 @@ pub trait EngineValidatorBuilder: Send + Sync + Clone type EngineValidator: EngineValidator<::Payload, ::Primitives> + WaitForCaches; - /// Builds the tree validator with launcher-owned shared caches. - /// - /// This is the primary entrypoint called by the launcher. It receives an - /// [`EngineSharedCaches`] so the launcher retains ownership of cache handles. - fn build_tree_validator_with_caches( + /// Builds the tree validator for the consensus engine. + fn build_tree_validator( self, ctx: &AddOnsContext<'_, Node>, tree_config: TreeConfig, changeset_cache: ChangesetCache, - shared_caches: EngineSharedCaches, ) -> impl Future> + Send; - /// Convenience entrypoint that creates default shared caches. + /// Builds the tree validator with launcher-owned shared caches. /// - /// Delegates to [`Self::build_tree_validator_with_caches`] with - /// [`EngineSharedCaches::default()`]. - fn build_tree_validator( + /// Override this to receive the [`EngineSharedCaches`] created by the launcher. + /// The default ignores `shared_caches` and forwards to [`Self::build_tree_validator`]. + fn build_tree_validator_with_caches( self, ctx: &AddOnsContext<'_, Node>, tree_config: TreeConfig, changeset_cache: ChangesetCache, + _shared_caches: EngineSharedCaches, ) -> impl Future> + Send { - self.build_tree_validator_with_caches( - ctx, - tree_config, - changeset_cache, - EngineSharedCaches::default(), - ) + self.build_tree_validator(ctx, tree_config, changeset_cache) } } @@ -1356,6 +1348,21 @@ where { type EngineValidator = BasicEngineValidator; + async fn build_tree_validator( + self, + ctx: &AddOnsContext<'_, Node>, + tree_config: TreeConfig, + changeset_cache: ChangesetCache, + ) -> eyre::Result { + self.build_tree_validator_with_caches( + ctx, + tree_config, + changeset_cache, + EngineSharedCaches::default(), + ) + .await + } + async fn build_tree_validator_with_caches( self, ctx: &AddOnsContext<'_, Node>, From 40491a82e86aaf577305cc1fbefca03256c735e5 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Mon, 16 Mar 2026 07:41:51 +0000 Subject: [PATCH 08/10] Remove ChangesetCache pattern comment --- crates/node/builder/src/launch/engine.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/node/builder/src/launch/engine.rs b/crates/node/builder/src/launch/engine.rs index 70815ed4b05..1f39abaf6cc 100644 --- a/crates/node/builder/src/launch/engine.rs +++ b/crates/node/builder/src/launch/engine.rs @@ -194,7 +194,6 @@ impl EngineNodeLauncher { }; let validator_builder = add_ons.engine_validator_builder(); - // Create launcher-owned engine shared caches (following ChangesetCache pattern) let shared_caches = EngineSharedCaches::default(); // Build the engine validator with all required components From 2c7fcfca20eb66df41b379665766355b61a66599 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Mon, 16 Mar 2026 09:03:24 +0000 Subject: [PATCH 09/10] chore: add changelog entry --- .changelog/cool-hens-dance.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changelog/cool-hens-dance.md diff --git a/.changelog/cool-hens-dance.md b/.changelog/cool-hens-dance.md new file mode 100644 index 00000000000..692be79250f --- /dev/null +++ b/.changelog/cool-hens-dance.md @@ -0,0 +1,6 @@ +--- +reth-engine-tree: minor +reth-node-builder: minor +--- + +Added `EngineSharedCaches` struct that bundles `PayloadExecutionCache`, `SharedPreservedSparseTrie`, and `PrecompileCacheMap` into a single launcher-owned handle. Updated `PayloadProcessor::new()` and `EngineValidatorBuilder` to accept `EngineSharedCaches`, and added `build_tree_validator_with_caches` to the builder trait with a default fallback to the existing method. From 6da9bfcf9ea0d1b73ad8865171a4d0f4ab3bb4fa Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Mon, 16 Mar 2026 09:20:32 +0000 Subject: [PATCH 10/10] refactor: merge build_tree_validator_with_caches into build_tree_validator --- .changelog/cool-hens-dance.md | 2 +- crates/node/builder/src/launch/engine.rs | 5 ++-- crates/node/builder/src/rpc.rs | 30 +----------------------- 3 files changed, 4 insertions(+), 33 deletions(-) diff --git a/.changelog/cool-hens-dance.md b/.changelog/cool-hens-dance.md index 692be79250f..8e7d8758a27 100644 --- a/.changelog/cool-hens-dance.md +++ b/.changelog/cool-hens-dance.md @@ -3,4 +3,4 @@ reth-engine-tree: minor reth-node-builder: minor --- -Added `EngineSharedCaches` struct that bundles `PayloadExecutionCache`, `SharedPreservedSparseTrie`, and `PrecompileCacheMap` into a single launcher-owned handle. Updated `PayloadProcessor::new()` and `EngineValidatorBuilder` to accept `EngineSharedCaches`, and added `build_tree_validator_with_caches` to the builder trait with a default fallback to the existing method. +Added `EngineSharedCaches` struct that bundles `PayloadExecutionCache`, `SharedPreservedSparseTrie`, and `PrecompileCacheMap` into a single launcher-owned handle. Updated `PayloadProcessor::new()` and `EngineValidatorBuilder::build_tree_validator` to accept `EngineSharedCaches`. diff --git a/crates/node/builder/src/launch/engine.rs b/crates/node/builder/src/launch/engine.rs index 1f39abaf6cc..81955f63399 100644 --- a/crates/node/builder/src/launch/engine.rs +++ b/crates/node/builder/src/launch/engine.rs @@ -196,10 +196,9 @@ impl EngineNodeLauncher { let shared_caches = EngineSharedCaches::default(); - // Build the engine validator with all required components let engine_validator = validator_builder .clone() - .build_tree_validator_with_caches( + .build_tree_validator( &add_ons_ctx, engine_tree_config.clone(), changeset_cache.clone(), @@ -219,7 +218,7 @@ impl EngineNodeLauncher { let reorg_cache = ChangesetCache::new(); let reorg_shared_caches = EngineSharedCaches::default(); validator_builder - .build_tree_validator_with_caches( + .build_tree_validator( &add_ons_ctx, engine_tree_config.clone(), reorg_cache, diff --git a/crates/node/builder/src/rpc.rs b/crates/node/builder/src/rpc.rs index 91f32c598b8..475c693fac0 100644 --- a/crates/node/builder/src/rpc.rs +++ b/crates/node/builder/src/rpc.rs @@ -1291,21 +1291,8 @@ pub trait EngineValidatorBuilder: Send + Sync + Clone ctx: &AddOnsContext<'_, Node>, tree_config: TreeConfig, changeset_cache: ChangesetCache, + shared_caches: EngineSharedCaches, ) -> impl Future> + Send; - - /// Builds the tree validator with launcher-owned shared caches. - /// - /// Override this to receive the [`EngineSharedCaches`] created by the launcher. - /// The default ignores `shared_caches` and forwards to [`Self::build_tree_validator`]. - fn build_tree_validator_with_caches( - self, - ctx: &AddOnsContext<'_, Node>, - tree_config: TreeConfig, - changeset_cache: ChangesetCache, - _shared_caches: EngineSharedCaches, - ) -> impl Future> + Send { - self.build_tree_validator(ctx, tree_config, changeset_cache) - } } /// Basic implementation of [`EngineValidatorBuilder`]. @@ -1353,21 +1340,6 @@ where ctx: &AddOnsContext<'_, Node>, tree_config: TreeConfig, changeset_cache: ChangesetCache, - ) -> eyre::Result { - self.build_tree_validator_with_caches( - ctx, - tree_config, - changeset_cache, - EngineSharedCaches::default(), - ) - .await - } - - async fn build_tree_validator_with_caches( - self, - ctx: &AddOnsContext<'_, Node>, - tree_config: TreeConfig, - changeset_cache: ChangesetCache, shared_caches: EngineSharedCaches, ) -> eyre::Result { let validator = self.payload_validator_builder.build(ctx).await?;