diff --git a/crates/engine/primitives/src/config.rs b/crates/engine/primitives/src/config.rs index 892372bf2ef..3951253f101 100644 --- a/crates/engine/primitives/src/config.rs +++ b/crates/engine/primitives/src/config.rs @@ -170,6 +170,12 @@ pub struct TreeConfig { share_execution_cache_with_payload_builder: bool, /// Whether to share sparse trie with the payload builder. share_sparse_trie_with_payload_builder: bool, + /// Whether to suppress persistence cycles while building a payload. + /// + /// When enabled, persistence is deferred from the moment an FCU with payload attributes + /// arrives until the next FCU without attributes. This avoids persistence I/O competing + /// with block building on latency-sensitive chains. + suppress_persistence_during_build: bool, /// Maximum random jitter applied before each proof computation (trie-debug only). /// When set, each proof worker sleeps for a random duration up to this value /// before starting a proof calculation. @@ -212,6 +218,7 @@ impl Default for TreeConfig { state_root_task_timeout: Some(DEFAULT_STATE_ROOT_TASK_TIMEOUT), share_execution_cache_with_payload_builder: false, share_sparse_trie_with_payload_builder: false, + suppress_persistence_during_build: false, #[cfg(feature = "trie-debug")] proof_jitter: None, } @@ -283,6 +290,7 @@ impl TreeConfig { state_root_task_timeout, share_execution_cache_with_payload_builder, share_sparse_trie_with_payload_builder, + suppress_persistence_during_build: false, #[cfg(feature = "trie-debug")] proof_jitter: None, } @@ -646,6 +654,17 @@ impl TreeConfig { self } + /// Returns whether persistence is suppressed during payload building. + pub const fn suppress_persistence_during_build(&self) -> bool { + self.suppress_persistence_during_build + } + + /// Setter for whether to suppress persistence during payload building. + pub const fn with_suppress_persistence_during_build(mut self, value: bool) -> Self { + self.suppress_persistence_during_build = value; + self + } + /// Returns the proof jitter duration, if configured (trie-debug only). #[cfg(feature = "trie-debug")] pub const fn proof_jitter(&self) -> Option { diff --git a/crates/engine/tree/src/tree/mod.rs b/crates/engine/tree/src/tree/mod.rs index a254f85003d..962d522b3ab 100644 --- a/crates/engine/tree/src/tree/mod.rs +++ b/crates/engine/tree/src/tree/mod.rs @@ -305,6 +305,9 @@ where /// Stored here (not in `ExecutedBlock`) to avoid leaking observability concerns into the block /// type. Entries are removed when blocks are persisted or invalidated. execution_timing_stats: HashMap>, + /// Set when an FCU with payload attributes is received, cleared on the next FCU without. + /// Suppresses persistence cycles during payload building. + building_payload: bool, /// Task runtime for spawning blocking work on named, reusable threads. runtime: reth_tasks::Runtime, } @@ -396,6 +399,7 @@ where evm_config, changeset_cache, execution_timing_stats: HashMap::new(), + building_payload: false, runtime, } } @@ -1112,6 +1116,8 @@ where ) -> ProviderResult> { trace!(target: "engine::tree", ?attrs, "invoked forkchoice update"); + self.building_payload = attrs.is_some() && self.config.suppress_persistence_during_build(); + // Record metrics self.record_forkchoice_metrics(); @@ -2010,9 +2016,13 @@ where } /// Returns true if the canonical chain length minus the last persisted - /// block is greater than or equal to the persistence threshold and - /// backfill is not running. + /// block is greater than or equal to the persistence threshold, + /// backfill is not running, and no payload is currently being built. pub const fn should_persist(&self) -> bool { + if self.building_payload { + return false + } + if !self.backfill_sync_state.is_idle() { // can't persist if backfill is running return false diff --git a/crates/node/core/src/args/engine.rs b/crates/node/core/src/args/engine.rs index f6614f67532..0f79d4d2d59 100644 --- a/crates/node/core/src/args/engine.rs +++ b/crates/node/core/src/args/engine.rs @@ -49,6 +49,7 @@ pub struct DefaultEngineValues { state_root_task_timeout: Option, share_execution_cache_with_payload_builder: bool, share_sparse_trie_with_payload_builder: bool, + suppress_persistence_during_build: bool, } impl DefaultEngineValues { @@ -226,6 +227,12 @@ impl DefaultEngineValues { self.share_sparse_trie_with_payload_builder = v; self } + + /// Set whether to suppress persistence during payload building by default + pub const fn with_suppress_persistence_during_build(mut self, v: bool) -> Self { + self.suppress_persistence_during_build = v; + self + } } impl Default for DefaultEngineValues { @@ -258,6 +265,7 @@ impl Default for DefaultEngineValues { state_root_task_timeout: Some("1s".to_string()), share_execution_cache_with_payload_builder: false, share_sparse_trie_with_payload_builder: false, + suppress_persistence_during_build: false, } } } @@ -459,6 +467,17 @@ pub struct EngineArgs { )] pub share_sparse_trie_with_payload_builder: bool, + /// Suppress persistence while building a payload. + /// + /// When enabled, persistence cycles are deferred from the moment an FCU with payload + /// attributes arrives until the next FCU clears the build. Useful on chains with short + /// block times where persistence I/O can interfere with block building latency. + #[arg( + long = "engine.suppress-persistence-during-build", + default_value_t = DefaultEngineValues::get_global().suppress_persistence_during_build, + )] + pub suppress_persistence_during_build: bool, + /// Add random jitter before each proof computation (trie-debug only). /// Each proof worker sleeps for a random duration up to this value before /// starting work. Useful for stress-testing timing-sensitive proof logic. @@ -504,6 +523,7 @@ impl Default for EngineArgs { state_root_task_timeout, share_execution_cache_with_payload_builder, share_sparse_trie_with_payload_builder, + suppress_persistence_during_build, } = DefaultEngineValues::get_global().clone(); Self { persistence_threshold, @@ -539,6 +559,7 @@ impl Default for EngineArgs { .map(|s| humantime::parse_duration(s).expect("valid default duration")), share_execution_cache_with_payload_builder, share_sparse_trie_with_payload_builder, + suppress_persistence_during_build, #[cfg(feature = "trie-debug")] proof_jitter: None, } @@ -588,7 +609,8 @@ impl EngineArgs { ) .with_share_sparse_trie_with_payload_builder( self.share_sparse_trie_with_payload_builder, - ); + ) + .with_suppress_persistence_during_build(self.suppress_persistence_during_build); #[cfg(feature = "trie-debug")] let config = config.with_proof_jitter(self.proof_jitter); config @@ -649,6 +671,7 @@ mod tests { state_root_task_timeout: Some(Duration::from_secs(2)), share_execution_cache_with_payload_builder: false, share_sparse_trie_with_payload_builder: false, + suppress_persistence_during_build: false, #[cfg(feature = "trie-debug")] proof_jitter: None, }; diff --git a/docs/vocs/docs/pages/cli/reth/node.mdx b/docs/vocs/docs/pages/cli/reth/node.mdx index 39522debcd5..7541cac0483 100644 --- a/docs/vocs/docs/pages/cli/reth/node.mdx +++ b/docs/vocs/docs/pages/cli/reth/node.mdx @@ -1046,6 +1046,11 @@ Engine: The builder also anchors the trie at the built block's state root, so if the next `newPayload` is not on top of that block, the trie cache is invalidated and cleared. + --engine.suppress-persistence-during-build + Suppress persistence while building a payload. + + When enabled, persistence cycles are deferred from the moment an FCU with payload attributes arrives until the next FCU clears the build. Useful on chains with short block times where persistence I/O can interfere with block building latency. + ERA: --era.enable Enable import from ERA1 files