Skip to main content

reth_node_core/args/
engine.rs

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