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    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    /// Override the available CPU count for node thread pools and parallel execution decisions.
458    /// Does not change CPU affinity. Explicit thread counts take precedence.
459    #[arg(long = "cpu-cores", value_name = "COUNT")]
460    pub cpu_cores: Option<NonZeroUsize>,
461
462    /// CAUTION: This CLI flag has no effect anymore, use --engine.disable-precompile-cache
463    /// if you want to disable precompile cache
464    #[arg(long = "engine.precompile-cache", default_value = "true", hide = true)]
465    #[deprecated]
466    pub precompile_cache_enabled: bool,
467
468    /// Disable precompile cache
469    #[arg(long = "engine.disable-precompile-cache", default_value_t = DefaultEngineValues::get_global().precompile_cache_disabled)]
470    pub precompile_cache_disabled: bool,
471
472    /// Enable state root fallback, useful for testing
473    #[arg(long = "engine.state-root-fallback", default_value_t = DefaultEngineValues::get_global().state_root_fallback)]
474    pub state_root_fallback: bool,
475
476    /// Always process payload attributes and begin a payload build process even if
477    /// `forkchoiceState.headBlockHash` is already the canonical head or an ancestor. See
478    /// `TreeConfig::always_process_payload_attributes_on_canonical_head` for more details.
479    ///
480    /// Note: This is a no-op on OP Stack.
481    #[arg(
482        long = "engine.always-process-payload-attributes-on-canonical-head",
483        default_value_t = DefaultEngineValues::get_global().always_process_payload_attributes_on_canonical_head
484    )]
485    pub always_process_payload_attributes_on_canonical_head: bool,
486
487    /// Allow unwinding canonical header to ancestor during forkchoice updates.
488    /// See `TreeConfig::unwind_canonical_header` for more details.
489    #[arg(long = "engine.allow-unwind-canonical-header", default_value_t = DefaultEngineValues::get_global().allow_unwind_canonical_header)]
490    pub allow_unwind_canonical_header: bool,
491
492    /// Configure the number of storage proof workers spawned for each block.
493    /// If not specified or zero, the count is derived from the block: 2x available parallelism,
494    /// halved for blocks with few transactions and doubled for blocks at or above 100M gas.
495    #[arg(long = "engine.storage-worker-count", default_value = Resettable::from(DefaultEngineValues::get_global().storage_worker_count.map(|v| v.to_string().into())))]
496    pub storage_worker_count: Option<usize>,
497
498    /// Configure the number of account proof workers spawned for each block.
499    /// If not specified or zero, the count is derived from the block the same way as for storage
500    /// workers.
501    #[arg(long = "engine.account-worker-count", default_value = Resettable::from(DefaultEngineValues::get_global().account_worker_count.map(|v| v.to_string().into())))]
502    pub account_worker_count: Option<usize>,
503
504    /// Configure the number of prewarming threads.
505    /// If not specified, defaults to available parallelism.
506    #[arg(long = "engine.prewarming-threads", default_value = Resettable::from(DefaultEngineValues::get_global().prewarming_threads.map(|v| v.to_string().into())))]
507    pub prewarming_threads: Option<usize>,
508
509    /// Disable cache metrics recording, which can take up to 50ms with large cached state.
510    #[arg(long = "engine.disable-cache-metrics", default_value_t = DefaultEngineValues::get_global().cache_metrics_disabled)]
511    pub cache_metrics_disabled: bool,
512
513    /// Configure the slow block logging threshold in milliseconds.
514    ///
515    /// When set, blocks that take longer than this threshold to execute will be logged
516    /// with detailed metrics including timing, state operations, and cache statistics.
517    ///
518    /// Set to 0 to log all blocks (useful for debugging/profiling).
519    ///
520    /// When not set, slow block logging is disabled (default).
521    #[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())))]
522    pub slow_block_threshold: Option<Duration>,
523
524    /// Fully disable sparse trie cache pruning. When set, the cached sparse trie is preserved
525    /// without any node pruning or storage trie eviction between blocks. Useful for benchmarking
526    /// the effects of retaining the full trie cache.
527    #[arg(long = "engine.disable-sparse-trie-cache-pruning", default_value_t = DefaultEngineValues::get_global().disable_sparse_trie_cache_pruning)]
528    pub disable_sparse_trie_cache_pruning: bool,
529
530    /// Configure the timeout for the state root task before spawning a sequential fallback.
531    /// If the state root task takes longer than this, a sequential computation starts in
532    /// parallel and whichever finishes first is used.
533    ///
534    /// --engine.state-root-task-timeout 4s
535    /// --engine.state-root-task-timeout 400ms
536    ///
537    /// Set to 0s to disable.
538    #[arg(
539        long = "engine.state-root-task-timeout",
540        value_parser = humantime::parse_duration,
541        default_value = Resettable::from(DefaultEngineValues::get_global().state_root_task_timeout.as_ref().map(|v| v.as_str().into())),
542    )]
543    pub state_root_task_timeout: Option<Duration>,
544
545    /// Whether to share execution cache with the payload builder.
546    ///
547    /// When enabled, each payload job will get an instance of cross-block execution cache from the
548    /// engine.
549    ///
550    /// Note: this should only be enabled if node would not be requested to process any payloads in
551    /// parallel with payload building.
552    #[arg(
553        long = "engine.share-execution-cache-with-payload-builder",
554        default_value_t = DefaultEngineValues::get_global().share_execution_cache_with_payload_builder,
555    )]
556    pub share_execution_cache_with_payload_builder: bool,
557
558    /// Whether to share the sparse trie with the payload builder.
559    ///
560    /// Replaces the payload builder's blocking `state_root_with_updates()` call with the
561    /// sparse trie, computing the state root concurrently with transaction execution.
562    ///
563    /// The engine and payload builder contend for the same trie — if a builder task is
564    /// still running when `newPayload` arrives, the engine will block until the trie is
565    /// stored back.
566    ///
567    /// The builder also anchors the trie at the built block's state root, so if the next
568    /// `newPayload` is not on top of that block, the trie cache is invalidated and cleared.
569    #[arg(
570        long = "engine.share-sparse-trie-with-payload-builder",
571        default_value_t = DefaultEngineValues::get_global().share_sparse_trie_with_payload_builder,
572    )]
573    pub share_sparse_trie_with_payload_builder: bool,
574
575    /// Suppress persistence while building a payload.
576    ///
577    /// When enabled, persistence cycles are deferred while a payload build is active. Useful on
578    /// chains with short block times where persistence I/O can interfere with block building
579    /// latency.
580    #[arg(
581        long = "engine.suppress-persistence-during-build",
582        default_value_t = DefaultEngineValues::get_global().suppress_persistence_during_build,
583    )]
584    pub suppress_persistence_during_build: bool,
585
586    /// Disable BAL (Block Access List, EIP-7928) based parallel execution.
587    #[arg(long = "engine.disable-bal-parallel-execution", default_value_t = DefaultEngineValues::get_global().bal_parallel_execution_disabled)]
588    pub bal_parallel_execution_disabled: bool,
589
590    /// Disable BAL-driven parallel state root computation. This is only valid together with
591    /// `--engine.disable-bal-parallel-execution`.
592    #[arg(long = "engine.disable-bal-parallel-state-root", default_value_t = DefaultEngineValues::get_global().bal_parallel_state_root_disabled)]
593    pub bal_parallel_state_root_disabled: bool,
594
595    /// Disable BAL (Block Access List) storage prefetch IO during prewarming. When set, BAL
596    /// storage slots are not read into the execution cache.
597    #[arg(long = "engine.disable-bal-batch-io", default_value_t = false)]
598    pub disable_bal_batch_io: bool,
599
600    /// Reuse transaction prewarming results during block execution instead of executing
601    /// transactions again when their prewarm reads are still valid.
602    #[arg(
603        long = "engine.prewarm-reuse",
604        env = "RETH_ENGINE_PREWARM_REUSE",
605        default_value_t = false
606    )]
607    pub prewarm_reuse: bool,
608
609    /// Add random jitter before each proof computation (trie-debug only).
610    /// Each proof worker sleeps for a random duration up to this value before
611    /// starting work. Useful for stress-testing timing-sensitive proof logic.
612    ///
613    /// --engine.proof-jitter 100ms
614    /// --engine.proof-jitter 1s
615    #[cfg(feature = "trie-debug")]
616    #[arg(
617        long = "engine.proof-jitter",
618        value_parser = humantime::parse_duration,
619    )]
620    pub proof_jitter: Option<Duration>,
621}
622
623#[allow(deprecated)]
624impl Default for EngineArgs {
625    fn default() -> Self {
626        let DefaultEngineValues {
627            persistence_threshold,
628            persistence_backpressure_threshold: _,
629            num_state_masking_blocks,
630            memory_block_buffer_target: _,
631            backfill_run_threshold,
632            invalid_header_hit_eviction_threshold,
633            state_cache_disabled,
634            prewarming_disabled,
635            txpool_prewarming_enabled,
636            sender_recovery_cache_enabled,
637            state_provider_metrics,
638            cross_block_cache_size,
639            state_root_task_compare_updates,
640            accept_execution_requests_hash,
641            multiproof_chunk_size,
642            reserved_cpu_cores,
643            precompile_cache_disabled,
644            state_root_fallback,
645            always_process_payload_attributes_on_canonical_head,
646            allow_unwind_canonical_header,
647            storage_worker_count,
648            account_worker_count,
649            prewarming_threads,
650            cache_metrics_disabled,
651            slow_block_threshold,
652            disable_sparse_trie_cache_pruning,
653            state_root_task_timeout,
654            share_execution_cache_with_payload_builder,
655            share_sparse_trie_with_payload_builder,
656            suppress_persistence_during_build,
657            bal_parallel_execution_disabled,
658            bal_parallel_state_root_disabled,
659        } = DefaultEngineValues::get_global().clone();
660        Self {
661            persistence_threshold,
662            persistence_backpressure_threshold: None,
663            num_state_masking_blocks,
664            memory_block_buffer_target: None,
665            backfill_run_threshold,
666            invalid_header_hit_eviction_threshold,
667            state_root_task_compare_updates,
668            legacy_state_root_task_enabled: false,
669            caching_and_prewarming_enabled: true,
670            state_cache_disabled,
671            prewarming_disabled,
672            txpool_prewarming_enabled,
673            sender_recovery_cache_enabled,
674            parallel_sparse_trie_enabled: true,
675            parallel_sparse_trie_disabled: false,
676            state_provider_metrics,
677            cross_block_cache_size,
678            accept_execution_requests_hash,
679            multiproof_chunk_size,
680            reserved_cpu_cores,
681            cpu_cores: None,
682            precompile_cache_enabled: true,
683            precompile_cache_disabled,
684            state_root_fallback,
685            always_process_payload_attributes_on_canonical_head,
686            allow_unwind_canonical_header,
687            storage_worker_count,
688            account_worker_count,
689            prewarming_threads,
690            cache_metrics_disabled,
691            slow_block_threshold,
692            disable_sparse_trie_cache_pruning,
693            state_root_task_timeout: state_root_task_timeout
694                .as_deref()
695                .map(|s| humantime::parse_duration(s).expect("valid default duration")),
696            share_execution_cache_with_payload_builder,
697            share_sparse_trie_with_payload_builder,
698            suppress_persistence_during_build,
699            bal_parallel_execution_disabled,
700            bal_parallel_state_root_disabled,
701            disable_bal_batch_io: false,
702            prewarm_reuse: false,
703            #[cfg(feature = "trie-debug")]
704            proof_jitter: None,
705        }
706    }
707}
708
709impl EngineArgs {
710    /// Returns the effective state masking window, disabled when persistence is immediate.
711    pub const fn num_state_masking_blocks(&self) -> u64 {
712        if self.persistence_threshold == 0 {
713            0
714        } else {
715            self.num_state_masking_blocks
716        }
717    }
718
719    /// Returns the effective memory block buffer target.
720    pub fn memory_block_buffer_target(&self) -> u64 {
721        self.memory_block_buffer_target.unwrap_or_else(|| {
722            self.persistence_threshold
723                .min(DefaultEngineValues::get_global().memory_block_buffer_target)
724        })
725    }
726
727    /// Returns the effective persistence backpressure threshold.
728    pub fn persistence_backpressure_threshold(&self) -> u64 {
729        self.persistence_backpressure_threshold.unwrap_or_else(|| {
730            default_persistence_backpressure_threshold(self.persistence_threshold)
731        })
732    }
733
734    /// Validates cross-field engine arguments.
735    pub fn validate(&self) -> eyre::Result<()> {
736        let persistence_backpressure_threshold = self.persistence_backpressure_threshold();
737        let memory_block_buffer_target = self.memory_block_buffer_target();
738        let num_state_masking_blocks = self.num_state_masking_blocks();
739        ensure!(
740            persistence_backpressure_threshold > self.persistence_threshold,
741            "--engine.persistence-backpressure-threshold ({}) must be greater than --engine.persistence-threshold ({})",
742            persistence_backpressure_threshold,
743            self.persistence_threshold
744        );
745        ensure!(
746            memory_block_buffer_target <= self.persistence_threshold,
747            "--engine.memory-block-buffer-target ({}) must be less than or equal to --engine.persistence-threshold ({})",
748            memory_block_buffer_target,
749            self.persistence_threshold,
750        );
751        ensure!(
752            num_state_masking_blocks == 0 ||
753                matches!(
754                    num_state_masking_blocks.checked_add(memory_block_buffer_target),
755                    Some(window) if window < self.persistence_threshold
756                ),
757            "--engine.num-state-masking-blocks ({}) + --engine.memory-block-buffer-target ({}) must be less than --engine.persistence-threshold ({})",
758            num_state_masking_blocks,
759            memory_block_buffer_target,
760            self.persistence_threshold,
761        );
762        ensure!(
763            !self.state_cache_disabled || !self.txpool_prewarming_enabled,
764            "--engine.txpool-prewarming conflicts with --engine.disable-state-cache"
765        );
766        ensure!(
767            self.bal_parallel_execution_disabled || !self.bal_parallel_state_root_disabled,
768            "--engine.disable-bal-parallel-state-root requires --engine.disable-bal-parallel-execution because BAL parallel execution depends on BAL prewarm state-root updates"
769        );
770        Ok(())
771    }
772
773    /// Creates a [`TreeConfig`] from the engine arguments.
774    pub fn tree_config(&self) -> TreeConfig {
775        #[allow(deprecated)]
776        if self.legacy_state_root_task_enabled {
777            tracing::warn!(target: "reth::cli", "--engine.legacy-state-root has no effect anymore, use --engine.state-root-fallback to force synchronous state root computation");
778        }
779        let config = TreeConfig::default()
780            // Clear the default window before applying overrides that may be smaller than it.
781            .with_persistence_threshold(0)
782            .with_persistence_backpressure_threshold(self.persistence_backpressure_threshold())
783            .with_persistence_threshold(self.persistence_threshold)
784            .with_memory_block_buffer_target(self.memory_block_buffer_target())
785            .with_num_state_masking_blocks(self.num_state_masking_blocks())
786            .with_backfill_run_threshold(self.backfill_run_threshold)
787            .with_invalid_header_hit_eviction_threshold(self.invalid_header_hit_eviction_threshold)
788            .without_state_cache(self.state_cache_disabled)
789            .without_prewarming(self.prewarming_disabled)
790            .with_txpool_prewarming(self.txpool_prewarming_enabled)
791            .with_state_provider_metrics(self.state_provider_metrics)
792            .with_always_compare_trie_updates(self.state_root_task_compare_updates)
793            .with_cross_block_cache_size(self.cross_block_cache_size * 1024 * 1024)
794            .with_multiproof_chunk_size(self.multiproof_chunk_size)
795            .with_reserved_cpu_cores(self.reserved_cpu_cores)
796            .without_precompile_cache(self.precompile_cache_disabled)
797            .with_state_root_fallback(self.state_root_fallback)
798            .with_always_process_payload_attributes_on_canonical_head(
799                self.always_process_payload_attributes_on_canonical_head,
800            )
801            .with_unwind_canonical_header(self.allow_unwind_canonical_header)
802            .without_cache_metrics(self.cache_metrics_disabled)
803            .with_slow_block_threshold(self.slow_block_threshold)
804            .with_disable_sparse_trie_cache_pruning(self.disable_sparse_trie_cache_pruning)
805            .with_state_root_task_timeout(self.state_root_task_timeout.filter(|d| !d.is_zero()))
806            .with_share_execution_cache_with_payload_builder(
807                self.share_execution_cache_with_payload_builder,
808            )
809            .with_share_sparse_trie_with_payload_builder(
810                self.share_sparse_trie_with_payload_builder,
811            )
812            .with_suppress_persistence_during_build(self.suppress_persistence_during_build)
813            .without_bal_parallel_execution(self.bal_parallel_execution_disabled)
814            .without_bal_parallel_state_root(self.bal_parallel_state_root_disabled)
815            .without_bal_batch_io(self.disable_bal_batch_io)
816            .with_prewarm_reuse(self.prewarm_reuse);
817        #[cfg(feature = "trie-debug")]
818        let config = config.with_proof_jitter(self.proof_jitter);
819        if let Some(cpu_cores) = self.cpu_cores {
820            config.with_has_enough_parallelism(cpu_cores.get() >= 5)
821        } else {
822            config
823        }
824    }
825}
826
827#[cfg(test)]
828mod tests {
829    use super::*;
830    use clap::Parser;
831
832    /// A helper type to parse Args more easily
833    #[derive(Parser)]
834    struct CommandParser<T: Args> {
835        #[command(flatten)]
836        args: T,
837    }
838
839    #[test]
840    fn cpu_cores_override_controls_parallel_state_root() {
841        for (cores, enabled) in [(1, false), (4, false), (5, true), (8, true)] {
842            let args = CommandParser::<EngineArgs>::parse_from([
843                "reth",
844                "--cpu-cores",
845                &cores.to_string(),
846            ])
847            .args;
848            assert_eq!(args.cpu_cores.unwrap().get(), cores);
849            assert_eq!(args.tree_config().use_state_root_task(), enabled);
850        }
851    }
852
853    #[test]
854    fn cpu_cores_override_preserves_state_root_fallback() {
855        let args = CommandParser::<EngineArgs>::parse_from([
856            "reth",
857            "--cpu-cores",
858            "8",
859            "--engine.state-root-fallback",
860        ])
861        .args;
862        assert!(!args.tree_config().use_state_root_task());
863    }
864
865    #[test]
866    fn test_parse_engine_args() {
867        let default_args = EngineArgs::default();
868        let args = CommandParser::<EngineArgs>::parse_from(["reth"]).args;
869        assert_eq!(args, default_args);
870        assert_eq!(args.persistence_threshold, 50);
871        assert_eq!(args.num_state_masking_blocks, 30);
872        assert_eq!(args.memory_block_buffer_target, None);
873        assert_eq!(args.memory_block_buffer_target(), 5);
874        assert_eq!(args.persistence_backpressure_threshold(), 100);
875        args.validate().unwrap();
876        let config = args.tree_config();
877        assert_eq!(config.persistence_threshold(), 50);
878        assert_eq!(config.num_state_masking_blocks(), 30);
879        assert_eq!(config.persistence_backpressure_threshold(), 100);
880    }
881
882    #[test]
883    fn txpool_prewarming_is_disabled_by_default_and_can_be_enabled() {
884        let args = CommandParser::<EngineArgs>::parse_from(["reth"]).args;
885        assert!(!args.txpool_prewarming_enabled);
886        assert!(!args.tree_config().txpool_prewarming());
887
888        let args =
889            CommandParser::<EngineArgs>::parse_from(["reth", "--engine.txpool-prewarming"]).args;
890        assert!(args.txpool_prewarming_enabled);
891        assert!(args.tree_config().txpool_prewarming());
892    }
893
894    #[test]
895    fn validate_rejects_txpool_prewarming_with_disabled_state_cache() {
896        let args = EngineArgs {
897            state_cache_disabled: true,
898            txpool_prewarming_enabled: true,
899            ..EngineArgs::default()
900        };
901
902        let err = args.validate().unwrap_err().to_string();
903        assert!(err.contains("engine.txpool-prewarming"));
904        assert!(err.contains("engine.disable-state-cache"));
905    }
906
907    #[test]
908    fn default_backpressure_threshold_uses_parsed_persistence_args() {
909        let args = CommandParser::<EngineArgs>::parse_from([
910            "reth",
911            "--engine.persistence-threshold",
912            "100",
913            "--engine.memory-block-buffer-target",
914            "50",
915        ])
916        .args;
917
918        assert_eq!(args.persistence_backpressure_threshold(), 200);
919
920        let tree_config = args.tree_config();
921        assert_eq!(tree_config.persistence_threshold(), 100);
922        assert_eq!(tree_config.memory_block_buffer_target(), 50);
923        assert_eq!(tree_config.persistence_backpressure_threshold(), 200);
924    }
925
926    #[test]
927    fn default_backpressure_threshold_uses_minimum_when_larger() {
928        let args = CommandParser::<EngineArgs>::parse_from([
929            "reth",
930            "--engine.persistence-threshold",
931            "4",
932        ])
933        .args;
934
935        assert_eq!(
936            args.persistence_backpressure_threshold(),
937            DefaultEngineValues::get_global().persistence_backpressure_threshold
938        );
939        assert_eq!(args.persistence_backpressure_threshold(), 16);
940    }
941
942    #[test]
943    fn explicit_backpressure_threshold_overrides_calculated_default() {
944        let args = CommandParser::<EngineArgs>::parse_from([
945            "reth",
946            "--engine.persistence-threshold",
947            "100",
948            "--engine.memory-block-buffer-target",
949            "50",
950            "--engine.persistence-backpressure-threshold",
951            "101",
952        ])
953        .args;
954
955        assert_eq!(args.persistence_backpressure_threshold(), 101);
956    }
957
958    #[test]
959    fn sender_recovery_cache_is_enabled_by_default_and_can_be_disabled() {
960        let args = CommandParser::<EngineArgs>::parse_from(["reth"]).args;
961        assert!(args.sender_recovery_cache_enabled);
962
963        let args =
964            CommandParser::<EngineArgs>::parse_from(["reth", "--engine.sender-recovery-cache"])
965                .args;
966        assert!(args.sender_recovery_cache_enabled);
967
968        let args = CommandParser::<EngineArgs>::parse_from([
969            "reth",
970            "--engine.sender-recovery-cache=true",
971        ])
972        .args;
973        assert!(args.sender_recovery_cache_enabled);
974
975        let args = CommandParser::<EngineArgs>::parse_from([
976            "reth",
977            "--engine.sender-recovery-cache=false",
978        ])
979        .args;
980        assert!(!args.sender_recovery_cache_enabled);
981
982        let args = CommandParser::<EngineArgs>::parse_from([
983            "reth",
984            "--engine.sender-recovery-cache",
985            "false",
986        ])
987        .args;
988        assert!(!args.sender_recovery_cache_enabled);
989    }
990
991    #[test]
992    #[allow(deprecated)]
993    fn engine_args() {
994        let args = EngineArgs {
995            persistence_threshold: 100,
996            persistence_backpressure_threshold: Some(101),
997            num_state_masking_blocks: DEFAULT_NUM_STATE_MASKING_BLOCKS,
998            memory_block_buffer_target: Some(50),
999            backfill_run_threshold: 100,
1000            invalid_header_hit_eviction_threshold: 7,
1001            legacy_state_root_task_enabled: true,
1002            caching_and_prewarming_enabled: true,
1003            state_cache_disabled: true,
1004            prewarming_disabled: true,
1005            // conflicts with --engine.disable-state-cache, covered by its own test below
1006            txpool_prewarming_enabled: false,
1007            sender_recovery_cache_enabled: true,
1008            parallel_sparse_trie_enabled: true,
1009            parallel_sparse_trie_disabled: false,
1010            state_provider_metrics: true,
1011            cross_block_cache_size: 256,
1012            state_root_task_compare_updates: true,
1013            accept_execution_requests_hash: true,
1014            multiproof_chunk_size: 512,
1015            reserved_cpu_cores: 4,
1016            cpu_cores: Some(NonZeroUsize::new(8).unwrap()),
1017            precompile_cache_enabled: true,
1018            precompile_cache_disabled: true,
1019            state_root_fallback: true,
1020            always_process_payload_attributes_on_canonical_head: true,
1021            allow_unwind_canonical_header: true,
1022            storage_worker_count: Some(16),
1023            account_worker_count: Some(8),
1024            prewarming_threads: Some(4),
1025            cache_metrics_disabled: true,
1026            slow_block_threshold: None,
1027            disable_sparse_trie_cache_pruning: true,
1028            state_root_task_timeout: Some(Duration::from_secs(2)),
1029            share_execution_cache_with_payload_builder: false,
1030            share_sparse_trie_with_payload_builder: false,
1031            suppress_persistence_during_build: false,
1032            bal_parallel_execution_disabled: true,
1033            bal_parallel_state_root_disabled: true,
1034            disable_bal_batch_io: true,
1035            prewarm_reuse: true,
1036            #[cfg(feature = "trie-debug")]
1037            proof_jitter: None,
1038        };
1039
1040        let parsed_args = CommandParser::<EngineArgs>::parse_from([
1041            "reth",
1042            "--engine.persistence-threshold",
1043            "100",
1044            "--engine.persistence-backpressure-threshold",
1045            "101",
1046            "--engine.memory-block-buffer-target",
1047            "50",
1048            "--engine.backfill-threshold",
1049            "100",
1050            "--engine.invalid-header-cache-hit-eviction-threshold",
1051            "7",
1052            "--engine.legacy-state-root",
1053            "--engine.disable-state-cache",
1054            "--engine.disable-prewarming",
1055            "--engine.sender-recovery-cache",
1056            "--engine.state-provider-metrics",
1057            "--engine.cross-block-cache-size",
1058            "256",
1059            "--engine.state-root-task-compare-updates",
1060            "--engine.accept-execution-requests-hash",
1061            "--engine.multiproof-chunk-size",
1062            "512",
1063            "--engine.reserved-cpu-cores",
1064            "4",
1065            "--cpu-cores",
1066            "8",
1067            "--engine.disable-precompile-cache",
1068            "--engine.state-root-fallback",
1069            "--engine.always-process-payload-attributes-on-canonical-head",
1070            "--engine.allow-unwind-canonical-header",
1071            "--engine.storage-worker-count",
1072            "16",
1073            "--engine.account-worker-count",
1074            "8",
1075            "--engine.prewarming-threads",
1076            "4",
1077            "--engine.disable-cache-metrics",
1078            "--engine.disable-sparse-trie-cache-pruning",
1079            "--engine.state-root-task-timeout",
1080            "2s",
1081            "--engine.disable-bal-parallel-execution",
1082            "--engine.disable-bal-parallel-state-root",
1083            "--engine.disable-bal-batch-io",
1084            "--engine.prewarm-reuse",
1085        ])
1086        .args;
1087
1088        assert_eq!(parsed_args, args);
1089    }
1090
1091    #[test]
1092    fn validate_rejects_invalid_backpressure_threshold() {
1093        let args = EngineArgs {
1094            persistence_threshold: 4,
1095            persistence_backpressure_threshold: Some(4),
1096            ..EngineArgs::default()
1097        };
1098
1099        let err = args.validate().unwrap_err().to_string();
1100        assert!(err.contains("engine.persistence-backpressure-threshold"));
1101        assert!(err.contains("engine.persistence-threshold"));
1102    }
1103
1104    #[test]
1105    fn validate_memory_block_buffer_target() {
1106        let args = EngineArgs {
1107            persistence_threshold: 4,
1108            num_state_masking_blocks: 0,
1109            memory_block_buffer_target: Some(4),
1110            ..EngineArgs::default()
1111        };
1112        args.validate().unwrap();
1113
1114        let args = EngineArgs { memory_block_buffer_target: Some(5), ..args };
1115        let err = args.validate().unwrap_err().to_string();
1116        assert!(err.contains("engine.memory-block-buffer-target"));
1117        assert!(err.contains("engine.persistence-threshold"));
1118    }
1119
1120    #[test]
1121    fn test_parse_backfill_threshold() {
1122        let args = CommandParser::<EngineArgs>::parse_from(["reth"]).args;
1123        let config = args.tree_config();
1124        assert_eq!(config.backfill_run_threshold(), DEFAULT_BACKFILL_RUN_THRESHOLD);
1125
1126        for (threshold, expected) in [("500", 500), ("2048", 1024)] {
1127            let args = CommandParser::<EngineArgs>::parse_from([
1128                "reth",
1129                "--engine.backfill-threshold",
1130                threshold,
1131            ])
1132            .args;
1133            assert_eq!(args.tree_config().backfill_run_threshold(), expected);
1134        }
1135    }
1136
1137    #[test]
1138    fn validate_rejects_state_masking_window_at_or_above_threshold() {
1139        let args = EngineArgs {
1140            persistence_threshold: 4,
1141            num_state_masking_blocks: 2,
1142            memory_block_buffer_target: Some(2),
1143            ..EngineArgs::default()
1144        };
1145
1146        let err = args.validate().unwrap_err().to_string();
1147        assert!(err.contains("engine.num-state-masking-blocks"));
1148    }
1149
1150    #[test]
1151    fn validate_rejects_overflowing_state_masking_window() {
1152        let args = EngineArgs {
1153            persistence_threshold: 7,
1154            num_state_masking_blocks: u64::MAX,
1155            ..EngineArgs::default()
1156        };
1157
1158        let err = args.validate().unwrap_err().to_string();
1159        assert!(err.contains("engine.num-state-masking-blocks"));
1160    }
1161
1162    #[test]
1163    fn default_memory_block_buffer_target_is_bounded_by_persistence_threshold() {
1164        let args = CommandParser::<EngineArgs>::parse_from([
1165            "reth",
1166            "--engine.persistence-threshold",
1167            "4",
1168            "--engine.num-state-masking-blocks",
1169            "0",
1170        ])
1171        .args;
1172
1173        assert_eq!(args.memory_block_buffer_target, None);
1174        assert_eq!(args.memory_block_buffer_target(), 4);
1175        assert_eq!(args.tree_config().memory_block_buffer_target(), 4);
1176        args.validate().unwrap();
1177    }
1178
1179    #[test]
1180    fn explicit_persistence_settings_can_be_lower_than_defaults() {
1181        let args = CommandParser::<EngineArgs>::parse_from([
1182            "reth",
1183            "--engine.persistence-threshold",
1184            "0",
1185            "--engine.persistence-backpressure-threshold",
1186            "1",
1187        ])
1188        .args;
1189
1190        args.validate().unwrap();
1191        let config = args.tree_config();
1192        assert_eq!(config.persistence_threshold(), 0);
1193        assert_eq!(config.num_state_masking_blocks(), 0);
1194        assert_eq!(config.memory_block_buffer_target(), 0);
1195        assert_eq!(config.persistence_backpressure_threshold(), 1);
1196    }
1197
1198    #[test]
1199    fn parse_rejects_zero_multiproof_chunk_size() {
1200        let result = CommandParser::<EngineArgs>::try_parse_from([
1201            "reth",
1202            "--engine.multiproof-chunk-size",
1203            "0",
1204        ]);
1205
1206        assert!(result.is_err());
1207    }
1208
1209    #[test]
1210    fn validate_rejects_bal_parallel_execution_without_bal_parallel_state_root() {
1211        let args = EngineArgs {
1212            bal_parallel_execution_disabled: false,
1213            bal_parallel_state_root_disabled: true,
1214            ..EngineArgs::default()
1215        };
1216
1217        let err = args.validate().unwrap_err().to_string();
1218        assert!(err.contains("engine.disable-bal-parallel-state-root"));
1219        assert!(err.contains("engine.disable-bal-parallel-execution"));
1220    }
1221
1222    #[test]
1223    fn test_parse_slow_block_threshold() {
1224        // Test default value (None - disabled)
1225        let args = CommandParser::<EngineArgs>::parse_from(["reth"]).args;
1226        assert_eq!(args.slow_block_threshold, None);
1227
1228        // Test setting to 0 (log all blocks)
1229        let args =
1230            CommandParser::<EngineArgs>::parse_from(["reth", "--engine.slow-block-threshold", "0"])
1231                .args;
1232        assert_eq!(args.slow_block_threshold, Some(Duration::ZERO));
1233
1234        // Test setting to custom value
1235        let args = CommandParser::<EngineArgs>::parse_from([
1236            "reth",
1237            "--engine.slow-block-threshold",
1238            "500",
1239        ])
1240        .args;
1241        assert_eq!(args.slow_block_threshold, Some(Duration::from_secs(500)));
1242
1243        let args = CommandParser::<EngineArgs>::parse_from([
1244            "reth",
1245            "--engine.slow-block-threshold",
1246            "500ms",
1247        ])
1248        .args;
1249        assert_eq!(args.slow_block_threshold, Some(Duration::from_millis(500)));
1250    }
1251
1252    #[test]
1253    fn test_parse_invalid_header_hit_eviction_threshold() {
1254        let args = CommandParser::<EngineArgs>::parse_from(["reth"]).args;
1255        assert_eq!(
1256            args.invalid_header_hit_eviction_threshold,
1257            DEFAULT_INVALID_HEADER_HIT_EVICTION_THRESHOLD
1258        );
1259        assert_eq!(
1260            args.tree_config().invalid_header_hit_eviction_threshold(),
1261            DEFAULT_INVALID_HEADER_HIT_EVICTION_THRESHOLD
1262        );
1263
1264        let args = CommandParser::<EngineArgs>::parse_from([
1265            "reth",
1266            "--engine.invalid-header-cache-hit-eviction-threshold",
1267            "0",
1268        ])
1269        .args;
1270        assert_eq!(args.invalid_header_hit_eviction_threshold, 0);
1271        assert_eq!(args.tree_config().invalid_header_hit_eviction_threshold(), 0);
1272    }
1273
1274    #[test]
1275    fn test_parse_share_sparse_trie_flag() {
1276        let args = CommandParser::<EngineArgs>::parse_from(["reth"]).args;
1277        assert!(!args.share_sparse_trie_with_payload_builder);
1278        assert!(!args.tree_config().share_sparse_trie_with_payload_builder());
1279
1280        let args = CommandParser::<EngineArgs>::parse_from([
1281            "reth",
1282            "--engine.share-sparse-trie-with-payload-builder",
1283        ])
1284        .args;
1285        assert!(args.share_sparse_trie_with_payload_builder);
1286        assert!(args.tree_config().share_sparse_trie_with_payload_builder());
1287    }
1288}