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