Skip to main content

reth_node_core/args/
engine.rs

1//! clap [Args](clap::Args) for engine purposes
2
3use clap::{builder::Resettable, Args};
4use eyre::ensure;
5use reth_cli_util::{parse_duration_from_secs_or_ms, parsers::format_duration_as_secs_or_ms};
6use reth_engine_primitives::{
7    TreeConfig, DEFAULT_MULTIPROOF_TASK_CHUNK_SIZE, DEFAULT_PERSISTENCE_BACKPRESSURE_THRESHOLD,
8    DEFAULT_SPARSE_TRIE_MAX_HOT_ACCOUNTS, DEFAULT_SPARSE_TRIE_MAX_HOT_SLOTS,
9};
10use std::{sync::OnceLock, time::Duration};
11
12use crate::node_config::{
13    DEFAULT_CROSS_BLOCK_CACHE_SIZE_MB, DEFAULT_MEMORY_BLOCK_BUFFER_TARGET,
14    DEFAULT_PERSISTENCE_THRESHOLD, DEFAULT_RESERVED_CPU_CORES,
15};
16
17/// Global static engine defaults
18static ENGINE_DEFAULTS: OnceLock<DefaultEngineValues> = OnceLock::new();
19
20/// Default values for engine that can be customized
21///
22/// Global defaults can be set via [`DefaultEngineValues::try_init`].
23#[derive(Debug, Clone)]
24pub struct DefaultEngineValues {
25    persistence_threshold: u64,
26    persistence_backpressure_threshold: u64,
27    memory_block_buffer_target: u64,
28    legacy_state_root_task_enabled: bool,
29    state_cache_disabled: bool,
30    prewarming_disabled: bool,
31    state_provider_metrics: bool,
32    cross_block_cache_size: usize,
33    state_root_task_compare_updates: bool,
34    accept_execution_requests_hash: bool,
35    multiproof_chunk_size: usize,
36    reserved_cpu_cores: usize,
37    precompile_cache_disabled: bool,
38    state_root_fallback: bool,
39    always_process_payload_attributes_on_canonical_head: bool,
40    allow_unwind_canonical_header: bool,
41    storage_worker_count: Option<usize>,
42    account_worker_count: Option<usize>,
43    prewarming_threads: Option<usize>,
44    cache_metrics_disabled: bool,
45    sparse_trie_max_hot_slots: usize,
46    sparse_trie_max_hot_accounts: usize,
47    slow_block_threshold: Option<Duration>,
48    disable_sparse_trie_cache_pruning: bool,
49    state_root_task_timeout: Option<String>,
50    share_execution_cache_with_payload_builder: bool,
51    share_sparse_trie_with_payload_builder: bool,
52}
53
54impl DefaultEngineValues {
55    /// Initialize the global engine defaults with this configuration
56    pub fn try_init(self) -> Result<(), Self> {
57        ENGINE_DEFAULTS.set(self)
58    }
59
60    /// Get a reference to the global engine defaults
61    pub fn get_global() -> &'static Self {
62        ENGINE_DEFAULTS.get_or_init(Self::default)
63    }
64
65    /// Set the default persistence threshold
66    pub const fn with_persistence_threshold(mut self, v: u64) -> Self {
67        self.persistence_threshold = v;
68        self
69    }
70
71    /// Set the default persistence backpressure threshold
72    pub const fn with_persistence_backpressure_threshold(mut self, v: u64) -> Self {
73        self.persistence_backpressure_threshold = v;
74        self
75    }
76
77    /// Set the default memory block buffer target
78    pub const fn with_memory_block_buffer_target(mut self, v: u64) -> Self {
79        self.memory_block_buffer_target = v;
80        self
81    }
82
83    /// Set whether to enable legacy state root task by default
84    pub const fn with_legacy_state_root_task_enabled(mut self, v: bool) -> Self {
85        self.legacy_state_root_task_enabled = v;
86        self
87    }
88
89    /// Set whether to disable state cache by default
90    pub const fn with_state_cache_disabled(mut self, v: bool) -> Self {
91        self.state_cache_disabled = v;
92        self
93    }
94
95    /// Set whether to disable prewarming by default
96    pub const fn with_prewarming_disabled(mut self, v: bool) -> Self {
97        self.prewarming_disabled = v;
98        self
99    }
100
101    /// Set whether to enable state provider metrics by default
102    pub const fn with_state_provider_metrics(mut self, v: bool) -> Self {
103        self.state_provider_metrics = v;
104        self
105    }
106
107    /// Set the default cross-block cache size in MB
108    pub const fn with_cross_block_cache_size(mut self, v: usize) -> Self {
109        self.cross_block_cache_size = v;
110        self
111    }
112
113    /// Set whether to compare state root task updates by default
114    pub const fn with_state_root_task_compare_updates(mut self, v: bool) -> Self {
115        self.state_root_task_compare_updates = v;
116        self
117    }
118
119    /// Set whether to accept execution requests hash by default
120    pub const fn with_accept_execution_requests_hash(mut self, v: bool) -> Self {
121        self.accept_execution_requests_hash = v;
122        self
123    }
124
125    /// Set the default multiproof chunk size
126    pub const fn with_multiproof_chunk_size(mut self, v: usize) -> Self {
127        self.multiproof_chunk_size = v;
128        self
129    }
130
131    /// Set the default number of reserved CPU cores
132    pub const fn with_reserved_cpu_cores(mut self, v: usize) -> Self {
133        self.reserved_cpu_cores = v;
134        self
135    }
136
137    /// Set whether to disable precompile cache by default
138    pub const fn with_precompile_cache_disabled(mut self, v: bool) -> Self {
139        self.precompile_cache_disabled = v;
140        self
141    }
142
143    /// Set whether to enable state root fallback by default
144    pub const fn with_state_root_fallback(mut self, v: bool) -> Self {
145        self.state_root_fallback = v;
146        self
147    }
148
149    /// Set whether to always process payload attributes on canonical head by default
150    pub const fn with_always_process_payload_attributes_on_canonical_head(
151        mut self,
152        v: bool,
153    ) -> Self {
154        self.always_process_payload_attributes_on_canonical_head = v;
155        self
156    }
157
158    /// Set whether to allow unwinding canonical header by default
159    pub const fn with_allow_unwind_canonical_header(mut self, v: bool) -> Self {
160        self.allow_unwind_canonical_header = v;
161        self
162    }
163
164    /// Set the default storage worker count
165    pub const fn with_storage_worker_count(mut self, v: Option<usize>) -> Self {
166        self.storage_worker_count = v;
167        self
168    }
169
170    /// Set the default account worker count
171    pub const fn with_account_worker_count(mut self, v: Option<usize>) -> Self {
172        self.account_worker_count = v;
173        self
174    }
175
176    /// Set the default prewarming thread count
177    pub const fn with_prewarming_threads(mut self, v: Option<usize>) -> Self {
178        self.prewarming_threads = v;
179        self
180    }
181
182    /// Set whether to disable cache metrics by default
183    pub const fn with_cache_metrics_disabled(mut self, v: bool) -> Self {
184        self.cache_metrics_disabled = v;
185        self
186    }
187
188    /// Set the LFU hot-slot capacity for sparse trie pruning by default
189    pub const fn with_sparse_trie_max_hot_slots(mut self, v: usize) -> Self {
190        self.sparse_trie_max_hot_slots = v;
191        self
192    }
193
194    /// Set the LFU hot-account capacity for sparse trie pruning by default
195    pub const fn with_sparse_trie_max_hot_accounts(mut self, v: usize) -> Self {
196        self.sparse_trie_max_hot_accounts = v;
197        self
198    }
199
200    /// Set the default slow block threshold.
201    pub const fn with_slow_block_threshold(mut self, v: Option<Duration>) -> Self {
202        self.slow_block_threshold = v;
203        self
204    }
205
206    /// Set whether to disable sparse trie cache pruning by default
207    pub const fn with_disable_sparse_trie_cache_pruning(mut self, v: bool) -> Self {
208        self.disable_sparse_trie_cache_pruning = v;
209        self
210    }
211
212    /// Set the default state root task timeout
213    pub fn with_state_root_task_timeout(mut self, v: Option<String>) -> Self {
214        self.state_root_task_timeout = v;
215        self
216    }
217
218    /// Set whether to share the execution cache with the payload builder by default
219    pub const fn with_share_execution_cache_with_payload_builder(mut self, v: bool) -> Self {
220        self.share_execution_cache_with_payload_builder = v;
221        self
222    }
223
224    /// Set whether to share the sparse trie with the payload builder by default
225    pub const fn with_share_sparse_trie_with_payload_builder(mut self, v: bool) -> Self {
226        self.share_sparse_trie_with_payload_builder = v;
227        self
228    }
229}
230
231impl Default for DefaultEngineValues {
232    fn default() -> Self {
233        Self {
234            persistence_threshold: DEFAULT_PERSISTENCE_THRESHOLD,
235            persistence_backpressure_threshold: DEFAULT_PERSISTENCE_BACKPRESSURE_THRESHOLD,
236            memory_block_buffer_target: DEFAULT_MEMORY_BLOCK_BUFFER_TARGET,
237            legacy_state_root_task_enabled: false,
238            state_cache_disabled: false,
239            prewarming_disabled: false,
240            state_provider_metrics: false,
241            cross_block_cache_size: DEFAULT_CROSS_BLOCK_CACHE_SIZE_MB,
242            state_root_task_compare_updates: false,
243            accept_execution_requests_hash: false,
244            multiproof_chunk_size: DEFAULT_MULTIPROOF_TASK_CHUNK_SIZE,
245            reserved_cpu_cores: DEFAULT_RESERVED_CPU_CORES,
246            precompile_cache_disabled: false,
247            state_root_fallback: false,
248            always_process_payload_attributes_on_canonical_head: false,
249            allow_unwind_canonical_header: false,
250            storage_worker_count: None,
251            account_worker_count: None,
252            prewarming_threads: None,
253            cache_metrics_disabled: false,
254            sparse_trie_max_hot_slots: DEFAULT_SPARSE_TRIE_MAX_HOT_SLOTS,
255            sparse_trie_max_hot_accounts: DEFAULT_SPARSE_TRIE_MAX_HOT_ACCOUNTS,
256            slow_block_threshold: None,
257            disable_sparse_trie_cache_pruning: false,
258            state_root_task_timeout: Some("1s".to_string()),
259            share_execution_cache_with_payload_builder: false,
260            share_sparse_trie_with_payload_builder: false,
261        }
262    }
263}
264
265/// Parameters for configuring the engine driver.
266#[derive(Debug, Clone, Args, PartialEq, Eq)]
267#[command(next_help_heading = "Engine")]
268pub struct EngineArgs {
269    /// Configure persistence threshold for the engine. This determines how many canonical blocks
270    /// must be in-memory, ahead of the last persisted block, before flushing canonical blocks to
271    /// disk again.
272    ///
273    /// To persist blocks as fast as the node receives them, set this value to zero. This will
274    /// cause more frequent DB writes.
275    #[arg(long = "engine.persistence-threshold", default_value_t = DefaultEngineValues::get_global().persistence_threshold)]
276    pub persistence_threshold: u64,
277
278    /// Configure the maximum canonical-minus-persisted gap before engine API processing stalls.
279    ///
280    /// This value must be greater than `--engine.persistence-threshold`.
281    #[arg(long = "engine.persistence-backpressure-threshold", default_value_t = DefaultEngineValues::get_global().persistence_backpressure_threshold)]
282    pub persistence_backpressure_threshold: u64,
283
284    /// Configure the target number of blocks to keep in memory.
285    #[arg(long = "engine.memory-block-buffer-target", default_value_t = DefaultEngineValues::get_global().memory_block_buffer_target)]
286    pub memory_block_buffer_target: u64,
287
288    /// Enable legacy state root
289    #[arg(long = "engine.legacy-state-root", default_value_t = DefaultEngineValues::get_global().legacy_state_root_task_enabled)]
290    pub legacy_state_root_task_enabled: bool,
291
292    /// CAUTION: This CLI flag has no effect anymore, use --engine.disable-caching-and-prewarming
293    /// if you want to disable caching and prewarming
294    #[arg(long = "engine.caching-and-prewarming", default_value = "true", hide = true)]
295    #[deprecated]
296    pub caching_and_prewarming_enabled: bool,
297
298    /// Disable state cache
299    #[arg(long = "engine.disable-state-cache", default_value_t = DefaultEngineValues::get_global().state_cache_disabled)]
300    pub state_cache_disabled: bool,
301
302    /// Disable parallel prewarming
303    #[arg(long = "engine.disable-prewarming", alias = "engine.disable-caching-and-prewarming", default_value_t = DefaultEngineValues::get_global().prewarming_disabled)]
304    pub prewarming_disabled: bool,
305
306    /// CAUTION: This CLI flag has no effect anymore. The parallel sparse trie is always enabled.
307    #[deprecated]
308    #[arg(long = "engine.parallel-sparse-trie", default_value = "true", hide = true)]
309    pub parallel_sparse_trie_enabled: bool,
310
311    /// CAUTION: This CLI flag has no effect anymore. The parallel sparse trie is always enabled.
312    #[deprecated]
313    #[arg(long = "engine.disable-parallel-sparse-trie", default_value = "false", hide = true)]
314    pub parallel_sparse_trie_disabled: bool,
315
316    /// Enable state provider latency metrics. This allows the engine to collect and report stats
317    /// about how long state provider calls took during execution, but this does introduce slight
318    /// overhead to state provider calls.
319    #[arg(long = "engine.state-provider-metrics", default_value_t = DefaultEngineValues::get_global().state_provider_metrics)]
320    pub state_provider_metrics: bool,
321
322    /// Configure the size of cross-block cache in megabytes
323    #[arg(long = "engine.cross-block-cache-size", default_value_t = DefaultEngineValues::get_global().cross_block_cache_size)]
324    pub cross_block_cache_size: usize,
325
326    /// Enable comparing trie updates from the state root task to the trie updates from the regular
327    /// state root calculation.
328    #[arg(long = "engine.state-root-task-compare-updates", default_value_t = DefaultEngineValues::get_global().state_root_task_compare_updates)]
329    pub state_root_task_compare_updates: bool,
330
331    /// Enables accepting requests hash instead of an array of requests in `engine_newPayloadV4`.
332    #[arg(long = "engine.accept-execution-requests-hash", default_value_t = DefaultEngineValues::get_global().accept_execution_requests_hash)]
333    pub accept_execution_requests_hash: bool,
334
335    /// Multiproof task chunk size for proof targets.
336    #[arg(long = "engine.multiproof-chunk-size", default_value_t = DefaultEngineValues::get_global().multiproof_chunk_size)]
337    pub multiproof_chunk_size: usize,
338
339    /// Configure the number of reserved CPU cores for non-reth processes
340    #[arg(long = "engine.reserved-cpu-cores", default_value_t = DefaultEngineValues::get_global().reserved_cpu_cores)]
341    pub reserved_cpu_cores: usize,
342
343    /// CAUTION: This CLI flag has no effect anymore, use --engine.disable-precompile-cache
344    /// if you want to disable precompile cache
345    #[arg(long = "engine.precompile-cache", default_value = "true", hide = true)]
346    #[deprecated]
347    pub precompile_cache_enabled: bool,
348
349    /// Disable precompile cache
350    #[arg(long = "engine.disable-precompile-cache", default_value_t = DefaultEngineValues::get_global().precompile_cache_disabled)]
351    pub precompile_cache_disabled: bool,
352
353    /// Enable state root fallback, useful for testing
354    #[arg(long = "engine.state-root-fallback", default_value_t = DefaultEngineValues::get_global().state_root_fallback)]
355    pub state_root_fallback: bool,
356
357    /// Always process payload attributes and begin a payload build process even if
358    /// `forkchoiceState.headBlockHash` is already the canonical head or an ancestor. See
359    /// `TreeConfig::always_process_payload_attributes_on_canonical_head` for more details.
360    ///
361    /// Note: This is a no-op on OP Stack.
362    #[arg(
363        long = "engine.always-process-payload-attributes-on-canonical-head",
364        default_value_t = DefaultEngineValues::get_global().always_process_payload_attributes_on_canonical_head
365    )]
366    pub always_process_payload_attributes_on_canonical_head: bool,
367
368    /// Allow unwinding canonical header to ancestor during forkchoice updates.
369    /// See `TreeConfig::unwind_canonical_header` for more details.
370    #[arg(long = "engine.allow-unwind-canonical-header", default_value_t = DefaultEngineValues::get_global().allow_unwind_canonical_header)]
371    pub allow_unwind_canonical_header: bool,
372
373    /// Configure the number of storage proof workers in the Tokio blocking pool.
374    /// If not specified, defaults to 2x available parallelism.
375    #[arg(long = "engine.storage-worker-count", default_value = Resettable::from(DefaultEngineValues::get_global().storage_worker_count.map(|v| v.to_string().into())))]
376    pub storage_worker_count: Option<usize>,
377
378    /// Configure the number of account proof workers in the Tokio blocking pool.
379    /// If not specified, defaults to the same count as storage workers.
380    #[arg(long = "engine.account-worker-count", default_value = Resettable::from(DefaultEngineValues::get_global().account_worker_count.map(|v| v.to_string().into())))]
381    pub account_worker_count: Option<usize>,
382
383    /// Configure the number of prewarming threads.
384    /// If not specified, defaults to available parallelism.
385    #[arg(long = "engine.prewarming-threads", default_value = Resettable::from(DefaultEngineValues::get_global().prewarming_threads.map(|v| v.to_string().into())))]
386    pub prewarming_threads: Option<usize>,
387
388    /// Disable cache metrics recording, which can take up to 50ms with large cached state.
389    #[arg(long = "engine.disable-cache-metrics", default_value_t = DefaultEngineValues::get_global().cache_metrics_disabled)]
390    pub cache_metrics_disabled: bool,
391
392    /// LFU hot-slot capacity: max storage slots retained across sparse trie prune cycles.
393    #[arg(long = "engine.sparse-trie-max-hot-slots", alias = "engine.sparse-trie-max-storage-tries", default_value_t = DefaultEngineValues::get_global().sparse_trie_max_hot_slots)]
394    pub sparse_trie_max_hot_slots: usize,
395
396    /// LFU hot-account capacity: max account addresses retained across sparse trie prune cycles.
397    #[arg(long = "engine.sparse-trie-max-hot-accounts", default_value_t = DefaultEngineValues::get_global().sparse_trie_max_hot_accounts)]
398    pub sparse_trie_max_hot_accounts: usize,
399
400    /// Configure the slow block logging threshold in milliseconds.
401    ///
402    /// When set, blocks that take longer than this threshold to execute will be logged
403    /// with detailed metrics including timing, state operations, and cache statistics.
404    ///
405    /// Set to 0 to log all blocks (useful for debugging/profiling).
406    ///
407    /// When not set, slow block logging is disabled (default).
408    #[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())))]
409    pub slow_block_threshold: Option<Duration>,
410
411    /// Fully disable sparse trie cache pruning. When set, the cached sparse trie is preserved
412    /// without any node pruning or storage trie eviction between blocks. Useful for benchmarking
413    /// the effects of retaining the full trie cache.
414    #[arg(long = "engine.disable-sparse-trie-cache-pruning", default_value_t = DefaultEngineValues::get_global().disable_sparse_trie_cache_pruning)]
415    pub disable_sparse_trie_cache_pruning: bool,
416
417    /// Configure the timeout for the state root task before spawning a sequential fallback.
418    /// If the state root task takes longer than this, a sequential computation starts in
419    /// parallel and whichever finishes first is used.
420    ///
421    /// --engine.state-root-task-timeout 1s
422    /// --engine.state-root-task-timeout 400ms
423    ///
424    /// Set to 0s to disable.
425    #[arg(
426        long = "engine.state-root-task-timeout",
427        value_parser = humantime::parse_duration,
428        default_value = DefaultEngineValues::get_global().state_root_task_timeout.as_deref().unwrap_or("1s"),
429    )]
430    pub state_root_task_timeout: Option<Duration>,
431
432    /// Whether to share execution cache with the payload builder.
433    ///
434    /// When enabled, each payload job will get an instance of cross-block execution cache from the
435    /// engine.
436    ///
437    /// Note: this should only be enabled if node would not be requested to process any payloads in
438    /// parallel with payload building.
439    #[arg(
440        long = "engine.share-execution-cache-with-payload-builder",
441        default_value_t = DefaultEngineValues::get_global().share_execution_cache_with_payload_builder,
442    )]
443    pub share_execution_cache_with_payload_builder: bool,
444
445    /// Whether to share the sparse trie with the payload builder.
446    ///
447    /// Replaces the payload builder's blocking `state_root_with_updates()` call with the
448    /// sparse trie, computing the state root concurrently with transaction execution.
449    ///
450    /// The engine and payload builder contend for the same trie — if a builder task is
451    /// still running when `newPayload` arrives, the engine will block until the trie is
452    /// stored back.
453    ///
454    /// The builder also anchors the trie at the built block's state root, so if the next
455    /// `newPayload` is not on top of that block, the trie cache is invalidated and cleared.
456    #[arg(
457        long = "engine.share-sparse-trie-with-payload-builder",
458        default_value_t = DefaultEngineValues::get_global().share_sparse_trie_with_payload_builder,
459    )]
460    pub share_sparse_trie_with_payload_builder: bool,
461
462    /// Add random jitter before each proof computation (trie-debug only).
463    /// Each proof worker sleeps for a random duration up to this value before
464    /// starting work. Useful for stress-testing timing-sensitive proof logic.
465    ///
466    /// --engine.proof-jitter 100ms
467    /// --engine.proof-jitter 1s
468    #[cfg(feature = "trie-debug")]
469    #[arg(
470        long = "engine.proof-jitter",
471        value_parser = humantime::parse_duration,
472    )]
473    pub proof_jitter: Option<Duration>,
474}
475
476#[allow(deprecated)]
477impl Default for EngineArgs {
478    fn default() -> Self {
479        let DefaultEngineValues {
480            persistence_threshold,
481            persistence_backpressure_threshold,
482            memory_block_buffer_target,
483            legacy_state_root_task_enabled,
484            state_cache_disabled,
485            prewarming_disabled,
486            state_provider_metrics,
487            cross_block_cache_size,
488            state_root_task_compare_updates,
489            accept_execution_requests_hash,
490            multiproof_chunk_size,
491            reserved_cpu_cores,
492            precompile_cache_disabled,
493            state_root_fallback,
494            always_process_payload_attributes_on_canonical_head,
495            allow_unwind_canonical_header,
496            storage_worker_count,
497            account_worker_count,
498            prewarming_threads,
499            cache_metrics_disabled,
500            sparse_trie_max_hot_slots,
501            sparse_trie_max_hot_accounts,
502            slow_block_threshold,
503            disable_sparse_trie_cache_pruning,
504            state_root_task_timeout,
505            share_execution_cache_with_payload_builder,
506            share_sparse_trie_with_payload_builder,
507        } = DefaultEngineValues::get_global().clone();
508        Self {
509            persistence_threshold,
510            persistence_backpressure_threshold,
511            memory_block_buffer_target,
512            legacy_state_root_task_enabled,
513            state_root_task_compare_updates,
514            caching_and_prewarming_enabled: true,
515            state_cache_disabled,
516            prewarming_disabled,
517            parallel_sparse_trie_enabled: true,
518            parallel_sparse_trie_disabled: false,
519            state_provider_metrics,
520            cross_block_cache_size,
521            accept_execution_requests_hash,
522            multiproof_chunk_size,
523            reserved_cpu_cores,
524            precompile_cache_enabled: true,
525            precompile_cache_disabled,
526            state_root_fallback,
527            always_process_payload_attributes_on_canonical_head,
528            allow_unwind_canonical_header,
529            storage_worker_count,
530            account_worker_count,
531            prewarming_threads,
532            cache_metrics_disabled,
533            sparse_trie_max_hot_slots,
534            sparse_trie_max_hot_accounts,
535            slow_block_threshold,
536            disable_sparse_trie_cache_pruning,
537            state_root_task_timeout: state_root_task_timeout
538                .as_deref()
539                .map(|s| humantime::parse_duration(s).expect("valid default duration")),
540            share_execution_cache_with_payload_builder,
541            share_sparse_trie_with_payload_builder,
542            #[cfg(feature = "trie-debug")]
543            proof_jitter: None,
544        }
545    }
546}
547
548impl EngineArgs {
549    /// Validates cross-field engine arguments.
550    pub fn validate(&self) -> eyre::Result<()> {
551        ensure!(
552            self.persistence_backpressure_threshold > self.persistence_threshold,
553            "--engine.persistence-backpressure-threshold ({}) must be greater than --engine.persistence-threshold ({})",
554            self.persistence_backpressure_threshold,
555            self.persistence_threshold
556        );
557        Ok(())
558    }
559
560    /// Creates a [`TreeConfig`] from the engine arguments.
561    pub fn tree_config(&self) -> TreeConfig {
562        let config = TreeConfig::default()
563            .with_persistence_threshold(self.persistence_threshold)
564            .with_persistence_backpressure_threshold(self.persistence_backpressure_threshold)
565            .with_memory_block_buffer_target(self.memory_block_buffer_target)
566            .with_legacy_state_root(self.legacy_state_root_task_enabled)
567            .without_state_cache(self.state_cache_disabled)
568            .without_prewarming(self.prewarming_disabled)
569            .with_state_provider_metrics(self.state_provider_metrics)
570            .with_always_compare_trie_updates(self.state_root_task_compare_updates)
571            .with_cross_block_cache_size(self.cross_block_cache_size * 1024 * 1024)
572            .with_multiproof_chunk_size(self.multiproof_chunk_size)
573            .with_reserved_cpu_cores(self.reserved_cpu_cores)
574            .without_precompile_cache(self.precompile_cache_disabled)
575            .with_state_root_fallback(self.state_root_fallback)
576            .with_always_process_payload_attributes_on_canonical_head(
577                self.always_process_payload_attributes_on_canonical_head,
578            )
579            .with_unwind_canonical_header(self.allow_unwind_canonical_header)
580            .without_cache_metrics(self.cache_metrics_disabled)
581            .with_sparse_trie_max_hot_slots(self.sparse_trie_max_hot_slots)
582            .with_sparse_trie_max_hot_accounts(self.sparse_trie_max_hot_accounts)
583            .with_slow_block_threshold(self.slow_block_threshold)
584            .with_disable_sparse_trie_cache_pruning(self.disable_sparse_trie_cache_pruning)
585            .with_state_root_task_timeout(self.state_root_task_timeout.filter(|d| !d.is_zero()))
586            .with_share_execution_cache_with_payload_builder(
587                self.share_execution_cache_with_payload_builder,
588            )
589            .with_share_sparse_trie_with_payload_builder(
590                self.share_sparse_trie_with_payload_builder,
591            );
592        #[cfg(feature = "trie-debug")]
593        let config = config.with_proof_jitter(self.proof_jitter);
594        config
595    }
596}
597
598#[cfg(test)]
599mod tests {
600    use super::*;
601    use clap::Parser;
602
603    /// A helper type to parse Args more easily
604    #[derive(Parser)]
605    struct CommandParser<T: Args> {
606        #[command(flatten)]
607        args: T,
608    }
609
610    #[test]
611    fn test_parse_engine_args() {
612        let default_args = EngineArgs::default();
613        let args = CommandParser::<EngineArgs>::parse_from(["reth"]).args;
614        assert_eq!(args, default_args);
615    }
616
617    #[test]
618    #[allow(deprecated)]
619    fn engine_args() {
620        let args = EngineArgs {
621            persistence_threshold: 100,
622            persistence_backpressure_threshold: 101,
623            memory_block_buffer_target: 50,
624            legacy_state_root_task_enabled: true,
625            caching_and_prewarming_enabled: true,
626            state_cache_disabled: true,
627            prewarming_disabled: true,
628            parallel_sparse_trie_enabled: true,
629            parallel_sparse_trie_disabled: false,
630            state_provider_metrics: true,
631            cross_block_cache_size: 256,
632            state_root_task_compare_updates: true,
633            accept_execution_requests_hash: true,
634            multiproof_chunk_size: 512,
635            reserved_cpu_cores: 4,
636            precompile_cache_enabled: true,
637            precompile_cache_disabled: true,
638            state_root_fallback: true,
639            always_process_payload_attributes_on_canonical_head: true,
640            allow_unwind_canonical_header: true,
641            storage_worker_count: Some(16),
642            account_worker_count: Some(8),
643            prewarming_threads: Some(4),
644            cache_metrics_disabled: true,
645            sparse_trie_max_hot_slots: 100,
646            sparse_trie_max_hot_accounts: 500,
647            slow_block_threshold: None,
648            disable_sparse_trie_cache_pruning: true,
649            state_root_task_timeout: Some(Duration::from_secs(2)),
650            share_execution_cache_with_payload_builder: false,
651            share_sparse_trie_with_payload_builder: false,
652            #[cfg(feature = "trie-debug")]
653            proof_jitter: None,
654        };
655
656        let parsed_args = CommandParser::<EngineArgs>::parse_from([
657            "reth",
658            "--engine.persistence-threshold",
659            "100",
660            "--engine.persistence-backpressure-threshold",
661            "101",
662            "--engine.memory-block-buffer-target",
663            "50",
664            "--engine.legacy-state-root",
665            "--engine.disable-state-cache",
666            "--engine.disable-prewarming",
667            "--engine.state-provider-metrics",
668            "--engine.cross-block-cache-size",
669            "256",
670            "--engine.state-root-task-compare-updates",
671            "--engine.accept-execution-requests-hash",
672            "--engine.multiproof-chunk-size",
673            "512",
674            "--engine.reserved-cpu-cores",
675            "4",
676            "--engine.disable-precompile-cache",
677            "--engine.state-root-fallback",
678            "--engine.always-process-payload-attributes-on-canonical-head",
679            "--engine.allow-unwind-canonical-header",
680            "--engine.storage-worker-count",
681            "16",
682            "--engine.account-worker-count",
683            "8",
684            "--engine.prewarming-threads",
685            "4",
686            "--engine.disable-cache-metrics",
687            "--engine.sparse-trie-max-hot-slots",
688            "100",
689            "--engine.sparse-trie-max-hot-accounts",
690            "500",
691            "--engine.disable-sparse-trie-cache-pruning",
692            "--engine.state-root-task-timeout",
693            "2s",
694        ])
695        .args;
696
697        assert_eq!(parsed_args, args);
698    }
699
700    #[test]
701    fn validate_rejects_invalid_backpressure_threshold() {
702        let args = EngineArgs {
703            persistence_threshold: 4,
704            persistence_backpressure_threshold: 4,
705            ..EngineArgs::default()
706        };
707
708        let err = args.validate().unwrap_err().to_string();
709        assert!(err.contains("engine.persistence-backpressure-threshold"));
710        assert!(err.contains("engine.persistence-threshold"));
711    }
712
713    #[test]
714    fn test_parse_slow_block_threshold() {
715        // Test default value (None - disabled)
716        let args = CommandParser::<EngineArgs>::parse_from(["reth"]).args;
717        assert_eq!(args.slow_block_threshold, None);
718
719        // Test setting to 0 (log all blocks)
720        let args =
721            CommandParser::<EngineArgs>::parse_from(["reth", "--engine.slow-block-threshold", "0"])
722                .args;
723        assert_eq!(args.slow_block_threshold, Some(Duration::ZERO));
724
725        // Test setting to custom value
726        let args = CommandParser::<EngineArgs>::parse_from([
727            "reth",
728            "--engine.slow-block-threshold",
729            "500",
730        ])
731        .args;
732        assert_eq!(args.slow_block_threshold, Some(Duration::from_secs(500)));
733
734        let args = CommandParser::<EngineArgs>::parse_from([
735            "reth",
736            "--engine.slow-block-threshold",
737            "500ms",
738        ])
739        .args;
740        assert_eq!(args.slow_block_threshold, Some(Duration::from_millis(500)));
741    }
742
743    #[test]
744    fn test_parse_share_sparse_trie_flag() {
745        let args = CommandParser::<EngineArgs>::parse_from(["reth"]).args;
746        assert!(!args.share_sparse_trie_with_payload_builder);
747        assert!(!args.tree_config().share_sparse_trie_with_payload_builder());
748
749        let args = CommandParser::<EngineArgs>::parse_from([
750            "reth",
751            "--engine.share-sparse-trie-with-payload-builder",
752        ])
753        .args;
754        assert!(args.share_sparse_trie_with_payload_builder);
755        assert!(args.tree_config().share_sparse_trie_with_payload_builder());
756    }
757}