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