Skip to main content

reth_node_core/args/
engine.rs

1//! clap [`Args`] for engine purposes.
2
3use clap::{
4    builder::{RangedU64ValueParser, Resettable},
5    Args,
6};
7use eyre::ensure;
8use reth_cli_util::{parse_duration_from_secs_or_ms, parsers::format_duration_as_secs_or_ms};
9use reth_engine_primitives::{
10    TreeConfig, DEFAULT_BACKFILL_RUN_THRESHOLD, DEFAULT_INVALID_HEADER_HIT_EVICTION_THRESHOLD,
11    DEFAULT_MULTIPROOF_TASK_CHUNK_SIZE, DEFAULT_NUM_STATE_MASKING_BLOCKS,
12    MIN_PERSISTENCE_BACKPRESSURE_THRESHOLD,
13};
14use std::{sync::OnceLock, time::Duration};
15
16use crate::node_config::{
17    DEFAULT_CROSS_BLOCK_CACHE_SIZE_MB, DEFAULT_MEMORY_BLOCK_BUFFER_TARGET,
18    DEFAULT_PERSISTENCE_THRESHOLD, DEFAULT_RESERVED_CPU_CORES,
19};
20
21/// Global static engine defaults
22static ENGINE_DEFAULTS: OnceLock<DefaultEngineValues> = OnceLock::new();
23
24/// Default values for engine that can be customized
25///
26/// Global defaults can be set via [`DefaultEngineValues::try_init`].
27#[derive(Debug, Clone)]
28pub struct DefaultEngineValues {
29    persistence_threshold: u64,
30    persistence_backpressure_threshold: u64,
31    num_state_masking_blocks: u64,
32    memory_block_buffer_target: u64,
33    backfill_run_threshold: u64,
34    invalid_header_hit_eviction_threshold: u8,
35    state_cache_disabled: bool,
36    prewarming_disabled: bool,
37    txpool_prewarming_enabled: bool,
38    sender_recovery_cache_enabled: bool,
39    state_provider_metrics: bool,
40    cross_block_cache_size: usize,
41    state_root_task_compare_updates: bool,
42    accept_execution_requests_hash: bool,
43    multiproof_chunk_size: usize,
44    reserved_cpu_cores: usize,
45    precompile_cache_disabled: bool,
46    state_root_fallback: bool,
47    always_process_payload_attributes_on_canonical_head: bool,
48    allow_unwind_canonical_header: bool,
49    storage_worker_count: Option<usize>,
50    account_worker_count: Option<usize>,
51    prewarming_threads: Option<usize>,
52    cache_metrics_disabled: bool,
53    slow_block_threshold: Option<Duration>,
54    disable_sparse_trie_cache_pruning: bool,
55    state_root_task_timeout: Option<String>,
56    share_execution_cache_with_payload_builder: bool,
57    share_sparse_trie_with_payload_builder: bool,
58    suppress_persistence_during_build: bool,
59    bal_parallel_execution_disabled: bool,
60    bal_parallel_state_root_disabled: bool,
61}
62
63impl DefaultEngineValues {
64    /// Initialize the global engine defaults with this configuration
65    pub fn try_init(self) -> Result<(), Self> {
66        ENGINE_DEFAULTS.set(self)
67    }
68
69    /// Get a reference to the global engine defaults
70    pub fn get_global() -> &'static Self {
71        ENGINE_DEFAULTS.get_or_init(Self::default)
72    }
73
74    /// Set the default persistence threshold
75    pub const fn with_persistence_threshold(mut self, v: u64) -> Self {
76        self.persistence_threshold = v;
77        self
78    }
79
80    /// Set the minimum default persistence backpressure threshold.
81    pub const fn with_persistence_backpressure_threshold(mut self, v: u64) -> Self {
82        self.persistence_backpressure_threshold = v;
83        self
84    }
85
86    /// Set the default number of state masking blocks.
87    pub const fn with_num_state_masking_blocks(mut self, v: u64) -> Self {
88        self.num_state_masking_blocks = v;
89        self
90    }
91
92    /// Set the default memory block buffer target
93    pub const fn with_memory_block_buffer_target(mut self, v: u64) -> Self {
94        self.memory_block_buffer_target = v;
95        self
96    }
97
98    /// Set the default backfill run threshold
99    pub const fn with_backfill_run_threshold(mut self, v: u64) -> Self {
100        self.backfill_run_threshold = v;
101        self
102    }
103
104    /// Set the invalid header cache hit eviction threshold
105    pub const fn with_invalid_header_hit_eviction_threshold(mut self, v: u8) -> Self {
106        self.invalid_header_hit_eviction_threshold = v;
107        self
108    }
109
110    /// Set whether to disable state cache by default
111    pub const fn with_state_cache_disabled(mut self, v: bool) -> Self {
112        self.state_cache_disabled = v;
113        self
114    }
115
116    /// Set whether to disable prewarming by default
117    pub const fn with_prewarming_disabled(mut self, v: bool) -> Self {
118        self.prewarming_disabled = v;
119        self
120    }
121
122    /// Set whether to enable txpool prewarming by default
123    pub const fn with_txpool_prewarming_enabled(mut self, v: bool) -> Self {
124        self.txpool_prewarming_enabled = v;
125        self
126    }
127
128    /// Set whether to enable sender recovery caching by default
129    pub const fn with_sender_recovery_cache_enabled(mut self, v: bool) -> Self {
130        self.sender_recovery_cache_enabled = v;
131        self
132    }
133
134    /// Set whether to enable state provider metrics by default
135    pub const fn with_state_provider_metrics(mut self, v: bool) -> Self {
136        self.state_provider_metrics = v;
137        self
138    }
139
140    /// Set the default cross-block cache size in MB
141    pub const fn with_cross_block_cache_size(mut self, v: usize) -> Self {
142        self.cross_block_cache_size = v;
143        self
144    }
145
146    /// Set whether to compare state root task updates by default
147    pub const fn with_state_root_task_compare_updates(mut self, v: bool) -> Self {
148        self.state_root_task_compare_updates = v;
149        self
150    }
151
152    /// Set whether to accept execution requests hash by default
153    pub const fn with_accept_execution_requests_hash(mut self, v: bool) -> Self {
154        self.accept_execution_requests_hash = v;
155        self
156    }
157
158    /// Set the default multiproof chunk size
159    pub const fn with_multiproof_chunk_size(mut self, v: usize) -> Self {
160        self.multiproof_chunk_size = v;
161        self
162    }
163
164    /// Set the default number of reserved CPU cores
165    pub const fn with_reserved_cpu_cores(mut self, v: usize) -> Self {
166        self.reserved_cpu_cores = v;
167        self
168    }
169
170    /// Set whether to disable precompile cache by default
171    pub const fn with_precompile_cache_disabled(mut self, v: bool) -> Self {
172        self.precompile_cache_disabled = v;
173        self
174    }
175
176    /// Set whether to enable state root fallback by default
177    pub const fn with_state_root_fallback(mut self, v: bool) -> Self {
178        self.state_root_fallback = v;
179        self
180    }
181
182    /// Set whether to always process payload attributes on canonical head by default
183    pub const fn with_always_process_payload_attributes_on_canonical_head(
184        mut self,
185        v: bool,
186    ) -> Self {
187        self.always_process_payload_attributes_on_canonical_head = v;
188        self
189    }
190
191    /// Set whether to allow unwinding canonical header by default
192    pub const fn with_allow_unwind_canonical_header(mut self, v: bool) -> Self {
193        self.allow_unwind_canonical_header = v;
194        self
195    }
196
197    /// Set the default storage worker count
198    pub const fn with_storage_worker_count(mut self, v: Option<usize>) -> Self {
199        self.storage_worker_count = v;
200        self
201    }
202
203    /// Set the default account worker count
204    pub const fn with_account_worker_count(mut self, v: Option<usize>) -> Self {
205        self.account_worker_count = v;
206        self
207    }
208
209    /// Set the default prewarming thread count
210    pub const fn with_prewarming_threads(mut self, v: Option<usize>) -> Self {
211        self.prewarming_threads = v;
212        self
213    }
214
215    /// Set whether to disable cache metrics by default
216    pub const fn with_cache_metrics_disabled(mut self, v: bool) -> Self {
217        self.cache_metrics_disabled = v;
218        self
219    }
220
221    /// Set the default slow block threshold.
222    pub const fn with_slow_block_threshold(mut self, v: Option<Duration>) -> Self {
223        self.slow_block_threshold = v;
224        self
225    }
226
227    /// Set whether to disable sparse trie cache pruning by default
228    pub const fn with_disable_sparse_trie_cache_pruning(mut self, v: bool) -> Self {
229        self.disable_sparse_trie_cache_pruning = v;
230        self
231    }
232
233    /// Set the default state root task timeout
234    pub fn with_state_root_task_timeout(mut self, v: Option<String>) -> Self {
235        self.state_root_task_timeout = v;
236        self
237    }
238
239    /// Set whether to share the execution cache with the payload builder by default
240    pub const fn with_share_execution_cache_with_payload_builder(mut self, v: bool) -> Self {
241        self.share_execution_cache_with_payload_builder = v;
242        self
243    }
244
245    /// Set whether to share the sparse trie with the payload builder by default
246    pub const fn with_share_sparse_trie_with_payload_builder(mut self, v: bool) -> Self {
247        self.share_sparse_trie_with_payload_builder = v;
248        self
249    }
250
251    /// Set whether to suppress persistence during payload building by default
252    pub const fn with_suppress_persistence_during_build(mut self, v: bool) -> Self {
253        self.suppress_persistence_during_build = v;
254        self
255    }
256
257    /// Set whether to disable BAL-based parallel execution by default
258    pub const fn with_bal_parallel_execution_disabled(mut self, v: bool) -> Self {
259        self.bal_parallel_execution_disabled = v;
260        self
261    }
262
263    /// Set whether to disable BAL-driven parallel state root by default
264    pub const fn with_bal_parallel_state_root_disabled(mut self, v: bool) -> Self {
265        self.bal_parallel_state_root_disabled = v;
266        self
267    }
268}
269
270impl Default for DefaultEngineValues {
271    fn default() -> Self {
272        Self {
273            persistence_threshold: DEFAULT_PERSISTENCE_THRESHOLD,
274            persistence_backpressure_threshold: MIN_PERSISTENCE_BACKPRESSURE_THRESHOLD,
275            num_state_masking_blocks: DEFAULT_NUM_STATE_MASKING_BLOCKS,
276            memory_block_buffer_target: DEFAULT_MEMORY_BLOCK_BUFFER_TARGET,
277            backfill_run_threshold: DEFAULT_BACKFILL_RUN_THRESHOLD,
278            invalid_header_hit_eviction_threshold: DEFAULT_INVALID_HEADER_HIT_EVICTION_THRESHOLD,
279            state_cache_disabled: false,
280            prewarming_disabled: false,
281            txpool_prewarming_enabled: false,
282            sender_recovery_cache_enabled: true,
283            state_provider_metrics: false,
284            cross_block_cache_size: DEFAULT_CROSS_BLOCK_CACHE_SIZE_MB,
285            state_root_task_compare_updates: false,
286            accept_execution_requests_hash: false,
287            multiproof_chunk_size: DEFAULT_MULTIPROOF_TASK_CHUNK_SIZE,
288            reserved_cpu_cores: DEFAULT_RESERVED_CPU_CORES,
289            precompile_cache_disabled: false,
290            state_root_fallback: false,
291            always_process_payload_attributes_on_canonical_head: false,
292            allow_unwind_canonical_header: false,
293            storage_worker_count: None,
294            account_worker_count: None,
295            prewarming_threads: None,
296            cache_metrics_disabled: false,
297            slow_block_threshold: None,
298            disable_sparse_trie_cache_pruning: false,
299            state_root_task_timeout: Some("4s".to_string()),
300            share_execution_cache_with_payload_builder: false,
301            share_sparse_trie_with_payload_builder: false,
302            suppress_persistence_during_build: false,
303            bal_parallel_execution_disabled: false,
304            bal_parallel_state_root_disabled: false,
305        }
306    }
307}
308
309fn default_persistence_backpressure_threshold(persistence_threshold: u64) -> u64 {
310    DefaultEngineValues::get_global()
311        .persistence_backpressure_threshold
312        .max(persistence_threshold.saturating_mul(2))
313}
314
315/// Parameters for configuring the engine driver.
316#[derive(Debug, Clone, Args, PartialEq, Eq)]
317#[command(next_help_heading = "Engine")]
318pub struct EngineArgs {
319    /// Configure persistence threshold for the engine. This determines how many canonical blocks
320    /// must be in-memory, ahead of the last persisted block, before flushing canonical blocks to
321    /// disk again.
322    ///
323    /// To persist blocks as fast as the node receives them, set this value to zero. This disables
324    /// state masking and causes more frequent DB writes.
325    #[arg(
326        long = "engine.persistence-threshold",
327        env = "RETH_ENGINE_PERSISTENCE_THRESHOLD",
328        default_value_t = DefaultEngineValues::get_global().persistence_threshold
329    )]
330    pub persistence_threshold: u64,
331
332    /// Configure the maximum number of blocks beyond the in-memory buffer target that may await
333    /// persistence before engine API processing stalls.
334    ///
335    /// If omitted, this defaults to the larger of the minimum backpressure threshold and twice
336    /// `--engine.persistence-threshold`.
337    ///
338    /// This value must be greater than `--engine.persistence-threshold`.
339    #[arg(long = "engine.persistence-backpressure-threshold")]
340    pub persistence_backpressure_threshold: Option<u64>,
341
342    /// Configure how many of the blocks being persisted should only mask state/trie
343    /// writes instead of durably persisting their state/trie updates in the current cycle.
344    #[arg(
345        long = "engine.num-state-masking-blocks",
346        env = "RETH_ENGINE_NUM_STATE_MASKING_BLOCKS",
347        default_value_t = DefaultEngineValues::get_global().num_state_masking_blocks
348    )]
349    pub num_state_masking_blocks: u64,
350
351    /// Configure the target number of blocks to keep in memory.
352    ///
353    /// If omitted, this defaults to the lesser of `--engine.persistence-threshold` and the
354    /// configured default memory block buffer target.
355    #[arg(long = "engine.memory-block-buffer-target")]
356    pub memory_block_buffer_target: Option<u64>,
357
358    /// Configure the largest gap, in blocks, between the local head and the forkchoice head that
359    /// is closed by downloading the missing blocks directly. Larger gaps trigger a full pipeline
360    /// (backfill) run instead.
361    ///
362    /// On chains with short block times a pipeline run may take longer than it takes the chain to
363    /// produce another gap of this size, in which case raising this value lets the node catch up
364    /// via live sync. Values above 1024 are clamped to the peer header response limit.
365    #[arg(
366        long = "engine.backfill-threshold",
367        env = "RETH_ENGINE_BACKFILL_THRESHOLD",
368        default_value_t = DefaultEngineValues::get_global().backfill_run_threshold
369    )]
370    pub backfill_run_threshold: u64,
371
372    /// Configure how many cache hits an invalid header can accumulate before it is evicted and
373    /// reprocessed.
374    ///
375    /// Set to `0` to effectively disable the cache because entries are evicted on the first
376    /// lookup.
377    #[arg(long = "engine.invalid-header-cache-hit-eviction-threshold", default_value_t = DefaultEngineValues::get_global().invalid_header_hit_eviction_threshold)]
378    pub invalid_header_hit_eviction_threshold: u8,
379
380    /// CAUTION: This CLI flag has no effect anymore, use --engine.state-root-fallback if you
381    /// want to force synchronous state root computation
382    #[arg(long = "engine.legacy-state-root", default_value_t = false, hide = true)]
383    #[deprecated]
384    pub legacy_state_root_task_enabled: bool,
385
386    /// CAUTION: This CLI flag has no effect anymore, use --engine.disable-caching-and-prewarming
387    /// if you want to disable caching and prewarming
388    #[arg(long = "engine.caching-and-prewarming", default_value = "true", hide = true)]
389    #[deprecated]
390    pub caching_and_prewarming_enabled: bool,
391
392    /// Disable state cache
393    #[arg(long = "engine.disable-state-cache", default_value_t = DefaultEngineValues::get_global().state_cache_disabled)]
394    pub state_cache_disabled: bool,
395
396    /// Disable parallel prewarming
397    #[arg(long = "engine.disable-prewarming", alias = "engine.disable-caching-and-prewarming", default_value_t = DefaultEngineValues::get_global().prewarming_disabled)]
398    pub prewarming_disabled: bool,
399
400    /// Enable best-effort txpool transaction prewarming between payloads.
401    #[arg(
402        long = "engine.txpool-prewarming",
403        env = "RETH_ENGINE_TXPOOL_PREWARMING",
404        default_value_t = DefaultEngineValues::get_global().txpool_prewarming_enabled
405    )]
406    pub txpool_prewarming_enabled: bool,
407
408    /// Cache recovered transaction senders across transaction ingress and payload execution.
409    ///
410    /// Enabled by default, use `--engine.sender-recovery-cache=false` to disable it.
411    #[arg(
412        long = "engine.sender-recovery-cache",
413        env = "RETH_ENGINE_SENDER_RECOVERY_CACHE",
414        default_value_t = DefaultEngineValues::get_global().sender_recovery_cache_enabled,
415        num_args = 0..=1,
416        default_missing_value = "true",
417    )]
418    pub sender_recovery_cache_enabled: bool,
419
420    /// CAUTION: This CLI flag has no effect anymore. The parallel sparse trie is always enabled.
421    #[deprecated]
422    #[arg(long = "engine.parallel-sparse-trie", default_value = "true", hide = true)]
423    pub parallel_sparse_trie_enabled: bool,
424
425    /// CAUTION: This CLI flag has no effect anymore. The parallel sparse trie is always enabled.
426    #[deprecated]
427    #[arg(long = "engine.disable-parallel-sparse-trie", default_value = "false", hide = true)]
428    pub parallel_sparse_trie_disabled: bool,
429
430    /// Enable state provider latency metrics. This allows the engine to collect and report stats
431    /// about how long state provider calls took during execution, but this does introduce slight
432    /// overhead to state provider calls.
433    #[arg(long = "engine.state-provider-metrics", default_value_t = DefaultEngineValues::get_global().state_provider_metrics)]
434    pub state_provider_metrics: bool,
435
436    /// Configure the size of cross-block cache in megabytes
437    #[arg(long = "engine.cross-block-cache-size", default_value_t = DefaultEngineValues::get_global().cross_block_cache_size)]
438    pub cross_block_cache_size: usize,
439
440    /// Enable comparing trie updates from the state root task to the trie updates from the regular
441    /// state root calculation.
442    #[arg(long = "engine.state-root-task-compare-updates", default_value_t = DefaultEngineValues::get_global().state_root_task_compare_updates)]
443    pub state_root_task_compare_updates: bool,
444
445    /// Enables accepting requests hash instead of an array of requests in `engine_newPayloadV4`.
446    #[arg(long = "engine.accept-execution-requests-hash", default_value_t = DefaultEngineValues::get_global().accept_execution_requests_hash)]
447    pub accept_execution_requests_hash: bool,
448
449    /// Multiproof task chunk size for proof targets.
450    #[arg(long = "engine.multiproof-chunk-size", default_value_t = DefaultEngineValues::get_global().multiproof_chunk_size, value_parser = RangedU64ValueParser::<usize>::new().range(1..))]
451    pub multiproof_chunk_size: usize,
452
453    /// Configure the number of reserved CPU cores for non-reth processes
454    #[arg(long = "engine.reserved-cpu-cores", default_value_t = DefaultEngineValues::get_global().reserved_cpu_cores)]
455    pub reserved_cpu_cores: usize,
456
457    /// CAUTION: This CLI flag has no effect anymore, use --engine.disable-precompile-cache
458    /// if you want to disable precompile cache
459    #[arg(long = "engine.precompile-cache", default_value = "true", hide = true)]
460    #[deprecated]
461    pub precompile_cache_enabled: bool,
462
463    /// Disable precompile cache
464    #[arg(long = "engine.disable-precompile-cache", default_value_t = DefaultEngineValues::get_global().precompile_cache_disabled)]
465    pub precompile_cache_disabled: bool,
466
467    /// Enable state root fallback, useful for testing
468    #[arg(long = "engine.state-root-fallback", default_value_t = DefaultEngineValues::get_global().state_root_fallback)]
469    pub state_root_fallback: bool,
470
471    /// Always process payload attributes and begin a payload build process even if
472    /// `forkchoiceState.headBlockHash` is already the canonical head or an ancestor. See
473    /// `TreeConfig::always_process_payload_attributes_on_canonical_head` for more details.
474    ///
475    /// Note: This is a no-op on OP Stack.
476    #[arg(
477        long = "engine.always-process-payload-attributes-on-canonical-head",
478        default_value_t = DefaultEngineValues::get_global().always_process_payload_attributes_on_canonical_head
479    )]
480    pub always_process_payload_attributes_on_canonical_head: bool,
481
482    /// Allow unwinding canonical header to ancestor during forkchoice updates.
483    /// See `TreeConfig::unwind_canonical_header` for more details.
484    #[arg(long = "engine.allow-unwind-canonical-header", default_value_t = DefaultEngineValues::get_global().allow_unwind_canonical_header)]
485    pub allow_unwind_canonical_header: bool,
486
487    /// Configure the number of storage proof workers in the Tokio blocking pool.
488    /// If not specified, defaults to 2x available parallelism.
489    #[arg(long = "engine.storage-worker-count", default_value = Resettable::from(DefaultEngineValues::get_global().storage_worker_count.map(|v| v.to_string().into())))]
490    pub storage_worker_count: Option<usize>,
491
492    /// Configure the number of account proof workers in the Tokio blocking pool.
493    /// If not specified, defaults to the same count as storage workers.
494    #[arg(long = "engine.account-worker-count", default_value = Resettable::from(DefaultEngineValues::get_global().account_worker_count.map(|v| v.to_string().into())))]
495    pub account_worker_count: Option<usize>,
496
497    /// Configure the number of prewarming threads.
498    /// If not specified, defaults to available parallelism.
499    #[arg(long = "engine.prewarming-threads", default_value = Resettable::from(DefaultEngineValues::get_global().prewarming_threads.map(|v| v.to_string().into())))]
500    pub prewarming_threads: Option<usize>,
501
502    /// Disable cache metrics recording, which can take up to 50ms with large cached state.
503    #[arg(long = "engine.disable-cache-metrics", default_value_t = DefaultEngineValues::get_global().cache_metrics_disabled)]
504    pub cache_metrics_disabled: bool,
505
506    /// Configure the slow block logging threshold in milliseconds.
507    ///
508    /// When set, blocks that take longer than this threshold to execute will be logged
509    /// with detailed metrics including timing, state operations, and cache statistics.
510    ///
511    /// Set to 0 to log all blocks (useful for debugging/profiling).
512    ///
513    /// When not set, slow block logging is disabled (default).
514    #[arg(long = "engine.slow-block-threshold", value_parser = parse_duration_from_secs_or_ms, value_name = "DURATION", default_value = Resettable::from(DefaultEngineValues::get_global().slow_block_threshold.map(|threshold| format_duration_as_secs_or_ms(threshold).into())))]
515    pub slow_block_threshold: Option<Duration>,
516
517    /// Fully disable sparse trie cache pruning. When set, the cached sparse trie is preserved
518    /// without any node pruning or storage trie eviction between blocks. Useful for benchmarking
519    /// the effects of retaining the full trie cache.
520    #[arg(long = "engine.disable-sparse-trie-cache-pruning", default_value_t = DefaultEngineValues::get_global().disable_sparse_trie_cache_pruning)]
521    pub disable_sparse_trie_cache_pruning: bool,
522
523    /// Configure the timeout for the state root task before spawning a sequential fallback.
524    /// If the state root task takes longer than this, a sequential computation starts in
525    /// parallel and whichever finishes first is used.
526    ///
527    /// --engine.state-root-task-timeout 4s
528    /// --engine.state-root-task-timeout 400ms
529    ///
530    /// Set to 0s to disable.
531    #[arg(
532        long = "engine.state-root-task-timeout",
533        value_parser = humantime::parse_duration,
534        default_value = DefaultEngineValues::get_global().state_root_task_timeout.as_deref().unwrap_or("4s"),
535    )]
536    pub state_root_task_timeout: Option<Duration>,
537
538    /// Whether to share execution cache with the payload builder.
539    ///
540    /// When enabled, each payload job will get an instance of cross-block execution cache from the
541    /// engine.
542    ///
543    /// Note: this should only be enabled if node would not be requested to process any payloads in
544    /// parallel with payload building.
545    #[arg(
546        long = "engine.share-execution-cache-with-payload-builder",
547        default_value_t = DefaultEngineValues::get_global().share_execution_cache_with_payload_builder,
548    )]
549    pub share_execution_cache_with_payload_builder: bool,
550
551    /// Whether to share the sparse trie with the payload builder.
552    ///
553    /// Replaces the payload builder's blocking `state_root_with_updates()` call with the
554    /// sparse trie, computing the state root concurrently with transaction execution.
555    ///
556    /// The engine and payload builder contend for the same trie — if a builder task is
557    /// still running when `newPayload` arrives, the engine will block until the trie is
558    /// stored back.
559    ///
560    /// The builder also anchors the trie at the built block's state root, so if the next
561    /// `newPayload` is not on top of that block, the trie cache is invalidated and cleared.
562    #[arg(
563        long = "engine.share-sparse-trie-with-payload-builder",
564        default_value_t = DefaultEngineValues::get_global().share_sparse_trie_with_payload_builder,
565    )]
566    pub share_sparse_trie_with_payload_builder: bool,
567
568    /// Suppress persistence while building a payload.
569    ///
570    /// When enabled, persistence cycles are deferred while a payload build is active. Useful on
571    /// chains with short block times where persistence I/O can interfere with block building
572    /// latency.
573    #[arg(
574        long = "engine.suppress-persistence-during-build",
575        default_value_t = DefaultEngineValues::get_global().suppress_persistence_during_build,
576    )]
577    pub suppress_persistence_during_build: bool,
578
579    /// Disable BAL (Block Access List, EIP-7928) based parallel execution.
580    #[arg(long = "engine.disable-bal-parallel-execution", default_value_t = DefaultEngineValues::get_global().bal_parallel_execution_disabled)]
581    pub bal_parallel_execution_disabled: bool,
582
583    /// Disable BAL-driven parallel state root computation. This is only valid together with
584    /// `--engine.disable-bal-parallel-execution`.
585    #[arg(long = "engine.disable-bal-parallel-state-root", default_value_t = DefaultEngineValues::get_global().bal_parallel_state_root_disabled)]
586    pub bal_parallel_state_root_disabled: bool,
587
588    /// Disable BAL (Block Access List) storage prefetch IO during prewarming. When set, BAL
589    /// storage slots are not read into the execution cache.
590    #[arg(long = "engine.disable-bal-batch-io", default_value_t = false)]
591    pub disable_bal_batch_io: bool,
592
593    /// Add random jitter before each proof computation (trie-debug only).
594    /// Each proof worker sleeps for a random duration up to this value before
595    /// starting work. Useful for stress-testing timing-sensitive proof logic.
596    ///
597    /// --engine.proof-jitter 100ms
598    /// --engine.proof-jitter 1s
599    #[cfg(feature = "trie-debug")]
600    #[arg(
601        long = "engine.proof-jitter",
602        value_parser = humantime::parse_duration,
603    )]
604    pub proof_jitter: Option<Duration>,
605}
606
607#[allow(deprecated)]
608impl Default for EngineArgs {
609    fn default() -> Self {
610        let DefaultEngineValues {
611            persistence_threshold,
612            persistence_backpressure_threshold: _,
613            num_state_masking_blocks,
614            memory_block_buffer_target: _,
615            backfill_run_threshold,
616            invalid_header_hit_eviction_threshold,
617            state_cache_disabled,
618            prewarming_disabled,
619            txpool_prewarming_enabled,
620            sender_recovery_cache_enabled,
621            state_provider_metrics,
622            cross_block_cache_size,
623            state_root_task_compare_updates,
624            accept_execution_requests_hash,
625            multiproof_chunk_size,
626            reserved_cpu_cores,
627            precompile_cache_disabled,
628            state_root_fallback,
629            always_process_payload_attributes_on_canonical_head,
630            allow_unwind_canonical_header,
631            storage_worker_count,
632            account_worker_count,
633            prewarming_threads,
634            cache_metrics_disabled,
635            slow_block_threshold,
636            disable_sparse_trie_cache_pruning,
637            state_root_task_timeout,
638            share_execution_cache_with_payload_builder,
639            share_sparse_trie_with_payload_builder,
640            suppress_persistence_during_build,
641            bal_parallel_execution_disabled,
642            bal_parallel_state_root_disabled,
643        } = DefaultEngineValues::get_global().clone();
644        Self {
645            persistence_threshold,
646            persistence_backpressure_threshold: None,
647            num_state_masking_blocks,
648            memory_block_buffer_target: None,
649            backfill_run_threshold,
650            invalid_header_hit_eviction_threshold,
651            state_root_task_compare_updates,
652            legacy_state_root_task_enabled: false,
653            caching_and_prewarming_enabled: true,
654            state_cache_disabled,
655            prewarming_disabled,
656            txpool_prewarming_enabled,
657            sender_recovery_cache_enabled,
658            parallel_sparse_trie_enabled: true,
659            parallel_sparse_trie_disabled: false,
660            state_provider_metrics,
661            cross_block_cache_size,
662            accept_execution_requests_hash,
663            multiproof_chunk_size,
664            reserved_cpu_cores,
665            precompile_cache_enabled: true,
666            precompile_cache_disabled,
667            state_root_fallback,
668            always_process_payload_attributes_on_canonical_head,
669            allow_unwind_canonical_header,
670            storage_worker_count,
671            account_worker_count,
672            prewarming_threads,
673            cache_metrics_disabled,
674            slow_block_threshold,
675            disable_sparse_trie_cache_pruning,
676            state_root_task_timeout: state_root_task_timeout
677                .as_deref()
678                .map(|s| humantime::parse_duration(s).expect("valid default duration")),
679            share_execution_cache_with_payload_builder,
680            share_sparse_trie_with_payload_builder,
681            suppress_persistence_during_build,
682            bal_parallel_execution_disabled,
683            bal_parallel_state_root_disabled,
684            disable_bal_batch_io: false,
685            #[cfg(feature = "trie-debug")]
686            proof_jitter: None,
687        }
688    }
689}
690
691impl EngineArgs {
692    /// Returns the effective state masking window, disabled when persistence is immediate.
693    pub const fn num_state_masking_blocks(&self) -> u64 {
694        if self.persistence_threshold == 0 {
695            0
696        } else {
697            self.num_state_masking_blocks
698        }
699    }
700
701    /// Returns the effective memory block buffer target.
702    pub fn memory_block_buffer_target(&self) -> u64 {
703        self.memory_block_buffer_target.unwrap_or_else(|| {
704            self.persistence_threshold
705                .min(DefaultEngineValues::get_global().memory_block_buffer_target)
706        })
707    }
708
709    /// Returns the effective persistence backpressure threshold.
710    pub fn persistence_backpressure_threshold(&self) -> u64 {
711        self.persistence_backpressure_threshold.unwrap_or_else(|| {
712            default_persistence_backpressure_threshold(self.persistence_threshold)
713        })
714    }
715
716    /// Validates cross-field engine arguments.
717    pub fn validate(&self) -> eyre::Result<()> {
718        let persistence_backpressure_threshold = self.persistence_backpressure_threshold();
719        let memory_block_buffer_target = self.memory_block_buffer_target();
720        let num_state_masking_blocks = self.num_state_masking_blocks();
721        ensure!(
722            persistence_backpressure_threshold > self.persistence_threshold,
723            "--engine.persistence-backpressure-threshold ({}) must be greater than --engine.persistence-threshold ({})",
724            persistence_backpressure_threshold,
725            self.persistence_threshold
726        );
727        ensure!(
728            memory_block_buffer_target <= self.persistence_threshold,
729            "--engine.memory-block-buffer-target ({}) must be less than or equal to --engine.persistence-threshold ({})",
730            memory_block_buffer_target,
731            self.persistence_threshold,
732        );
733        ensure!(
734            num_state_masking_blocks == 0 ||
735                matches!(
736                    num_state_masking_blocks.checked_add(memory_block_buffer_target),
737                    Some(window) if window < self.persistence_threshold
738                ),
739            "--engine.num-state-masking-blocks ({}) + --engine.memory-block-buffer-target ({}) must be less than --engine.persistence-threshold ({})",
740            num_state_masking_blocks,
741            memory_block_buffer_target,
742            self.persistence_threshold,
743        );
744        ensure!(
745            !self.state_cache_disabled || !self.txpool_prewarming_enabled,
746            "--engine.txpool-prewarming conflicts with --engine.disable-state-cache"
747        );
748        ensure!(
749            self.bal_parallel_execution_disabled || !self.bal_parallel_state_root_disabled,
750            "--engine.disable-bal-parallel-state-root requires --engine.disable-bal-parallel-execution because BAL parallel execution depends on BAL prewarm state-root updates"
751        );
752        Ok(())
753    }
754
755    /// Creates a [`TreeConfig`] from the engine arguments.
756    pub fn tree_config(&self) -> TreeConfig {
757        #[allow(deprecated)]
758        if self.legacy_state_root_task_enabled {
759            tracing::warn!(target: "reth::cli", "--engine.legacy-state-root has no effect anymore, use --engine.state-root-fallback to force synchronous state root computation");
760        }
761        let config = TreeConfig::default()
762            // Clear the default window before applying overrides that may be smaller than it.
763            .with_persistence_threshold(0)
764            .with_persistence_backpressure_threshold(self.persistence_backpressure_threshold())
765            .with_persistence_threshold(self.persistence_threshold)
766            .with_memory_block_buffer_target(self.memory_block_buffer_target())
767            .with_num_state_masking_blocks(self.num_state_masking_blocks())
768            .with_backfill_run_threshold(self.backfill_run_threshold)
769            .with_invalid_header_hit_eviction_threshold(self.invalid_header_hit_eviction_threshold)
770            .without_state_cache(self.state_cache_disabled)
771            .without_prewarming(self.prewarming_disabled)
772            .with_txpool_prewarming(self.txpool_prewarming_enabled)
773            .with_state_provider_metrics(self.state_provider_metrics)
774            .with_always_compare_trie_updates(self.state_root_task_compare_updates)
775            .with_cross_block_cache_size(self.cross_block_cache_size * 1024 * 1024)
776            .with_multiproof_chunk_size(self.multiproof_chunk_size)
777            .with_reserved_cpu_cores(self.reserved_cpu_cores)
778            .without_precompile_cache(self.precompile_cache_disabled)
779            .with_state_root_fallback(self.state_root_fallback)
780            .with_always_process_payload_attributes_on_canonical_head(
781                self.always_process_payload_attributes_on_canonical_head,
782            )
783            .with_unwind_canonical_header(self.allow_unwind_canonical_header)
784            .without_cache_metrics(self.cache_metrics_disabled)
785            .with_slow_block_threshold(self.slow_block_threshold)
786            .with_disable_sparse_trie_cache_pruning(self.disable_sparse_trie_cache_pruning)
787            .with_state_root_task_timeout(self.state_root_task_timeout.filter(|d| !d.is_zero()))
788            .with_share_execution_cache_with_payload_builder(
789                self.share_execution_cache_with_payload_builder,
790            )
791            .with_share_sparse_trie_with_payload_builder(
792                self.share_sparse_trie_with_payload_builder,
793            )
794            .with_suppress_persistence_during_build(self.suppress_persistence_during_build)
795            .without_bal_parallel_execution(self.bal_parallel_execution_disabled)
796            .without_bal_parallel_state_root(self.bal_parallel_state_root_disabled)
797            .without_bal_batch_io(self.disable_bal_batch_io);
798        #[cfg(feature = "trie-debug")]
799        let config = config.with_proof_jitter(self.proof_jitter);
800        config
801    }
802}
803
804#[cfg(test)]
805mod tests {
806    use super::*;
807    use clap::Parser;
808
809    /// A helper type to parse Args more easily
810    #[derive(Parser)]
811    struct CommandParser<T: Args> {
812        #[command(flatten)]
813        args: T,
814    }
815
816    #[test]
817    fn test_parse_engine_args() {
818        let default_args = EngineArgs::default();
819        let args = CommandParser::<EngineArgs>::parse_from(["reth"]).args;
820        assert_eq!(args, default_args);
821        assert_eq!(args.persistence_threshold, 50);
822        assert_eq!(args.num_state_masking_blocks, 30);
823        assert_eq!(args.memory_block_buffer_target, None);
824        assert_eq!(args.memory_block_buffer_target(), 5);
825        assert_eq!(args.persistence_backpressure_threshold(), 100);
826        args.validate().unwrap();
827        let config = args.tree_config();
828        assert_eq!(config.persistence_threshold(), 50);
829        assert_eq!(config.num_state_masking_blocks(), 30);
830        assert_eq!(config.persistence_backpressure_threshold(), 100);
831    }
832
833    #[test]
834    fn txpool_prewarming_is_disabled_by_default_and_can_be_enabled() {
835        let args = CommandParser::<EngineArgs>::parse_from(["reth"]).args;
836        assert!(!args.txpool_prewarming_enabled);
837        assert!(!args.tree_config().txpool_prewarming());
838
839        let args =
840            CommandParser::<EngineArgs>::parse_from(["reth", "--engine.txpool-prewarming"]).args;
841        assert!(args.txpool_prewarming_enabled);
842        assert!(args.tree_config().txpool_prewarming());
843    }
844
845    #[test]
846    fn validate_rejects_txpool_prewarming_with_disabled_state_cache() {
847        let args = EngineArgs {
848            state_cache_disabled: true,
849            txpool_prewarming_enabled: true,
850            ..EngineArgs::default()
851        };
852
853        let err = args.validate().unwrap_err().to_string();
854        assert!(err.contains("engine.txpool-prewarming"));
855        assert!(err.contains("engine.disable-state-cache"));
856    }
857
858    #[test]
859    fn default_backpressure_threshold_uses_parsed_persistence_args() {
860        let args = CommandParser::<EngineArgs>::parse_from([
861            "reth",
862            "--engine.persistence-threshold",
863            "100",
864            "--engine.memory-block-buffer-target",
865            "50",
866        ])
867        .args;
868
869        assert_eq!(args.persistence_backpressure_threshold(), 200);
870
871        let tree_config = args.tree_config();
872        assert_eq!(tree_config.persistence_threshold(), 100);
873        assert_eq!(tree_config.memory_block_buffer_target(), 50);
874        assert_eq!(tree_config.persistence_backpressure_threshold(), 200);
875    }
876
877    #[test]
878    fn default_backpressure_threshold_uses_minimum_when_larger() {
879        let args = CommandParser::<EngineArgs>::parse_from([
880            "reth",
881            "--engine.persistence-threshold",
882            "4",
883        ])
884        .args;
885
886        assert_eq!(
887            args.persistence_backpressure_threshold(),
888            DefaultEngineValues::get_global().persistence_backpressure_threshold
889        );
890        assert_eq!(args.persistence_backpressure_threshold(), 16);
891    }
892
893    #[test]
894    fn explicit_backpressure_threshold_overrides_calculated_default() {
895        let args = CommandParser::<EngineArgs>::parse_from([
896            "reth",
897            "--engine.persistence-threshold",
898            "100",
899            "--engine.memory-block-buffer-target",
900            "50",
901            "--engine.persistence-backpressure-threshold",
902            "101",
903        ])
904        .args;
905
906        assert_eq!(args.persistence_backpressure_threshold(), 101);
907    }
908
909    #[test]
910    fn sender_recovery_cache_is_enabled_by_default_and_can_be_disabled() {
911        let args = CommandParser::<EngineArgs>::parse_from(["reth"]).args;
912        assert!(args.sender_recovery_cache_enabled);
913
914        let args =
915            CommandParser::<EngineArgs>::parse_from(["reth", "--engine.sender-recovery-cache"])
916                .args;
917        assert!(args.sender_recovery_cache_enabled);
918
919        let args = CommandParser::<EngineArgs>::parse_from([
920            "reth",
921            "--engine.sender-recovery-cache=true",
922        ])
923        .args;
924        assert!(args.sender_recovery_cache_enabled);
925
926        let args = CommandParser::<EngineArgs>::parse_from([
927            "reth",
928            "--engine.sender-recovery-cache=false",
929        ])
930        .args;
931        assert!(!args.sender_recovery_cache_enabled);
932
933        let args = CommandParser::<EngineArgs>::parse_from([
934            "reth",
935            "--engine.sender-recovery-cache",
936            "false",
937        ])
938        .args;
939        assert!(!args.sender_recovery_cache_enabled);
940    }
941
942    #[test]
943    #[allow(deprecated)]
944    fn engine_args() {
945        let args = EngineArgs {
946            persistence_threshold: 100,
947            persistence_backpressure_threshold: Some(101),
948            num_state_masking_blocks: DEFAULT_NUM_STATE_MASKING_BLOCKS,
949            memory_block_buffer_target: Some(50),
950            backfill_run_threshold: 100,
951            invalid_header_hit_eviction_threshold: 7,
952            legacy_state_root_task_enabled: true,
953            caching_and_prewarming_enabled: true,
954            state_cache_disabled: true,
955            prewarming_disabled: true,
956            // conflicts with --engine.disable-state-cache, covered by its own test below
957            txpool_prewarming_enabled: false,
958            sender_recovery_cache_enabled: true,
959            parallel_sparse_trie_enabled: true,
960            parallel_sparse_trie_disabled: false,
961            state_provider_metrics: true,
962            cross_block_cache_size: 256,
963            state_root_task_compare_updates: true,
964            accept_execution_requests_hash: true,
965            multiproof_chunk_size: 512,
966            reserved_cpu_cores: 4,
967            precompile_cache_enabled: true,
968            precompile_cache_disabled: true,
969            state_root_fallback: true,
970            always_process_payload_attributes_on_canonical_head: true,
971            allow_unwind_canonical_header: true,
972            storage_worker_count: Some(16),
973            account_worker_count: Some(8),
974            prewarming_threads: Some(4),
975            cache_metrics_disabled: true,
976            slow_block_threshold: None,
977            disable_sparse_trie_cache_pruning: true,
978            state_root_task_timeout: Some(Duration::from_secs(2)),
979            share_execution_cache_with_payload_builder: false,
980            share_sparse_trie_with_payload_builder: false,
981            suppress_persistence_during_build: false,
982            bal_parallel_execution_disabled: true,
983            bal_parallel_state_root_disabled: true,
984            disable_bal_batch_io: true,
985            #[cfg(feature = "trie-debug")]
986            proof_jitter: None,
987        };
988
989        let parsed_args = CommandParser::<EngineArgs>::parse_from([
990            "reth",
991            "--engine.persistence-threshold",
992            "100",
993            "--engine.persistence-backpressure-threshold",
994            "101",
995            "--engine.memory-block-buffer-target",
996            "50",
997            "--engine.backfill-threshold",
998            "100",
999            "--engine.invalid-header-cache-hit-eviction-threshold",
1000            "7",
1001            "--engine.legacy-state-root",
1002            "--engine.disable-state-cache",
1003            "--engine.disable-prewarming",
1004            "--engine.sender-recovery-cache",
1005            "--engine.state-provider-metrics",
1006            "--engine.cross-block-cache-size",
1007            "256",
1008            "--engine.state-root-task-compare-updates",
1009            "--engine.accept-execution-requests-hash",
1010            "--engine.multiproof-chunk-size",
1011            "512",
1012            "--engine.reserved-cpu-cores",
1013            "4",
1014            "--engine.disable-precompile-cache",
1015            "--engine.state-root-fallback",
1016            "--engine.always-process-payload-attributes-on-canonical-head",
1017            "--engine.allow-unwind-canonical-header",
1018            "--engine.storage-worker-count",
1019            "16",
1020            "--engine.account-worker-count",
1021            "8",
1022            "--engine.prewarming-threads",
1023            "4",
1024            "--engine.disable-cache-metrics",
1025            "--engine.disable-sparse-trie-cache-pruning",
1026            "--engine.state-root-task-timeout",
1027            "2s",
1028            "--engine.disable-bal-parallel-execution",
1029            "--engine.disable-bal-parallel-state-root",
1030            "--engine.disable-bal-batch-io",
1031        ])
1032        .args;
1033
1034        assert_eq!(parsed_args, args);
1035    }
1036
1037    #[test]
1038    fn validate_rejects_invalid_backpressure_threshold() {
1039        let args = EngineArgs {
1040            persistence_threshold: 4,
1041            persistence_backpressure_threshold: Some(4),
1042            ..EngineArgs::default()
1043        };
1044
1045        let err = args.validate().unwrap_err().to_string();
1046        assert!(err.contains("engine.persistence-backpressure-threshold"));
1047        assert!(err.contains("engine.persistence-threshold"));
1048    }
1049
1050    #[test]
1051    fn validate_memory_block_buffer_target() {
1052        let args = EngineArgs {
1053            persistence_threshold: 4,
1054            num_state_masking_blocks: 0,
1055            memory_block_buffer_target: Some(4),
1056            ..EngineArgs::default()
1057        };
1058        args.validate().unwrap();
1059
1060        let args = EngineArgs { memory_block_buffer_target: Some(5), ..args };
1061        let err = args.validate().unwrap_err().to_string();
1062        assert!(err.contains("engine.memory-block-buffer-target"));
1063        assert!(err.contains("engine.persistence-threshold"));
1064    }
1065
1066    #[test]
1067    fn test_parse_num_state_masking_blocks() {
1068        let args = CommandParser::<EngineArgs>::parse_from([
1069            "reth",
1070            "--engine.persistence-threshold",
1071            "13",
1072            "--engine.num-state-masking-blocks",
1073            "7",
1074        ])
1075        .args;
1076
1077        assert_eq!(args.tree_config().num_state_masking_blocks(), 7);
1078    }
1079
1080    #[test]
1081    fn test_parse_backfill_threshold() {
1082        let args = CommandParser::<EngineArgs>::parse_from(["reth"]).args;
1083        let config = args.tree_config();
1084        assert_eq!(config.backfill_run_threshold(), DEFAULT_BACKFILL_RUN_THRESHOLD);
1085
1086        for (threshold, expected) in [("500", 500), ("2048", 1024)] {
1087            let args = CommandParser::<EngineArgs>::parse_from([
1088                "reth",
1089                "--engine.backfill-threshold",
1090                threshold,
1091            ])
1092            .args;
1093            assert_eq!(args.tree_config().backfill_run_threshold(), expected);
1094        }
1095    }
1096
1097    #[test]
1098    fn validate_rejects_state_masking_window_at_or_above_threshold() {
1099        let args = EngineArgs {
1100            persistence_threshold: 4,
1101            num_state_masking_blocks: 2,
1102            memory_block_buffer_target: Some(2),
1103            ..EngineArgs::default()
1104        };
1105
1106        let err = args.validate().unwrap_err().to_string();
1107        assert!(err.contains("engine.num-state-masking-blocks"));
1108    }
1109
1110    #[test]
1111    fn validate_rejects_overflowing_state_masking_window() {
1112        let args = EngineArgs {
1113            persistence_threshold: 7,
1114            num_state_masking_blocks: u64::MAX,
1115            ..EngineArgs::default()
1116        };
1117
1118        let err = args.validate().unwrap_err().to_string();
1119        assert!(err.contains("engine.num-state-masking-blocks"));
1120    }
1121
1122    #[test]
1123    fn default_memory_block_buffer_target_is_bounded_by_persistence_threshold() {
1124        let args = CommandParser::<EngineArgs>::parse_from([
1125            "reth",
1126            "--engine.persistence-threshold",
1127            "4",
1128            "--engine.num-state-masking-blocks",
1129            "0",
1130        ])
1131        .args;
1132
1133        assert_eq!(args.memory_block_buffer_target, None);
1134        assert_eq!(args.memory_block_buffer_target(), 4);
1135        assert_eq!(args.tree_config().memory_block_buffer_target(), 4);
1136        args.validate().unwrap();
1137    }
1138
1139    #[test]
1140    fn explicit_persistence_settings_can_be_lower_than_defaults() {
1141        let args = CommandParser::<EngineArgs>::parse_from([
1142            "reth",
1143            "--engine.persistence-threshold",
1144            "0",
1145            "--engine.persistence-backpressure-threshold",
1146            "1",
1147        ])
1148        .args;
1149
1150        args.validate().unwrap();
1151        let config = args.tree_config();
1152        assert_eq!(config.persistence_threshold(), 0);
1153        assert_eq!(config.num_state_masking_blocks(), 0);
1154        assert_eq!(config.memory_block_buffer_target(), 0);
1155        assert_eq!(config.persistence_backpressure_threshold(), 1);
1156    }
1157
1158    #[test]
1159    fn zero_persistence_threshold_disables_explicit_state_masking() {
1160        let args = EngineArgs {
1161            persistence_threshold: 0,
1162            num_state_masking_blocks: u64::MAX,
1163            ..EngineArgs::default()
1164        };
1165        args.validate().unwrap();
1166        assert_eq!(args.tree_config().num_state_masking_blocks(), 0);
1167    }
1168
1169    #[test]
1170    fn parse_rejects_zero_multiproof_chunk_size() {
1171        let result = CommandParser::<EngineArgs>::try_parse_from([
1172            "reth",
1173            "--engine.multiproof-chunk-size",
1174            "0",
1175        ]);
1176
1177        assert!(result.is_err());
1178    }
1179
1180    #[test]
1181    fn validate_rejects_bal_parallel_execution_without_bal_parallel_state_root() {
1182        let args = EngineArgs {
1183            bal_parallel_execution_disabled: false,
1184            bal_parallel_state_root_disabled: true,
1185            ..EngineArgs::default()
1186        };
1187
1188        let err = args.validate().unwrap_err().to_string();
1189        assert!(err.contains("engine.disable-bal-parallel-state-root"));
1190        assert!(err.contains("engine.disable-bal-parallel-execution"));
1191    }
1192
1193    #[test]
1194    fn test_parse_slow_block_threshold() {
1195        // Test default value (None - disabled)
1196        let args = CommandParser::<EngineArgs>::parse_from(["reth"]).args;
1197        assert_eq!(args.slow_block_threshold, None);
1198
1199        // Test setting to 0 (log all blocks)
1200        let args =
1201            CommandParser::<EngineArgs>::parse_from(["reth", "--engine.slow-block-threshold", "0"])
1202                .args;
1203        assert_eq!(args.slow_block_threshold, Some(Duration::ZERO));
1204
1205        // Test setting to custom value
1206        let args = CommandParser::<EngineArgs>::parse_from([
1207            "reth",
1208            "--engine.slow-block-threshold",
1209            "500",
1210        ])
1211        .args;
1212        assert_eq!(args.slow_block_threshold, Some(Duration::from_secs(500)));
1213
1214        let args = CommandParser::<EngineArgs>::parse_from([
1215            "reth",
1216            "--engine.slow-block-threshold",
1217            "500ms",
1218        ])
1219        .args;
1220        assert_eq!(args.slow_block_threshold, Some(Duration::from_millis(500)));
1221    }
1222
1223    #[test]
1224    fn test_parse_invalid_header_hit_eviction_threshold() {
1225        let args = CommandParser::<EngineArgs>::parse_from(["reth"]).args;
1226        assert_eq!(
1227            args.invalid_header_hit_eviction_threshold,
1228            DEFAULT_INVALID_HEADER_HIT_EVICTION_THRESHOLD
1229        );
1230        assert_eq!(
1231            args.tree_config().invalid_header_hit_eviction_threshold(),
1232            DEFAULT_INVALID_HEADER_HIT_EVICTION_THRESHOLD
1233        );
1234
1235        let args = CommandParser::<EngineArgs>::parse_from([
1236            "reth",
1237            "--engine.invalid-header-cache-hit-eviction-threshold",
1238            "0",
1239        ])
1240        .args;
1241        assert_eq!(args.invalid_header_hit_eviction_threshold, 0);
1242        assert_eq!(args.tree_config().invalid_header_hit_eviction_threshold(), 0);
1243    }
1244
1245    #[test]
1246    fn test_parse_share_sparse_trie_flag() {
1247        let args = CommandParser::<EngineArgs>::parse_from(["reth"]).args;
1248        assert!(!args.share_sparse_trie_with_payload_builder);
1249        assert!(!args.tree_config().share_sparse_trie_with_payload_builder());
1250
1251        let args = CommandParser::<EngineArgs>::parse_from([
1252            "reth",
1253            "--engine.share-sparse-trie-with-payload-builder",
1254        ])
1255        .args;
1256        assert!(args.share_sparse_trie_with_payload_builder);
1257        assert!(args.tree_config().share_sparse_trie_with_payload_builder());
1258    }
1259}