Skip to main content

reth_node_core/args/
engine.rs

1//! clap [Args](clap::Args) for engine purposes
2
3use clap::{builder::Resettable, Args};
4use reth_engine_primitives::{
5    TreeConfig, DEFAULT_MULTIPROOF_TASK_CHUNK_SIZE, DEFAULT_SPARSE_TRIE_MAX_STORAGE_TRIES,
6    DEFAULT_SPARSE_TRIE_PRUNE_DEPTH,
7};
8use std::{sync::OnceLock, time::Duration};
9
10use crate::node_config::{
11    DEFAULT_CROSS_BLOCK_CACHE_SIZE_MB, DEFAULT_MEMORY_BLOCK_BUFFER_TARGET,
12    DEFAULT_PERSISTENCE_THRESHOLD, DEFAULT_RESERVED_CPU_CORES,
13};
14
15/// Global static engine defaults
16static ENGINE_DEFAULTS: OnceLock<DefaultEngineValues> = OnceLock::new();
17
18/// Default values for engine that can be customized
19///
20/// Global defaults can be set via [`DefaultEngineValues::try_init`].
21#[derive(Debug, Clone)]
22pub struct DefaultEngineValues {
23    persistence_threshold: u64,
24    memory_block_buffer_target: u64,
25    legacy_state_root_task_enabled: bool,
26    state_cache_disabled: bool,
27    prewarming_disabled: bool,
28    state_provider_metrics: bool,
29    cross_block_cache_size: usize,
30    state_root_task_compare_updates: bool,
31    accept_execution_requests_hash: bool,
32    multiproof_chunk_size: usize,
33    reserved_cpu_cores: usize,
34    precompile_cache_disabled: bool,
35    state_root_fallback: bool,
36    always_process_payload_attributes_on_canonical_head: bool,
37    allow_unwind_canonical_header: bool,
38    storage_worker_count: Option<usize>,
39    account_worker_count: Option<usize>,
40    prewarming_threads: Option<usize>,
41    cache_metrics_disabled: bool,
42    sparse_trie_prune_depth: usize,
43    sparse_trie_max_storage_tries: usize,
44    disable_sparse_trie_cache_pruning: bool,
45    state_root_task_timeout: Option<String>,
46}
47
48impl DefaultEngineValues {
49    /// Initialize the global engine defaults with this configuration
50    pub fn try_init(self) -> Result<(), Self> {
51        ENGINE_DEFAULTS.set(self)
52    }
53
54    /// Get a reference to the global engine defaults
55    pub fn get_global() -> &'static Self {
56        ENGINE_DEFAULTS.get_or_init(Self::default)
57    }
58
59    /// Set the default persistence threshold
60    pub const fn with_persistence_threshold(mut self, v: u64) -> Self {
61        self.persistence_threshold = v;
62        self
63    }
64
65    /// Set the default memory block buffer target
66    pub const fn with_memory_block_buffer_target(mut self, v: u64) -> Self {
67        self.memory_block_buffer_target = v;
68        self
69    }
70
71    /// Set whether to enable legacy state root task by default
72    pub const fn with_legacy_state_root_task_enabled(mut self, v: bool) -> Self {
73        self.legacy_state_root_task_enabled = v;
74        self
75    }
76
77    /// Set whether to disable state cache by default
78    pub const fn with_state_cache_disabled(mut self, v: bool) -> Self {
79        self.state_cache_disabled = v;
80        self
81    }
82
83    /// Set whether to disable prewarming by default
84    pub const fn with_prewarming_disabled(mut self, v: bool) -> Self {
85        self.prewarming_disabled = v;
86        self
87    }
88
89    /// Set whether to enable state provider metrics by default
90    pub const fn with_state_provider_metrics(mut self, v: bool) -> Self {
91        self.state_provider_metrics = v;
92        self
93    }
94
95    /// Set the default cross-block cache size in MB
96    pub const fn with_cross_block_cache_size(mut self, v: usize) -> Self {
97        self.cross_block_cache_size = v;
98        self
99    }
100
101    /// Set whether to compare state root task updates by default
102    pub const fn with_state_root_task_compare_updates(mut self, v: bool) -> Self {
103        self.state_root_task_compare_updates = v;
104        self
105    }
106
107    /// Set whether to accept execution requests hash by default
108    pub const fn with_accept_execution_requests_hash(mut self, v: bool) -> Self {
109        self.accept_execution_requests_hash = v;
110        self
111    }
112
113    /// Set the default multiproof chunk size
114    pub const fn with_multiproof_chunk_size(mut self, v: usize) -> Self {
115        self.multiproof_chunk_size = v;
116        self
117    }
118
119    /// Set the default number of reserved CPU cores
120    pub const fn with_reserved_cpu_cores(mut self, v: usize) -> Self {
121        self.reserved_cpu_cores = v;
122        self
123    }
124
125    /// Set whether to disable precompile cache by default
126    pub const fn with_precompile_cache_disabled(mut self, v: bool) -> Self {
127        self.precompile_cache_disabled = v;
128        self
129    }
130
131    /// Set whether to enable state root fallback by default
132    pub const fn with_state_root_fallback(mut self, v: bool) -> Self {
133        self.state_root_fallback = v;
134        self
135    }
136
137    /// Set whether to always process payload attributes on canonical head by default
138    pub const fn with_always_process_payload_attributes_on_canonical_head(
139        mut self,
140        v: bool,
141    ) -> Self {
142        self.always_process_payload_attributes_on_canonical_head = v;
143        self
144    }
145
146    /// Set whether to allow unwinding canonical header by default
147    pub const fn with_allow_unwind_canonical_header(mut self, v: bool) -> Self {
148        self.allow_unwind_canonical_header = v;
149        self
150    }
151
152    /// Set the default storage worker count
153    pub const fn with_storage_worker_count(mut self, v: Option<usize>) -> Self {
154        self.storage_worker_count = v;
155        self
156    }
157
158    /// Set the default account worker count
159    pub const fn with_account_worker_count(mut self, v: Option<usize>) -> Self {
160        self.account_worker_count = v;
161        self
162    }
163
164    /// Set the default prewarming thread count
165    pub const fn with_prewarming_threads(mut self, v: Option<usize>) -> Self {
166        self.prewarming_threads = v;
167        self
168    }
169
170    /// Set whether to disable cache metrics by default
171    pub const fn with_cache_metrics_disabled(mut self, v: bool) -> Self {
172        self.cache_metrics_disabled = v;
173        self
174    }
175
176    /// Set the sparse trie prune depth by default
177    pub const fn with_sparse_trie_prune_depth(mut self, v: usize) -> Self {
178        self.sparse_trie_prune_depth = v;
179        self
180    }
181
182    /// Set the maximum number of storage tries to retain after sparse trie pruning by default
183    pub const fn with_sparse_trie_max_storage_tries(mut self, v: usize) -> Self {
184        self.sparse_trie_max_storage_tries = v;
185        self
186    }
187
188    /// Set whether to disable sparse trie cache pruning by default
189    pub const fn with_disable_sparse_trie_cache_pruning(mut self, v: bool) -> Self {
190        self.disable_sparse_trie_cache_pruning = v;
191        self
192    }
193
194    /// Set the default state root task timeout
195    pub fn with_state_root_task_timeout(mut self, v: Option<String>) -> Self {
196        self.state_root_task_timeout = v;
197        self
198    }
199}
200
201impl Default for DefaultEngineValues {
202    fn default() -> Self {
203        Self {
204            persistence_threshold: DEFAULT_PERSISTENCE_THRESHOLD,
205            memory_block_buffer_target: DEFAULT_MEMORY_BLOCK_BUFFER_TARGET,
206            legacy_state_root_task_enabled: false,
207            state_cache_disabled: false,
208            prewarming_disabled: false,
209            state_provider_metrics: false,
210            cross_block_cache_size: DEFAULT_CROSS_BLOCK_CACHE_SIZE_MB,
211            state_root_task_compare_updates: false,
212            accept_execution_requests_hash: false,
213            multiproof_chunk_size: DEFAULT_MULTIPROOF_TASK_CHUNK_SIZE,
214            reserved_cpu_cores: DEFAULT_RESERVED_CPU_CORES,
215            precompile_cache_disabled: false,
216            state_root_fallback: false,
217            always_process_payload_attributes_on_canonical_head: false,
218            allow_unwind_canonical_header: false,
219            storage_worker_count: None,
220            account_worker_count: None,
221            prewarming_threads: None,
222            cache_metrics_disabled: false,
223            sparse_trie_prune_depth: DEFAULT_SPARSE_TRIE_PRUNE_DEPTH,
224            sparse_trie_max_storage_tries: DEFAULT_SPARSE_TRIE_MAX_STORAGE_TRIES,
225            disable_sparse_trie_cache_pruning: false,
226            state_root_task_timeout: Some("1s".to_string()),
227        }
228    }
229}
230
231/// Parameters for configuring the engine driver.
232#[derive(Debug, Clone, Args, PartialEq, Eq)]
233#[command(next_help_heading = "Engine")]
234pub struct EngineArgs {
235    /// Configure persistence threshold for the engine. This determines how many canonical blocks
236    /// must be in-memory, ahead of the last persisted block, before flushing canonical blocks to
237    /// disk again.
238    ///
239    /// To persist blocks as fast as the node receives them, set this value to zero. This will
240    /// cause more frequent DB writes.
241    #[arg(long = "engine.persistence-threshold", default_value_t = DefaultEngineValues::get_global().persistence_threshold)]
242    pub persistence_threshold: u64,
243
244    /// Configure the target number of blocks to keep in memory.
245    #[arg(long = "engine.memory-block-buffer-target", default_value_t = DefaultEngineValues::get_global().memory_block_buffer_target)]
246    pub memory_block_buffer_target: u64,
247
248    /// Enable legacy state root
249    #[arg(long = "engine.legacy-state-root", default_value_t = DefaultEngineValues::get_global().legacy_state_root_task_enabled)]
250    pub legacy_state_root_task_enabled: bool,
251
252    /// CAUTION: This CLI flag has no effect anymore, use --engine.disable-caching-and-prewarming
253    /// if you want to disable caching and prewarming
254    #[arg(long = "engine.caching-and-prewarming", default_value = "true", hide = true)]
255    #[deprecated]
256    pub caching_and_prewarming_enabled: bool,
257
258    /// Disable state cache
259    #[arg(long = "engine.disable-state-cache", default_value_t = DefaultEngineValues::get_global().state_cache_disabled)]
260    pub state_cache_disabled: bool,
261
262    /// Disable parallel prewarming
263    #[arg(long = "engine.disable-prewarming", alias = "engine.disable-caching-and-prewarming", default_value_t = DefaultEngineValues::get_global().prewarming_disabled)]
264    pub prewarming_disabled: bool,
265
266    /// CAUTION: This CLI flag has no effect anymore. The parallel sparse trie is always enabled.
267    #[deprecated]
268    #[arg(long = "engine.parallel-sparse-trie", default_value = "true", hide = true)]
269    pub parallel_sparse_trie_enabled: bool,
270
271    /// CAUTION: This CLI flag has no effect anymore. The parallel sparse trie is always enabled.
272    #[deprecated]
273    #[arg(long = "engine.disable-parallel-sparse-trie", default_value = "false", hide = true)]
274    pub parallel_sparse_trie_disabled: bool,
275
276    /// Enable state provider latency metrics. This allows the engine to collect and report stats
277    /// about how long state provider calls took during execution, but this does introduce slight
278    /// overhead to state provider calls.
279    #[arg(long = "engine.state-provider-metrics", default_value_t = DefaultEngineValues::get_global().state_provider_metrics)]
280    pub state_provider_metrics: bool,
281
282    /// Configure the size of cross-block cache in megabytes
283    #[arg(long = "engine.cross-block-cache-size", default_value_t = DefaultEngineValues::get_global().cross_block_cache_size)]
284    pub cross_block_cache_size: usize,
285
286    /// Enable comparing trie updates from the state root task to the trie updates from the regular
287    /// state root calculation.
288    #[arg(long = "engine.state-root-task-compare-updates", default_value_t = DefaultEngineValues::get_global().state_root_task_compare_updates)]
289    pub state_root_task_compare_updates: bool,
290
291    /// Enables accepting requests hash instead of an array of requests in `engine_newPayloadV4`.
292    #[arg(long = "engine.accept-execution-requests-hash", default_value_t = DefaultEngineValues::get_global().accept_execution_requests_hash)]
293    pub accept_execution_requests_hash: bool,
294
295    /// Multiproof task chunk size for proof targets.
296    #[arg(long = "engine.multiproof-chunk-size", default_value_t = DefaultEngineValues::get_global().multiproof_chunk_size)]
297    pub multiproof_chunk_size: usize,
298
299    /// Configure the number of reserved CPU cores for non-reth processes
300    #[arg(long = "engine.reserved-cpu-cores", default_value_t = DefaultEngineValues::get_global().reserved_cpu_cores)]
301    pub reserved_cpu_cores: usize,
302
303    /// CAUTION: This CLI flag has no effect anymore, use --engine.disable-precompile-cache
304    /// if you want to disable precompile cache
305    #[arg(long = "engine.precompile-cache", default_value = "true", hide = true)]
306    #[deprecated]
307    pub precompile_cache_enabled: bool,
308
309    /// Disable precompile cache
310    #[arg(long = "engine.disable-precompile-cache", default_value_t = DefaultEngineValues::get_global().precompile_cache_disabled)]
311    pub precompile_cache_disabled: bool,
312
313    /// Enable state root fallback, useful for testing
314    #[arg(long = "engine.state-root-fallback", default_value_t = DefaultEngineValues::get_global().state_root_fallback)]
315    pub state_root_fallback: bool,
316
317    /// Always process payload attributes and begin a payload build process even if
318    /// `forkchoiceState.headBlockHash` is already the canonical head or an ancestor. See
319    /// `TreeConfig::always_process_payload_attributes_on_canonical_head` for more details.
320    ///
321    /// Note: This is a no-op on OP Stack.
322    #[arg(
323        long = "engine.always-process-payload-attributes-on-canonical-head",
324        default_value_t = DefaultEngineValues::get_global().always_process_payload_attributes_on_canonical_head
325    )]
326    pub always_process_payload_attributes_on_canonical_head: bool,
327
328    /// Allow unwinding canonical header to ancestor during forkchoice updates.
329    /// See `TreeConfig::unwind_canonical_header` for more details.
330    #[arg(long = "engine.allow-unwind-canonical-header", default_value_t = DefaultEngineValues::get_global().allow_unwind_canonical_header)]
331    pub allow_unwind_canonical_header: bool,
332
333    /// Configure the number of storage proof workers in the Tokio blocking pool.
334    /// If not specified, defaults to 2x available parallelism.
335    #[arg(long = "engine.storage-worker-count", default_value = Resettable::from(DefaultEngineValues::get_global().storage_worker_count.map(|v| v.to_string().into())))]
336    pub storage_worker_count: Option<usize>,
337
338    /// Configure the number of account proof workers in the Tokio blocking pool.
339    /// If not specified, defaults to the same count as storage workers.
340    #[arg(long = "engine.account-worker-count", default_value = Resettable::from(DefaultEngineValues::get_global().account_worker_count.map(|v| v.to_string().into())))]
341    pub account_worker_count: Option<usize>,
342
343    /// Configure the number of prewarming threads.
344    /// If not specified, defaults to available parallelism.
345    #[arg(long = "engine.prewarming-threads", default_value = Resettable::from(DefaultEngineValues::get_global().prewarming_threads.map(|v| v.to_string().into())))]
346    pub prewarming_threads: Option<usize>,
347
348    /// Disable cache metrics recording, which can take up to 50ms with large cached state.
349    #[arg(long = "engine.disable-cache-metrics", default_value_t = DefaultEngineValues::get_global().cache_metrics_disabled)]
350    pub cache_metrics_disabled: bool,
351
352    /// Sparse trie prune depth.
353    #[arg(long = "engine.sparse-trie-prune-depth", default_value_t = DefaultEngineValues::get_global().sparse_trie_prune_depth)]
354    pub sparse_trie_prune_depth: usize,
355
356    /// Maximum number of storage tries to retain after sparse trie pruning.
357    #[arg(long = "engine.sparse-trie-max-storage-tries", default_value_t = DefaultEngineValues::get_global().sparse_trie_max_storage_tries)]
358    pub sparse_trie_max_storage_tries: usize,
359
360    /// Fully disable sparse trie cache pruning. When set, the cached sparse trie is preserved
361    /// without any node pruning or storage trie eviction between blocks. Useful for benchmarking
362    /// the effects of retaining the full trie cache.
363    #[arg(long = "engine.disable-sparse-trie-cache-pruning", default_value_t = DefaultEngineValues::get_global().disable_sparse_trie_cache_pruning)]
364    pub disable_sparse_trie_cache_pruning: bool,
365
366    /// Configure the timeout for the state root task before spawning a sequential fallback.
367    /// If the state root task takes longer than this, a sequential computation starts in
368    /// parallel and whichever finishes first is used.
369    ///
370    /// --engine.state-root-task-timeout 1s
371    /// --engine.state-root-task-timeout 400ms
372    ///
373    /// Set to 0s to disable.
374    #[arg(
375        long = "engine.state-root-task-timeout",
376        value_parser = humantime::parse_duration,
377        default_value = DefaultEngineValues::get_global().state_root_task_timeout.as_deref().unwrap_or("1s"),
378    )]
379    pub state_root_task_timeout: Option<Duration>,
380}
381
382#[allow(deprecated)]
383impl Default for EngineArgs {
384    fn default() -> Self {
385        let DefaultEngineValues {
386            persistence_threshold,
387            memory_block_buffer_target,
388            legacy_state_root_task_enabled,
389            state_cache_disabled,
390            prewarming_disabled,
391            state_provider_metrics,
392            cross_block_cache_size,
393            state_root_task_compare_updates,
394            accept_execution_requests_hash,
395            multiproof_chunk_size,
396            reserved_cpu_cores,
397            precompile_cache_disabled,
398            state_root_fallback,
399            always_process_payload_attributes_on_canonical_head,
400            allow_unwind_canonical_header,
401            storage_worker_count,
402            account_worker_count,
403            prewarming_threads,
404            cache_metrics_disabled,
405            sparse_trie_prune_depth,
406            sparse_trie_max_storage_tries,
407            disable_sparse_trie_cache_pruning,
408            state_root_task_timeout,
409        } = DefaultEngineValues::get_global().clone();
410        Self {
411            persistence_threshold,
412            memory_block_buffer_target,
413            legacy_state_root_task_enabled,
414            state_root_task_compare_updates,
415            caching_and_prewarming_enabled: true,
416            state_cache_disabled,
417            prewarming_disabled,
418            parallel_sparse_trie_enabled: true,
419            parallel_sparse_trie_disabled: false,
420            state_provider_metrics,
421            cross_block_cache_size,
422            accept_execution_requests_hash,
423            multiproof_chunk_size,
424            reserved_cpu_cores,
425            precompile_cache_enabled: true,
426            precompile_cache_disabled,
427            state_root_fallback,
428            always_process_payload_attributes_on_canonical_head,
429            allow_unwind_canonical_header,
430            storage_worker_count,
431            account_worker_count,
432            prewarming_threads,
433            cache_metrics_disabled,
434            sparse_trie_prune_depth,
435            sparse_trie_max_storage_tries,
436            disable_sparse_trie_cache_pruning,
437            state_root_task_timeout: state_root_task_timeout
438                .as_deref()
439                .map(|s| humantime::parse_duration(s).expect("valid default duration")),
440        }
441    }
442}
443
444impl EngineArgs {
445    /// Creates a [`TreeConfig`] from the engine arguments.
446    pub fn tree_config(&self) -> TreeConfig {
447        TreeConfig::default()
448            .with_persistence_threshold(self.persistence_threshold)
449            .with_memory_block_buffer_target(self.memory_block_buffer_target)
450            .with_legacy_state_root(self.legacy_state_root_task_enabled)
451            .without_state_cache(self.state_cache_disabled)
452            .without_prewarming(self.prewarming_disabled)
453            .with_state_provider_metrics(self.state_provider_metrics)
454            .with_always_compare_trie_updates(self.state_root_task_compare_updates)
455            .with_cross_block_cache_size(self.cross_block_cache_size * 1024 * 1024)
456            .with_multiproof_chunk_size(self.multiproof_chunk_size)
457            .with_reserved_cpu_cores(self.reserved_cpu_cores)
458            .without_precompile_cache(self.precompile_cache_disabled)
459            .with_state_root_fallback(self.state_root_fallback)
460            .with_always_process_payload_attributes_on_canonical_head(
461                self.always_process_payload_attributes_on_canonical_head,
462            )
463            .with_unwind_canonical_header(self.allow_unwind_canonical_header)
464            .without_cache_metrics(self.cache_metrics_disabled)
465            .with_sparse_trie_prune_depth(self.sparse_trie_prune_depth)
466            .with_sparse_trie_max_storage_tries(self.sparse_trie_max_storage_tries)
467            .with_disable_sparse_trie_cache_pruning(self.disable_sparse_trie_cache_pruning)
468            .with_state_root_task_timeout(self.state_root_task_timeout.filter(|d| !d.is_zero()))
469    }
470}
471
472#[cfg(test)]
473mod tests {
474    use super::*;
475    use clap::Parser;
476
477    /// A helper type to parse Args more easily
478    #[derive(Parser)]
479    struct CommandParser<T: Args> {
480        #[command(flatten)]
481        args: T,
482    }
483
484    #[test]
485    fn test_parse_engine_args() {
486        let default_args = EngineArgs::default();
487        let args = CommandParser::<EngineArgs>::parse_from(["reth"]).args;
488        assert_eq!(args, default_args);
489    }
490
491    #[test]
492    #[allow(deprecated)]
493    fn engine_args() {
494        let args = EngineArgs {
495            persistence_threshold: 100,
496            memory_block_buffer_target: 50,
497            legacy_state_root_task_enabled: true,
498            caching_and_prewarming_enabled: true,
499            state_cache_disabled: true,
500            prewarming_disabled: true,
501            parallel_sparse_trie_enabled: true,
502            parallel_sparse_trie_disabled: false,
503            state_provider_metrics: true,
504            cross_block_cache_size: 256,
505            state_root_task_compare_updates: true,
506            accept_execution_requests_hash: true,
507            multiproof_chunk_size: 512,
508            reserved_cpu_cores: 4,
509            precompile_cache_enabled: true,
510            precompile_cache_disabled: true,
511            state_root_fallback: true,
512            always_process_payload_attributes_on_canonical_head: true,
513            allow_unwind_canonical_header: true,
514            storage_worker_count: Some(16),
515            account_worker_count: Some(8),
516            prewarming_threads: Some(4),
517            cache_metrics_disabled: true,
518            sparse_trie_prune_depth: 10,
519            sparse_trie_max_storage_tries: 100,
520            disable_sparse_trie_cache_pruning: true,
521            state_root_task_timeout: Some(Duration::from_secs(2)),
522        };
523
524        let parsed_args = CommandParser::<EngineArgs>::parse_from([
525            "reth",
526            "--engine.persistence-threshold",
527            "100",
528            "--engine.memory-block-buffer-target",
529            "50",
530            "--engine.legacy-state-root",
531            "--engine.disable-state-cache",
532            "--engine.disable-prewarming",
533            "--engine.state-provider-metrics",
534            "--engine.cross-block-cache-size",
535            "256",
536            "--engine.state-root-task-compare-updates",
537            "--engine.accept-execution-requests-hash",
538            "--engine.multiproof-chunk-size",
539            "512",
540            "--engine.reserved-cpu-cores",
541            "4",
542            "--engine.disable-precompile-cache",
543            "--engine.state-root-fallback",
544            "--engine.always-process-payload-attributes-on-canonical-head",
545            "--engine.allow-unwind-canonical-header",
546            "--engine.storage-worker-count",
547            "16",
548            "--engine.account-worker-count",
549            "8",
550            "--engine.prewarming-threads",
551            "4",
552            "--engine.disable-cache-metrics",
553            "--engine.sparse-trie-prune-depth",
554            "10",
555            "--engine.sparse-trie-max-storage-tries",
556            "100",
557            "--engine.disable-sparse-trie-cache-pruning",
558            "--engine.state-root-task-timeout",
559            "2s",
560        ])
561        .args;
562
563        assert_eq!(parsed_args, args);
564    }
565}