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(
317        long = "engine.persistence-threshold",
318        env = "RETH_ENGINE_PERSISTENCE_THRESHOLD",
319        default_value_t = DefaultEngineValues::get_global().persistence_threshold
320    )]
321    pub persistence_threshold: u64,
322
323    /// Configure the maximum number of blocks beyond the in-memory buffer target that may await
324    /// persistence before engine API processing stalls.
325    ///
326    /// If omitted, this defaults to the larger of the default backpressure threshold and twice
327    /// `--engine.persistence-threshold`.
328    ///
329    /// This value must be greater than `--engine.persistence-threshold`.
330    #[arg(long = "engine.persistence-backpressure-threshold")]
331    pub persistence_backpressure_threshold: Option<u64>,
332
333    /// EXPERIMENTAL: Configure how many of the blocks being persisted should only mask state/trie
334    /// writes instead of durably persisting their state/trie updates in the current cycle.
335    #[arg(
336        long = "engine.num-state-masking-blocks",
337        env = "RETH_ENGINE_NUM_STATE_MASKING_BLOCKS",
338        default_value_t = DefaultEngineValues::get_global().num_state_masking_blocks
339    )]
340    pub num_state_masking_blocks: u64,
341
342    /// Configure the target number of blocks to keep in memory.
343    ///
344    /// If omitted, this defaults to the lesser of `--engine.persistence-threshold` and the
345    /// configured default memory block buffer target.
346    #[arg(long = "engine.memory-block-buffer-target")]
347    pub memory_block_buffer_target: Option<u64>,
348
349    /// Configure how many cache hits an invalid header can accumulate before it is evicted and
350    /// reprocessed.
351    ///
352    /// Set to `0` to effectively disable the cache because entries are evicted on the first
353    /// lookup.
354    #[arg(long = "engine.invalid-header-cache-hit-eviction-threshold", default_value_t = DefaultEngineValues::get_global().invalid_header_hit_eviction_threshold)]
355    pub invalid_header_hit_eviction_threshold: u8,
356
357    /// CAUTION: This CLI flag has no effect anymore, use --engine.state-root-fallback if you
358    /// want to force synchronous state root computation
359    #[arg(long = "engine.legacy-state-root", default_value_t = false, hide = true)]
360    #[deprecated]
361    pub legacy_state_root_task_enabled: bool,
362
363    /// CAUTION: This CLI flag has no effect anymore, use --engine.disable-caching-and-prewarming
364    /// if you want to disable caching and prewarming
365    #[arg(long = "engine.caching-and-prewarming", default_value = "true", hide = true)]
366    #[deprecated]
367    pub caching_and_prewarming_enabled: bool,
368
369    /// Disable state cache
370    #[arg(long = "engine.disable-state-cache", default_value_t = DefaultEngineValues::get_global().state_cache_disabled)]
371    pub state_cache_disabled: bool,
372
373    /// Disable parallel prewarming
374    #[arg(long = "engine.disable-prewarming", alias = "engine.disable-caching-and-prewarming", default_value_t = DefaultEngineValues::get_global().prewarming_disabled)]
375    pub prewarming_disabled: bool,
376
377    /// Enable best-effort txpool transaction prewarming between payloads.
378    #[arg(
379        long = "engine.txpool-prewarming",
380        env = "RETH_ENGINE_TXPOOL_PREWARMING",
381        default_value_t = DefaultEngineValues::get_global().txpool_prewarming_enabled
382    )]
383    pub txpool_prewarming_enabled: bool,
384
385    /// Enable caching recovered transaction senders across transaction ingress and payload
386    /// execution.
387    #[arg(
388        long = "engine.sender-recovery-cache",
389        env = "RETH_ENGINE_SENDER_RECOVERY_CACHE",
390        default_value_t = DefaultEngineValues::get_global().sender_recovery_cache_enabled
391    )]
392    pub sender_recovery_cache_enabled: bool,
393
394    /// CAUTION: This CLI flag has no effect anymore. The parallel sparse trie is always enabled.
395    #[deprecated]
396    #[arg(long = "engine.parallel-sparse-trie", default_value = "true", hide = true)]
397    pub parallel_sparse_trie_enabled: bool,
398
399    /// CAUTION: This CLI flag has no effect anymore. The parallel sparse trie is always enabled.
400    #[deprecated]
401    #[arg(long = "engine.disable-parallel-sparse-trie", default_value = "false", hide = true)]
402    pub parallel_sparse_trie_disabled: bool,
403
404    /// Enable state provider latency metrics. This allows the engine to collect and report stats
405    /// about how long state provider calls took during execution, but this does introduce slight
406    /// overhead to state provider calls.
407    #[arg(long = "engine.state-provider-metrics", default_value_t = DefaultEngineValues::get_global().state_provider_metrics)]
408    pub state_provider_metrics: bool,
409
410    /// Configure the size of cross-block cache in megabytes
411    #[arg(long = "engine.cross-block-cache-size", default_value_t = DefaultEngineValues::get_global().cross_block_cache_size)]
412    pub cross_block_cache_size: usize,
413
414    /// Enable comparing trie updates from the state root task to the trie updates from the regular
415    /// state root calculation.
416    #[arg(long = "engine.state-root-task-compare-updates", default_value_t = DefaultEngineValues::get_global().state_root_task_compare_updates)]
417    pub state_root_task_compare_updates: bool,
418
419    /// Enables accepting requests hash instead of an array of requests in `engine_newPayloadV4`.
420    #[arg(long = "engine.accept-execution-requests-hash", default_value_t = DefaultEngineValues::get_global().accept_execution_requests_hash)]
421    pub accept_execution_requests_hash: bool,
422
423    /// Multiproof task chunk size for proof targets.
424    #[arg(long = "engine.multiproof-chunk-size", default_value_t = DefaultEngineValues::get_global().multiproof_chunk_size, value_parser = RangedU64ValueParser::<usize>::new().range(1..))]
425    pub multiproof_chunk_size: usize,
426
427    /// Configure the number of reserved CPU cores for non-reth processes
428    #[arg(long = "engine.reserved-cpu-cores", default_value_t = DefaultEngineValues::get_global().reserved_cpu_cores)]
429    pub reserved_cpu_cores: usize,
430
431    /// CAUTION: This CLI flag has no effect anymore, use --engine.disable-precompile-cache
432    /// if you want to disable precompile cache
433    #[arg(long = "engine.precompile-cache", default_value = "true", hide = true)]
434    #[deprecated]
435    pub precompile_cache_enabled: bool,
436
437    /// Disable precompile cache
438    #[arg(long = "engine.disable-precompile-cache", default_value_t = DefaultEngineValues::get_global().precompile_cache_disabled)]
439    pub precompile_cache_disabled: bool,
440
441    /// Enable state root fallback, useful for testing
442    #[arg(long = "engine.state-root-fallback", default_value_t = DefaultEngineValues::get_global().state_root_fallback)]
443    pub state_root_fallback: bool,
444
445    /// Always process payload attributes and begin a payload build process even if
446    /// `forkchoiceState.headBlockHash` is already the canonical head or an ancestor. See
447    /// `TreeConfig::always_process_payload_attributes_on_canonical_head` for more details.
448    ///
449    /// Note: This is a no-op on OP Stack.
450    #[arg(
451        long = "engine.always-process-payload-attributes-on-canonical-head",
452        default_value_t = DefaultEngineValues::get_global().always_process_payload_attributes_on_canonical_head
453    )]
454    pub always_process_payload_attributes_on_canonical_head: bool,
455
456    /// Allow unwinding canonical header to ancestor during forkchoice updates.
457    /// See `TreeConfig::unwind_canonical_header` for more details.
458    #[arg(long = "engine.allow-unwind-canonical-header", default_value_t = DefaultEngineValues::get_global().allow_unwind_canonical_header)]
459    pub allow_unwind_canonical_header: bool,
460
461    /// Configure the number of storage proof workers in the Tokio blocking pool.
462    /// If not specified, defaults to 2x available parallelism.
463    #[arg(long = "engine.storage-worker-count", default_value = Resettable::from(DefaultEngineValues::get_global().storage_worker_count.map(|v| v.to_string().into())))]
464    pub storage_worker_count: Option<usize>,
465
466    /// Configure the number of account proof workers in the Tokio blocking pool.
467    /// If not specified, defaults to the same count as storage workers.
468    #[arg(long = "engine.account-worker-count", default_value = Resettable::from(DefaultEngineValues::get_global().account_worker_count.map(|v| v.to_string().into())))]
469    pub account_worker_count: Option<usize>,
470
471    /// Configure the number of prewarming threads.
472    /// If not specified, defaults to available parallelism.
473    #[arg(long = "engine.prewarming-threads", default_value = Resettable::from(DefaultEngineValues::get_global().prewarming_threads.map(|v| v.to_string().into())))]
474    pub prewarming_threads: Option<usize>,
475
476    /// Disable cache metrics recording, which can take up to 50ms with large cached state.
477    #[arg(long = "engine.disable-cache-metrics", default_value_t = DefaultEngineValues::get_global().cache_metrics_disabled)]
478    pub cache_metrics_disabled: bool,
479
480    /// Configure the slow block logging threshold in milliseconds.
481    ///
482    /// When set, blocks that take longer than this threshold to execute will be logged
483    /// with detailed metrics including timing, state operations, and cache statistics.
484    ///
485    /// Set to 0 to log all blocks (useful for debugging/profiling).
486    ///
487    /// When not set, slow block logging is disabled (default).
488    #[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())))]
489    pub slow_block_threshold: Option<Duration>,
490
491    /// Fully disable sparse trie cache pruning. When set, the cached sparse trie is preserved
492    /// without any node pruning or storage trie eviction between blocks. Useful for benchmarking
493    /// the effects of retaining the full trie cache.
494    #[arg(long = "engine.disable-sparse-trie-cache-pruning", default_value_t = DefaultEngineValues::get_global().disable_sparse_trie_cache_pruning)]
495    pub disable_sparse_trie_cache_pruning: bool,
496
497    /// Configure the timeout for the state root task before spawning a sequential fallback.
498    /// If the state root task takes longer than this, a sequential computation starts in
499    /// parallel and whichever finishes first is used.
500    ///
501    /// --engine.state-root-task-timeout 4s
502    /// --engine.state-root-task-timeout 400ms
503    ///
504    /// Set to 0s to disable.
505    #[arg(
506        long = "engine.state-root-task-timeout",
507        value_parser = humantime::parse_duration,
508        default_value = DefaultEngineValues::get_global().state_root_task_timeout.as_deref().unwrap_or("4s"),
509    )]
510    pub state_root_task_timeout: Option<Duration>,
511
512    /// Whether to share execution cache with the payload builder.
513    ///
514    /// When enabled, each payload job will get an instance of cross-block execution cache from the
515    /// engine.
516    ///
517    /// Note: this should only be enabled if node would not be requested to process any payloads in
518    /// parallel with payload building.
519    #[arg(
520        long = "engine.share-execution-cache-with-payload-builder",
521        default_value_t = DefaultEngineValues::get_global().share_execution_cache_with_payload_builder,
522    )]
523    pub share_execution_cache_with_payload_builder: bool,
524
525    /// Whether to share the sparse trie with the payload builder.
526    ///
527    /// Replaces the payload builder's blocking `state_root_with_updates()` call with the
528    /// sparse trie, computing the state root concurrently with transaction execution.
529    ///
530    /// The engine and payload builder contend for the same trie — if a builder task is
531    /// still running when `newPayload` arrives, the engine will block until the trie is
532    /// stored back.
533    ///
534    /// The builder also anchors the trie at the built block's state root, so if the next
535    /// `newPayload` is not on top of that block, the trie cache is invalidated and cleared.
536    #[arg(
537        long = "engine.share-sparse-trie-with-payload-builder",
538        default_value_t = DefaultEngineValues::get_global().share_sparse_trie_with_payload_builder,
539    )]
540    pub share_sparse_trie_with_payload_builder: bool,
541
542    /// Suppress persistence while building a payload.
543    ///
544    /// When enabled, persistence cycles are deferred while a payload build is active. Useful on
545    /// chains with short block times where persistence I/O can interfere with block building
546    /// latency.
547    #[arg(
548        long = "engine.suppress-persistence-during-build",
549        default_value_t = DefaultEngineValues::get_global().suppress_persistence_during_build,
550    )]
551    pub suppress_persistence_during_build: bool,
552
553    /// Disable BAL (Block Access List, EIP-7928) based parallel execution.
554    #[arg(long = "engine.disable-bal-parallel-execution", default_value_t = DefaultEngineValues::get_global().bal_parallel_execution_disabled)]
555    pub bal_parallel_execution_disabled: bool,
556
557    /// Disable BAL-driven parallel state root computation. This is only valid together with
558    /// `--engine.disable-bal-parallel-execution`.
559    #[arg(long = "engine.disable-bal-parallel-state-root", default_value_t = DefaultEngineValues::get_global().bal_parallel_state_root_disabled)]
560    pub bal_parallel_state_root_disabled: bool,
561
562    /// Disable BAL (Block Access List) storage prefetch IO during prewarming. When set, BAL
563    /// storage slots are not read into the execution cache.
564    #[arg(long = "engine.disable-bal-batch-io", default_value_t = false)]
565    pub disable_bal_batch_io: bool,
566
567    /// Add random jitter before each proof computation (trie-debug only).
568    /// Each proof worker sleeps for a random duration up to this value before
569    /// starting work. Useful for stress-testing timing-sensitive proof logic.
570    ///
571    /// --engine.proof-jitter 100ms
572    /// --engine.proof-jitter 1s
573    #[cfg(feature = "trie-debug")]
574    #[arg(
575        long = "engine.proof-jitter",
576        value_parser = humantime::parse_duration,
577    )]
578    pub proof_jitter: Option<Duration>,
579}
580
581#[allow(deprecated)]
582impl Default for EngineArgs {
583    fn default() -> Self {
584        let DefaultEngineValues {
585            persistence_threshold,
586            persistence_backpressure_threshold: _,
587            num_state_masking_blocks,
588            memory_block_buffer_target: _,
589            invalid_header_hit_eviction_threshold,
590            state_cache_disabled,
591            prewarming_disabled,
592            txpool_prewarming_enabled,
593            sender_recovery_cache_enabled,
594            state_provider_metrics,
595            cross_block_cache_size,
596            state_root_task_compare_updates,
597            accept_execution_requests_hash,
598            multiproof_chunk_size,
599            reserved_cpu_cores,
600            precompile_cache_disabled,
601            state_root_fallback,
602            always_process_payload_attributes_on_canonical_head,
603            allow_unwind_canonical_header,
604            storage_worker_count,
605            account_worker_count,
606            prewarming_threads,
607            cache_metrics_disabled,
608            slow_block_threshold,
609            disable_sparse_trie_cache_pruning,
610            state_root_task_timeout,
611            share_execution_cache_with_payload_builder,
612            share_sparse_trie_with_payload_builder,
613            suppress_persistence_during_build,
614            bal_parallel_execution_disabled,
615            bal_parallel_state_root_disabled,
616        } = DefaultEngineValues::get_global().clone();
617        Self {
618            persistence_threshold,
619            persistence_backpressure_threshold: None,
620            num_state_masking_blocks,
621            memory_block_buffer_target: None,
622            invalid_header_hit_eviction_threshold,
623            state_root_task_compare_updates,
624            legacy_state_root_task_enabled: false,
625            caching_and_prewarming_enabled: true,
626            state_cache_disabled,
627            prewarming_disabled,
628            txpool_prewarming_enabled,
629            sender_recovery_cache_enabled,
630            parallel_sparse_trie_enabled: true,
631            parallel_sparse_trie_disabled: false,
632            state_provider_metrics,
633            cross_block_cache_size,
634            accept_execution_requests_hash,
635            multiproof_chunk_size,
636            reserved_cpu_cores,
637            precompile_cache_enabled: true,
638            precompile_cache_disabled,
639            state_root_fallback,
640            always_process_payload_attributes_on_canonical_head,
641            allow_unwind_canonical_header,
642            storage_worker_count,
643            account_worker_count,
644            prewarming_threads,
645            cache_metrics_disabled,
646            slow_block_threshold,
647            disable_sparse_trie_cache_pruning,
648            state_root_task_timeout: state_root_task_timeout
649                .as_deref()
650                .map(|s| humantime::parse_duration(s).expect("valid default duration")),
651            share_execution_cache_with_payload_builder,
652            share_sparse_trie_with_payload_builder,
653            suppress_persistence_during_build,
654            bal_parallel_execution_disabled,
655            bal_parallel_state_root_disabled,
656            disable_bal_batch_io: false,
657            #[cfg(feature = "trie-debug")]
658            proof_jitter: None,
659        }
660    }
661}
662
663impl EngineArgs {
664    /// Returns the effective memory block buffer target.
665    pub fn memory_block_buffer_target(&self) -> u64 {
666        self.memory_block_buffer_target.unwrap_or_else(|| {
667            self.persistence_threshold
668                .min(DefaultEngineValues::get_global().memory_block_buffer_target)
669        })
670    }
671
672    /// Returns the effective persistence backpressure threshold.
673    pub fn persistence_backpressure_threshold(&self) -> u64 {
674        self.persistence_backpressure_threshold.unwrap_or_else(|| {
675            default_persistence_backpressure_threshold(self.persistence_threshold)
676        })
677    }
678
679    /// Validates cross-field engine arguments.
680    pub fn validate(&self) -> eyre::Result<()> {
681        let persistence_backpressure_threshold = self.persistence_backpressure_threshold();
682        let memory_block_buffer_target = self.memory_block_buffer_target();
683        ensure!(
684            persistence_backpressure_threshold > self.persistence_threshold,
685            "--engine.persistence-backpressure-threshold ({}) must be greater than --engine.persistence-threshold ({})",
686            persistence_backpressure_threshold,
687            self.persistence_threshold
688        );
689        ensure!(
690            memory_block_buffer_target <= self.persistence_threshold,
691            "--engine.memory-block-buffer-target ({}) must be less than or equal to --engine.persistence-threshold ({})",
692            memory_block_buffer_target,
693            self.persistence_threshold,
694        );
695        ensure!(
696            self.num_state_masking_blocks == 0 ||
697                matches!(
698                    self.num_state_masking_blocks.checked_add(memory_block_buffer_target),
699                    Some(window) if window < self.persistence_threshold
700                ),
701            "--engine.num-state-masking-blocks ({}) + --engine.memory-block-buffer-target ({}) must be less than --engine.persistence-threshold ({})",
702            self.num_state_masking_blocks,
703            memory_block_buffer_target,
704            self.persistence_threshold,
705        );
706        ensure!(
707            !self.state_cache_disabled || !self.txpool_prewarming_enabled,
708            "--engine.txpool-prewarming conflicts with --engine.disable-state-cache"
709        );
710        ensure!(
711            self.bal_parallel_execution_disabled || !self.bal_parallel_state_root_disabled,
712            "--engine.disable-bal-parallel-state-root requires --engine.disable-bal-parallel-execution because BAL parallel execution depends on BAL prewarm state-root updates"
713        );
714        Ok(())
715    }
716
717    /// Creates a [`TreeConfig`] from the engine arguments.
718    pub fn tree_config(&self) -> TreeConfig {
719        #[allow(deprecated)]
720        if self.legacy_state_root_task_enabled {
721            tracing::warn!(target: "reth::cli", "--engine.legacy-state-root has no effect anymore, use --engine.state-root-fallback to force synchronous state root computation");
722        }
723        let config = TreeConfig::default()
724            .with_persistence_backpressure_threshold(self.persistence_backpressure_threshold())
725            .with_persistence_threshold(self.persistence_threshold)
726            .with_memory_block_buffer_target(self.memory_block_buffer_target())
727            .with_num_state_masking_blocks(self.num_state_masking_blocks)
728            .with_invalid_header_hit_eviction_threshold(self.invalid_header_hit_eviction_threshold)
729            .without_state_cache(self.state_cache_disabled)
730            .without_prewarming(self.prewarming_disabled)
731            .with_txpool_prewarming(self.txpool_prewarming_enabled)
732            .with_state_provider_metrics(self.state_provider_metrics)
733            .with_always_compare_trie_updates(self.state_root_task_compare_updates)
734            .with_cross_block_cache_size(self.cross_block_cache_size * 1024 * 1024)
735            .with_multiproof_chunk_size(self.multiproof_chunk_size)
736            .with_reserved_cpu_cores(self.reserved_cpu_cores)
737            .without_precompile_cache(self.precompile_cache_disabled)
738            .with_state_root_fallback(self.state_root_fallback)
739            .with_always_process_payload_attributes_on_canonical_head(
740                self.always_process_payload_attributes_on_canonical_head,
741            )
742            .with_unwind_canonical_header(self.allow_unwind_canonical_header)
743            .without_cache_metrics(self.cache_metrics_disabled)
744            .with_slow_block_threshold(self.slow_block_threshold)
745            .with_disable_sparse_trie_cache_pruning(self.disable_sparse_trie_cache_pruning)
746            .with_state_root_task_timeout(self.state_root_task_timeout.filter(|d| !d.is_zero()))
747            .with_share_execution_cache_with_payload_builder(
748                self.share_execution_cache_with_payload_builder,
749            )
750            .with_share_sparse_trie_with_payload_builder(
751                self.share_sparse_trie_with_payload_builder,
752            )
753            .with_suppress_persistence_during_build(self.suppress_persistence_during_build)
754            .without_bal_parallel_execution(self.bal_parallel_execution_disabled)
755            .without_bal_parallel_state_root(self.bal_parallel_state_root_disabled)
756            .without_bal_batch_io(self.disable_bal_batch_io);
757        #[cfg(feature = "trie-debug")]
758        let config = config.with_proof_jitter(self.proof_jitter);
759        config
760    }
761}
762
763#[cfg(test)]
764mod tests {
765    use super::*;
766    use clap::Parser;
767
768    /// A helper type to parse Args more easily
769    #[derive(Parser)]
770    struct CommandParser<T: Args> {
771        #[command(flatten)]
772        args: T,
773    }
774
775    #[test]
776    fn test_parse_engine_args() {
777        let default_args = EngineArgs::default();
778        let args = CommandParser::<EngineArgs>::parse_from(["reth"]).args;
779        assert_eq!(args, default_args);
780        assert_eq!(args.persistence_threshold, 7);
781        assert_eq!(args.memory_block_buffer_target, None);
782        assert_eq!(args.memory_block_buffer_target(), 5);
783        assert_eq!(
784            args.persistence_backpressure_threshold(),
785            DefaultEngineValues::get_global().persistence_backpressure_threshold
786        );
787    }
788
789    #[test]
790    fn txpool_prewarming_is_disabled_by_default_and_can_be_enabled() {
791        let args = CommandParser::<EngineArgs>::parse_from(["reth"]).args;
792        assert!(!args.txpool_prewarming_enabled);
793        assert!(!args.tree_config().txpool_prewarming());
794
795        let args =
796            CommandParser::<EngineArgs>::parse_from(["reth", "--engine.txpool-prewarming"]).args;
797        assert!(args.txpool_prewarming_enabled);
798        assert!(args.tree_config().txpool_prewarming());
799    }
800
801    #[test]
802    fn validate_rejects_txpool_prewarming_with_disabled_state_cache() {
803        let args = EngineArgs {
804            state_cache_disabled: true,
805            txpool_prewarming_enabled: true,
806            ..EngineArgs::default()
807        };
808
809        let err = args.validate().unwrap_err().to_string();
810        assert!(err.contains("engine.txpool-prewarming"));
811        assert!(err.contains("engine.disable-state-cache"));
812    }
813
814    #[test]
815    fn default_backpressure_threshold_uses_parsed_persistence_args() {
816        let args = CommandParser::<EngineArgs>::parse_from([
817            "reth",
818            "--engine.persistence-threshold",
819            "100",
820            "--engine.memory-block-buffer-target",
821            "50",
822        ])
823        .args;
824
825        assert_eq!(args.persistence_backpressure_threshold(), 200);
826
827        let tree_config = args.tree_config();
828        assert_eq!(tree_config.persistence_threshold(), 100);
829        assert_eq!(tree_config.memory_block_buffer_target(), 50);
830        assert_eq!(tree_config.persistence_backpressure_threshold(), 200);
831    }
832
833    #[test]
834    fn default_backpressure_threshold_uses_global_default_when_larger() {
835        let args = CommandParser::<EngineArgs>::parse_from([
836            "reth",
837            "--engine.persistence-threshold",
838            "4",
839        ])
840        .args;
841
842        assert_eq!(
843            args.persistence_backpressure_threshold(),
844            DefaultEngineValues::get_global().persistence_backpressure_threshold
845        );
846    }
847
848    #[test]
849    fn explicit_backpressure_threshold_overrides_calculated_default() {
850        let args = CommandParser::<EngineArgs>::parse_from([
851            "reth",
852            "--engine.persistence-threshold",
853            "100",
854            "--engine.memory-block-buffer-target",
855            "50",
856            "--engine.persistence-backpressure-threshold",
857            "101",
858        ])
859        .args;
860
861        assert_eq!(args.persistence_backpressure_threshold(), 101);
862    }
863
864    #[test]
865    fn sender_recovery_cache_is_disabled_by_default_and_can_be_enabled() {
866        let args = CommandParser::<EngineArgs>::parse_from(["reth"]).args;
867        assert!(!args.sender_recovery_cache_enabled);
868
869        let args =
870            CommandParser::<EngineArgs>::parse_from(["reth", "--engine.sender-recovery-cache"])
871                .args;
872        assert!(args.sender_recovery_cache_enabled);
873    }
874
875    #[test]
876    #[allow(deprecated)]
877    fn engine_args() {
878        let args = EngineArgs {
879            persistence_threshold: 100,
880            persistence_backpressure_threshold: Some(101),
881            num_state_masking_blocks: DEFAULT_NUM_STATE_MASKING_BLOCKS,
882            memory_block_buffer_target: Some(50),
883            invalid_header_hit_eviction_threshold: 7,
884            legacy_state_root_task_enabled: true,
885            caching_and_prewarming_enabled: true,
886            state_cache_disabled: true,
887            prewarming_disabled: true,
888            // conflicts with --engine.disable-state-cache, covered by its own test below
889            txpool_prewarming_enabled: false,
890            sender_recovery_cache_enabled: true,
891            parallel_sparse_trie_enabled: true,
892            parallel_sparse_trie_disabled: false,
893            state_provider_metrics: true,
894            cross_block_cache_size: 256,
895            state_root_task_compare_updates: true,
896            accept_execution_requests_hash: true,
897            multiproof_chunk_size: 512,
898            reserved_cpu_cores: 4,
899            precompile_cache_enabled: true,
900            precompile_cache_disabled: true,
901            state_root_fallback: true,
902            always_process_payload_attributes_on_canonical_head: true,
903            allow_unwind_canonical_header: true,
904            storage_worker_count: Some(16),
905            account_worker_count: Some(8),
906            prewarming_threads: Some(4),
907            cache_metrics_disabled: true,
908            slow_block_threshold: None,
909            disable_sparse_trie_cache_pruning: true,
910            state_root_task_timeout: Some(Duration::from_secs(2)),
911            share_execution_cache_with_payload_builder: false,
912            share_sparse_trie_with_payload_builder: false,
913            suppress_persistence_during_build: false,
914            bal_parallel_execution_disabled: true,
915            bal_parallel_state_root_disabled: true,
916            disable_bal_batch_io: true,
917            #[cfg(feature = "trie-debug")]
918            proof_jitter: None,
919        };
920
921        let parsed_args = CommandParser::<EngineArgs>::parse_from([
922            "reth",
923            "--engine.persistence-threshold",
924            "100",
925            "--engine.persistence-backpressure-threshold",
926            "101",
927            "--engine.memory-block-buffer-target",
928            "50",
929            "--engine.invalid-header-cache-hit-eviction-threshold",
930            "7",
931            "--engine.legacy-state-root",
932            "--engine.disable-state-cache",
933            "--engine.disable-prewarming",
934            "--engine.sender-recovery-cache",
935            "--engine.state-provider-metrics",
936            "--engine.cross-block-cache-size",
937            "256",
938            "--engine.state-root-task-compare-updates",
939            "--engine.accept-execution-requests-hash",
940            "--engine.multiproof-chunk-size",
941            "512",
942            "--engine.reserved-cpu-cores",
943            "4",
944            "--engine.disable-precompile-cache",
945            "--engine.state-root-fallback",
946            "--engine.always-process-payload-attributes-on-canonical-head",
947            "--engine.allow-unwind-canonical-header",
948            "--engine.storage-worker-count",
949            "16",
950            "--engine.account-worker-count",
951            "8",
952            "--engine.prewarming-threads",
953            "4",
954            "--engine.disable-cache-metrics",
955            "--engine.disable-sparse-trie-cache-pruning",
956            "--engine.state-root-task-timeout",
957            "2s",
958            "--engine.disable-bal-parallel-execution",
959            "--engine.disable-bal-parallel-state-root",
960            "--engine.disable-bal-batch-io",
961        ])
962        .args;
963
964        assert_eq!(parsed_args, args);
965    }
966
967    #[test]
968    fn validate_rejects_invalid_backpressure_threshold() {
969        let args = EngineArgs {
970            persistence_threshold: 4,
971            persistence_backpressure_threshold: Some(4),
972            ..EngineArgs::default()
973        };
974
975        let err = args.validate().unwrap_err().to_string();
976        assert!(err.contains("engine.persistence-backpressure-threshold"));
977        assert!(err.contains("engine.persistence-threshold"));
978    }
979
980    #[test]
981    fn validate_memory_block_buffer_target() {
982        let args = EngineArgs {
983            persistence_threshold: 4,
984            memory_block_buffer_target: Some(4),
985            ..EngineArgs::default()
986        };
987        args.validate().unwrap();
988
989        let args = EngineArgs { memory_block_buffer_target: Some(5), ..args };
990        let err = args.validate().unwrap_err().to_string();
991        assert!(err.contains("engine.memory-block-buffer-target"));
992        assert!(err.contains("engine.persistence-threshold"));
993    }
994
995    #[test]
996    fn test_parse_num_state_masking_blocks() {
997        let args = CommandParser::<EngineArgs>::parse_from([
998            "reth",
999            "--engine.persistence-threshold",
1000            "13",
1001            "--engine.num-state-masking-blocks",
1002            "7",
1003        ])
1004        .args;
1005
1006        assert_eq!(args.tree_config().num_state_masking_blocks(), 7);
1007    }
1008
1009    #[test]
1010    fn validate_rejects_state_masking_window_at_or_above_threshold() {
1011        let args = EngineArgs {
1012            persistence_threshold: 4,
1013            num_state_masking_blocks: 2,
1014            memory_block_buffer_target: Some(2),
1015            ..EngineArgs::default()
1016        };
1017
1018        let err = args.validate().unwrap_err().to_string();
1019        assert!(err.contains("engine.num-state-masking-blocks"));
1020    }
1021
1022    #[test]
1023    fn validate_rejects_overflowing_state_masking_window() {
1024        let args = EngineArgs {
1025            persistence_threshold: 7,
1026            num_state_masking_blocks: u64::MAX,
1027            ..EngineArgs::default()
1028        };
1029
1030        let err = args.validate().unwrap_err().to_string();
1031        assert!(err.contains("engine.num-state-masking-blocks"));
1032    }
1033
1034    #[test]
1035    fn default_memory_block_buffer_target_is_bounded_by_persistence_threshold() {
1036        let args = CommandParser::<EngineArgs>::parse_from([
1037            "reth",
1038            "--engine.persistence-threshold",
1039            "4",
1040        ])
1041        .args;
1042
1043        assert_eq!(args.memory_block_buffer_target, None);
1044        assert_eq!(args.memory_block_buffer_target(), 4);
1045        assert_eq!(args.tree_config().memory_block_buffer_target(), 4);
1046        args.validate().unwrap();
1047    }
1048
1049    #[test]
1050    fn parse_rejects_zero_multiproof_chunk_size() {
1051        let result = CommandParser::<EngineArgs>::try_parse_from([
1052            "reth",
1053            "--engine.multiproof-chunk-size",
1054            "0",
1055        ]);
1056
1057        assert!(result.is_err());
1058    }
1059
1060    #[test]
1061    fn validate_rejects_bal_parallel_execution_without_bal_parallel_state_root() {
1062        let args = EngineArgs {
1063            bal_parallel_execution_disabled: false,
1064            bal_parallel_state_root_disabled: true,
1065            ..EngineArgs::default()
1066        };
1067
1068        let err = args.validate().unwrap_err().to_string();
1069        assert!(err.contains("engine.disable-bal-parallel-state-root"));
1070        assert!(err.contains("engine.disable-bal-parallel-execution"));
1071    }
1072
1073    #[test]
1074    fn test_parse_slow_block_threshold() {
1075        // Test default value (None - disabled)
1076        let args = CommandParser::<EngineArgs>::parse_from(["reth"]).args;
1077        assert_eq!(args.slow_block_threshold, None);
1078
1079        // Test setting to 0 (log all blocks)
1080        let args =
1081            CommandParser::<EngineArgs>::parse_from(["reth", "--engine.slow-block-threshold", "0"])
1082                .args;
1083        assert_eq!(args.slow_block_threshold, Some(Duration::ZERO));
1084
1085        // Test setting to custom value
1086        let args = CommandParser::<EngineArgs>::parse_from([
1087            "reth",
1088            "--engine.slow-block-threshold",
1089            "500",
1090        ])
1091        .args;
1092        assert_eq!(args.slow_block_threshold, Some(Duration::from_secs(500)));
1093
1094        let args = CommandParser::<EngineArgs>::parse_from([
1095            "reth",
1096            "--engine.slow-block-threshold",
1097            "500ms",
1098        ])
1099        .args;
1100        assert_eq!(args.slow_block_threshold, Some(Duration::from_millis(500)));
1101    }
1102
1103    #[test]
1104    fn test_parse_invalid_header_hit_eviction_threshold() {
1105        let args = CommandParser::<EngineArgs>::parse_from(["reth"]).args;
1106        assert_eq!(
1107            args.invalid_header_hit_eviction_threshold,
1108            DEFAULT_INVALID_HEADER_HIT_EVICTION_THRESHOLD
1109        );
1110        assert_eq!(
1111            args.tree_config().invalid_header_hit_eviction_threshold(),
1112            DEFAULT_INVALID_HEADER_HIT_EVICTION_THRESHOLD
1113        );
1114
1115        let args = CommandParser::<EngineArgs>::parse_from([
1116            "reth",
1117            "--engine.invalid-header-cache-hit-eviction-threshold",
1118            "0",
1119        ])
1120        .args;
1121        assert_eq!(args.invalid_header_hit_eviction_threshold, 0);
1122        assert_eq!(args.tree_config().invalid_header_hit_eviction_threshold(), 0);
1123    }
1124
1125    #[test]
1126    fn test_parse_share_sparse_trie_flag() {
1127        let args = CommandParser::<EngineArgs>::parse_from(["reth"]).args;
1128        assert!(!args.share_sparse_trie_with_payload_builder);
1129        assert!(!args.tree_config().share_sparse_trie_with_payload_builder());
1130
1131        let args = CommandParser::<EngineArgs>::parse_from([
1132            "reth",
1133            "--engine.share-sparse-trie-with-payload-builder",
1134        ])
1135        .args;
1136        assert!(args.share_sparse_trie_with_payload_builder);
1137        assert!(args.tree_config().share_sparse_trie_with_payload_builder());
1138    }
1139}