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