1use 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_BACKFILL_RUN_THRESHOLD, DEFAULT_INVALID_HEADER_HIT_EVICTION_THRESHOLD,
11 DEFAULT_MULTIPROOF_TASK_CHUNK_SIZE, DEFAULT_NUM_STATE_MASKING_BLOCKS,
12 MIN_PERSISTENCE_BACKPRESSURE_THRESHOLD,
13};
14use std::{num::NonZeroUsize, sync::OnceLock, time::Duration};
15
16use crate::node_config::{
17 DEFAULT_CROSS_BLOCK_CACHE_SIZE_MB, DEFAULT_MEMORY_BLOCK_BUFFER_TARGET,
18 DEFAULT_PERSISTENCE_THRESHOLD, DEFAULT_RESERVED_CPU_CORES,
19};
20
21static ENGINE_DEFAULTS: OnceLock<DefaultEngineValues> = OnceLock::new();
23
24#[derive(Debug, Clone)]
28pub struct DefaultEngineValues {
29 persistence_threshold: u64,
30 persistence_backpressure_threshold: u64,
31 num_state_masking_blocks: u64,
32 memory_block_buffer_target: u64,
33 backfill_run_threshold: u64,
34 invalid_header_hit_eviction_threshold: u8,
35 state_cache_disabled: bool,
36 prewarming_disabled: bool,
37 txpool_prewarming_enabled: bool,
38 sender_recovery_cache_enabled: bool,
39 state_provider_metrics: bool,
40 cross_block_cache_size: usize,
41 accept_execution_requests_hash: bool,
42 multiproof_chunk_size: usize,
43 reserved_cpu_cores: usize,
44 precompile_cache_disabled: bool,
45 state_root_fallback: bool,
46 always_process_payload_attributes_on_canonical_head: bool,
47 allow_unwind_canonical_header: bool,
48 storage_worker_count: Option<usize>,
49 account_worker_count: Option<usize>,
50 prewarming_threads: Option<usize>,
51 cache_metrics_disabled: bool,
52 slow_block_threshold: Option<Duration>,
53 disable_sparse_trie_cache_pruning: bool,
54 state_root_task_timeout: Option<String>,
55 share_execution_cache_with_payload_builder: bool,
56 share_sparse_trie_with_payload_builder: bool,
57 suppress_persistence_during_build: bool,
58 bal_parallel_execution_disabled: bool,
59 bal_parallel_state_root_disabled: bool,
60}
61
62impl DefaultEngineValues {
63 pub fn try_init(self) -> Result<(), Self> {
65 ENGINE_DEFAULTS.set(self)
66 }
67
68 pub fn get_global() -> &'static Self {
70 ENGINE_DEFAULTS.get_or_init(Self::default)
71 }
72
73 pub const fn with_persistence_threshold(mut self, v: u64) -> Self {
75 self.persistence_threshold = v;
76 self
77 }
78
79 pub const fn with_persistence_backpressure_threshold(mut self, v: u64) -> Self {
81 self.persistence_backpressure_threshold = v;
82 self
83 }
84
85 pub const fn with_num_state_masking_blocks(mut self, v: u64) -> Self {
87 self.num_state_masking_blocks = v;
88 self
89 }
90
91 pub const fn with_memory_block_buffer_target(mut self, v: u64) -> Self {
93 self.memory_block_buffer_target = v;
94 self
95 }
96
97 pub const fn with_backfill_run_threshold(mut self, v: u64) -> Self {
99 self.backfill_run_threshold = v;
100 self
101 }
102
103 pub const fn with_invalid_header_hit_eviction_threshold(mut self, v: u8) -> Self {
105 self.invalid_header_hit_eviction_threshold = v;
106 self
107 }
108
109 pub const fn with_state_cache_disabled(mut self, v: bool) -> Self {
111 self.state_cache_disabled = v;
112 self
113 }
114
115 pub const fn with_prewarming_disabled(mut self, v: bool) -> Self {
117 self.prewarming_disabled = v;
118 self
119 }
120
121 pub const fn with_txpool_prewarming_enabled(mut self, v: bool) -> Self {
123 self.txpool_prewarming_enabled = v;
124 self
125 }
126
127 pub const fn with_sender_recovery_cache_enabled(mut self, v: bool) -> Self {
129 self.sender_recovery_cache_enabled = v;
130 self
131 }
132
133 pub const fn with_state_provider_metrics(mut self, v: bool) -> Self {
135 self.state_provider_metrics = v;
136 self
137 }
138
139 pub const fn with_cross_block_cache_size(mut self, v: usize) -> Self {
141 self.cross_block_cache_size = v;
142 self
143 }
144
145 pub const fn with_accept_execution_requests_hash(mut self, v: bool) -> Self {
147 self.accept_execution_requests_hash = v;
148 self
149 }
150
151 pub const fn with_multiproof_chunk_size(mut self, v: usize) -> Self {
153 self.multiproof_chunk_size = v;
154 self
155 }
156
157 pub const fn with_reserved_cpu_cores(mut self, v: usize) -> Self {
159 self.reserved_cpu_cores = v;
160 self
161 }
162
163 pub const fn with_precompile_cache_disabled(mut self, v: bool) -> Self {
165 self.precompile_cache_disabled = v;
166 self
167 }
168
169 pub const fn with_state_root_fallback(mut self, v: bool) -> Self {
171 self.state_root_fallback = v;
172 self
173 }
174
175 pub const fn with_always_process_payload_attributes_on_canonical_head(
177 mut self,
178 v: bool,
179 ) -> Self {
180 self.always_process_payload_attributes_on_canonical_head = v;
181 self
182 }
183
184 pub const fn with_allow_unwind_canonical_header(mut self, v: bool) -> Self {
186 self.allow_unwind_canonical_header = v;
187 self
188 }
189
190 pub const fn with_storage_worker_count(mut self, v: Option<usize>) -> Self {
192 self.storage_worker_count = v;
193 self
194 }
195
196 pub const fn with_account_worker_count(mut self, v: Option<usize>) -> Self {
198 self.account_worker_count = v;
199 self
200 }
201
202 pub const fn with_prewarming_threads(mut self, v: Option<usize>) -> Self {
204 self.prewarming_threads = v;
205 self
206 }
207
208 pub const fn with_cache_metrics_disabled(mut self, v: bool) -> Self {
210 self.cache_metrics_disabled = v;
211 self
212 }
213
214 pub const fn with_slow_block_threshold(mut self, v: Option<Duration>) -> Self {
216 self.slow_block_threshold = v;
217 self
218 }
219
220 pub const fn with_disable_sparse_trie_cache_pruning(mut self, v: bool) -> Self {
222 self.disable_sparse_trie_cache_pruning = v;
223 self
224 }
225
226 pub fn with_state_root_task_timeout(mut self, v: Option<String>) -> Self {
228 self.state_root_task_timeout = v;
229 self
230 }
231
232 pub const fn with_share_execution_cache_with_payload_builder(mut self, v: bool) -> Self {
234 self.share_execution_cache_with_payload_builder = v;
235 self
236 }
237
238 pub const fn with_share_sparse_trie_with_payload_builder(mut self, v: bool) -> Self {
240 self.share_sparse_trie_with_payload_builder = v;
241 self
242 }
243
244 pub const fn with_suppress_persistence_during_build(mut self, v: bool) -> Self {
246 self.suppress_persistence_during_build = v;
247 self
248 }
249
250 pub const fn with_bal_parallel_execution_disabled(mut self, v: bool) -> Self {
252 self.bal_parallel_execution_disabled = v;
253 self
254 }
255
256 pub const fn with_bal_parallel_state_root_disabled(mut self, v: bool) -> Self {
258 self.bal_parallel_state_root_disabled = v;
259 self
260 }
261}
262
263impl Default for DefaultEngineValues {
264 fn default() -> Self {
265 Self {
266 persistence_threshold: DEFAULT_PERSISTENCE_THRESHOLD,
267 persistence_backpressure_threshold: MIN_PERSISTENCE_BACKPRESSURE_THRESHOLD,
268 num_state_masking_blocks: DEFAULT_NUM_STATE_MASKING_BLOCKS,
269 memory_block_buffer_target: DEFAULT_MEMORY_BLOCK_BUFFER_TARGET,
270 backfill_run_threshold: DEFAULT_BACKFILL_RUN_THRESHOLD,
271 invalid_header_hit_eviction_threshold: DEFAULT_INVALID_HEADER_HIT_EVICTION_THRESHOLD,
272 state_cache_disabled: false,
273 prewarming_disabled: false,
274 txpool_prewarming_enabled: false,
275 sender_recovery_cache_enabled: true,
276 state_provider_metrics: false,
277 cross_block_cache_size: DEFAULT_CROSS_BLOCK_CACHE_SIZE_MB,
278 accept_execution_requests_hash: false,
279 multiproof_chunk_size: DEFAULT_MULTIPROOF_TASK_CHUNK_SIZE,
280 reserved_cpu_cores: DEFAULT_RESERVED_CPU_CORES,
281 precompile_cache_disabled: false,
282 state_root_fallback: false,
283 always_process_payload_attributes_on_canonical_head: false,
284 allow_unwind_canonical_header: false,
285 storage_worker_count: None,
286 account_worker_count: None,
287 prewarming_threads: None,
288 cache_metrics_disabled: false,
289 slow_block_threshold: None,
290 disable_sparse_trie_cache_pruning: false,
291 state_root_task_timeout: Some("4s".to_string()),
292 share_execution_cache_with_payload_builder: false,
293 share_sparse_trie_with_payload_builder: false,
294 suppress_persistence_during_build: false,
295 bal_parallel_execution_disabled: false,
296 bal_parallel_state_root_disabled: false,
297 }
298 }
299}
300
301fn default_persistence_backpressure_threshold(persistence_threshold: u64) -> u64 {
302 DefaultEngineValues::get_global()
303 .persistence_backpressure_threshold
304 .max(persistence_threshold.saturating_mul(2))
305}
306
307#[derive(Debug, Clone, Args, PartialEq, Eq)]
309#[command(next_help_heading = "Engine")]
310pub struct EngineArgs {
311 #[arg(
318 long = "engine.persistence-threshold",
319 env = "RETH_ENGINE_PERSISTENCE_THRESHOLD",
320 default_value_t = DefaultEngineValues::get_global().persistence_threshold
321 )]
322 pub persistence_threshold: u64,
323
324 #[arg(long = "engine.persistence-backpressure-threshold")]
332 pub persistence_backpressure_threshold: Option<u64>,
333
334 #[arg(
337 long = "engine.num-state-masking-blocks",
338 env = "RETH_ENGINE_NUM_STATE_MASKING_BLOCKS",
339 default_value_t = DefaultEngineValues::get_global().num_state_masking_blocks
340 )]
341 pub num_state_masking_blocks: u64,
342
343 #[arg(long = "engine.memory-block-buffer-target")]
359 pub memory_block_buffer_target: Option<u64>,
360
361 #[arg(
369 long = "engine.backfill-threshold",
370 env = "RETH_ENGINE_BACKFILL_THRESHOLD",
371 default_value_t = DefaultEngineValues::get_global().backfill_run_threshold
372 )]
373 pub backfill_run_threshold: u64,
374
375 #[arg(long = "engine.invalid-header-cache-hit-eviction-threshold", default_value_t = DefaultEngineValues::get_global().invalid_header_hit_eviction_threshold)]
381 pub invalid_header_hit_eviction_threshold: u8,
382
383 #[arg(long = "engine.disable-state-cache", default_value_t = DefaultEngineValues::get_global().state_cache_disabled)]
385 pub state_cache_disabled: bool,
386
387 #[arg(long = "engine.disable-prewarming", alias = "engine.disable-caching-and-prewarming", default_value_t = DefaultEngineValues::get_global().prewarming_disabled)]
389 pub prewarming_disabled: bool,
390
391 #[arg(
393 long = "engine.txpool-prewarming",
394 env = "RETH_ENGINE_TXPOOL_PREWARMING",
395 default_value_t = DefaultEngineValues::get_global().txpool_prewarming_enabled
396 )]
397 pub txpool_prewarming_enabled: bool,
398
399 #[arg(
403 long = "engine.sender-recovery-cache",
404 env = "RETH_ENGINE_SENDER_RECOVERY_CACHE",
405 default_value_t = DefaultEngineValues::get_global().sender_recovery_cache_enabled,
406 num_args = 0..=1,
407 default_missing_value = "true",
408 )]
409 pub sender_recovery_cache_enabled: bool,
410
411 #[arg(long = "engine.state-provider-metrics", default_value_t = DefaultEngineValues::get_global().state_provider_metrics)]
415 pub state_provider_metrics: bool,
416
417 #[arg(long = "engine.cross-block-cache-size", default_value_t = DefaultEngineValues::get_global().cross_block_cache_size)]
419 pub cross_block_cache_size: usize,
420
421 #[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 #[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 #[arg(long = "engine.reserved-cpu-cores", default_value_t = DefaultEngineValues::get_global().reserved_cpu_cores)]
431 pub reserved_cpu_cores: usize,
432
433 #[arg(long = "cpu-cores", value_name = "COUNT")]
436 pub cpu_cores: Option<NonZeroUsize>,
437
438 #[arg(long = "engine.disable-precompile-cache", default_value_t = DefaultEngineValues::get_global().precompile_cache_disabled)]
440 pub precompile_cache_disabled: bool,
441
442 #[arg(long = "engine.state-root-fallback", default_value_t = DefaultEngineValues::get_global().state_root_fallback)]
444 pub state_root_fallback: bool,
445
446 #[arg(
452 long = "engine.always-process-payload-attributes-on-canonical-head",
453 default_value_t = DefaultEngineValues::get_global().always_process_payload_attributes_on_canonical_head
454 )]
455 pub always_process_payload_attributes_on_canonical_head: bool,
456
457 #[arg(long = "engine.allow-unwind-canonical-header", default_value_t = DefaultEngineValues::get_global().allow_unwind_canonical_header)]
460 pub allow_unwind_canonical_header: bool,
461
462 #[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 #[arg(long = "engine.account-worker-count", default_value = Resettable::from(DefaultEngineValues::get_global().account_worker_count.map(|v| v.to_string().into())))]
472 pub account_worker_count: Option<usize>,
473
474 #[arg(long = "engine.prewarming-threads", default_value = Resettable::from(DefaultEngineValues::get_global().prewarming_threads.map(|v| v.to_string().into())))]
477 pub prewarming_threads: Option<usize>,
478
479 #[arg(long = "engine.disable-cache-metrics", default_value_t = DefaultEngineValues::get_global().cache_metrics_disabled)]
481 pub cache_metrics_disabled: bool,
482
483 #[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())))]
492 pub slow_block_threshold: Option<Duration>,
493
494 #[arg(long = "engine.disable-sparse-trie-cache-pruning", default_value_t = DefaultEngineValues::get_global().disable_sparse_trie_cache_pruning)]
498 pub disable_sparse_trie_cache_pruning: bool,
499
500 #[arg(
509 long = "engine.state-root-task-timeout",
510 value_parser = humantime::parse_duration,
511 default_value = Resettable::from(DefaultEngineValues::get_global().state_root_task_timeout.as_ref().map(|v| v.as_str().into())),
512 )]
513 pub state_root_task_timeout: Option<Duration>,
514
515 #[arg(
523 long = "engine.share-execution-cache-with-payload-builder",
524 default_value_t = DefaultEngineValues::get_global().share_execution_cache_with_payload_builder,
525 )]
526 pub share_execution_cache_with_payload_builder: bool,
527
528 #[arg(
540 long = "engine.share-sparse-trie-with-payload-builder",
541 default_value_t = DefaultEngineValues::get_global().share_sparse_trie_with_payload_builder,
542 )]
543 pub share_sparse_trie_with_payload_builder: bool,
544
545 #[arg(
551 long = "engine.suppress-persistence-during-build",
552 default_value_t = DefaultEngineValues::get_global().suppress_persistence_during_build,
553 )]
554 pub suppress_persistence_during_build: bool,
555
556 #[arg(long = "engine.disable-bal-parallel-execution", default_value_t = DefaultEngineValues::get_global().bal_parallel_execution_disabled)]
558 pub bal_parallel_execution_disabled: bool,
559
560 #[arg(long = "engine.disable-bal-parallel-state-root", default_value_t = DefaultEngineValues::get_global().bal_parallel_state_root_disabled)]
563 pub bal_parallel_state_root_disabled: bool,
564
565 #[arg(long = "engine.disable-bal-batch-io", default_value_t = false)]
568 pub disable_bal_batch_io: bool,
569
570 #[arg(
573 long = "engine.prewarm-reuse",
574 env = "RETH_ENGINE_PREWARM_REUSE",
575 default_value_t = false
576 )]
577 pub prewarm_reuse: bool,
578
579 #[cfg(feature = "trie-debug")]
586 #[arg(
587 long = "engine.proof-jitter",
588 value_parser = humantime::parse_duration,
589 )]
590 pub proof_jitter: Option<Duration>,
591}
592
593impl Default for EngineArgs {
594 fn default() -> Self {
595 let DefaultEngineValues {
596 persistence_threshold,
597 persistence_backpressure_threshold: _,
598 num_state_masking_blocks,
599 memory_block_buffer_target: _,
600 backfill_run_threshold,
601 invalid_header_hit_eviction_threshold,
602 state_cache_disabled,
603 prewarming_disabled,
604 txpool_prewarming_enabled,
605 sender_recovery_cache_enabled,
606 state_provider_metrics,
607 cross_block_cache_size,
608 accept_execution_requests_hash,
609 multiproof_chunk_size,
610 reserved_cpu_cores,
611 precompile_cache_disabled,
612 state_root_fallback,
613 always_process_payload_attributes_on_canonical_head,
614 allow_unwind_canonical_header,
615 storage_worker_count,
616 account_worker_count,
617 prewarming_threads,
618 cache_metrics_disabled,
619 slow_block_threshold,
620 disable_sparse_trie_cache_pruning,
621 state_root_task_timeout,
622 share_execution_cache_with_payload_builder,
623 share_sparse_trie_with_payload_builder,
624 suppress_persistence_during_build,
625 bal_parallel_execution_disabled,
626 bal_parallel_state_root_disabled,
627 } = DefaultEngineValues::get_global().clone();
628 Self {
629 persistence_threshold,
630 persistence_backpressure_threshold: None,
631 num_state_masking_blocks,
632 memory_block_buffer_target: None,
633 backfill_run_threshold,
634 invalid_header_hit_eviction_threshold,
635 state_cache_disabled,
636 prewarming_disabled,
637 txpool_prewarming_enabled,
638 sender_recovery_cache_enabled,
639 state_provider_metrics,
640 cross_block_cache_size,
641 accept_execution_requests_hash,
642 multiproof_chunk_size,
643 reserved_cpu_cores,
644 cpu_cores: None,
645 precompile_cache_disabled,
646 state_root_fallback,
647 always_process_payload_attributes_on_canonical_head,
648 allow_unwind_canonical_header,
649 storage_worker_count,
650 account_worker_count,
651 prewarming_threads,
652 cache_metrics_disabled,
653 slow_block_threshold,
654 disable_sparse_trie_cache_pruning,
655 state_root_task_timeout: state_root_task_timeout
656 .as_deref()
657 .map(|s| humantime::parse_duration(s).expect("valid default duration")),
658 share_execution_cache_with_payload_builder,
659 share_sparse_trie_with_payload_builder,
660 suppress_persistence_during_build,
661 bal_parallel_execution_disabled,
662 bal_parallel_state_root_disabled,
663 disable_bal_batch_io: false,
664 prewarm_reuse: false,
665 #[cfg(feature = "trie-debug")]
666 proof_jitter: None,
667 }
668 }
669}
670
671impl EngineArgs {
672 pub const fn num_state_masking_blocks(&self) -> u64 {
674 if self.persistence_threshold == 0 {
675 0
676 } else {
677 self.num_state_masking_blocks
678 }
679 }
680
681 pub fn memory_block_buffer_target(&self) -> u64 {
683 self.memory_block_buffer_target.unwrap_or_else(|| {
684 self.persistence_threshold
685 .min(DefaultEngineValues::get_global().memory_block_buffer_target)
686 })
687 }
688
689 pub fn persistence_backpressure_threshold(&self) -> u64 {
691 self.persistence_backpressure_threshold.unwrap_or_else(|| {
692 default_persistence_backpressure_threshold(self.persistence_threshold)
693 })
694 }
695
696 pub fn validate(&self) -> eyre::Result<()> {
698 let persistence_backpressure_threshold = self.persistence_backpressure_threshold();
699 let memory_block_buffer_target = self.memory_block_buffer_target();
700 let num_state_masking_blocks = self.num_state_masking_blocks();
701 ensure!(
702 persistence_backpressure_threshold > self.persistence_threshold,
703 "--engine.persistence-backpressure-threshold ({}) must be greater than --engine.persistence-threshold ({})",
704 persistence_backpressure_threshold,
705 self.persistence_threshold
706 );
707 ensure!(
708 memory_block_buffer_target <= self.persistence_threshold,
709 "--engine.memory-block-buffer-target ({}) must be less than or equal to --engine.persistence-threshold ({})",
710 memory_block_buffer_target,
711 self.persistence_threshold,
712 );
713 ensure!(
714 num_state_masking_blocks == 0 ||
715 matches!(
716 num_state_masking_blocks.checked_add(memory_block_buffer_target),
717 Some(window) if window < self.persistence_threshold
718 ),
719 "--engine.num-state-masking-blocks ({}) + --engine.memory-block-buffer-target ({}) must be less than --engine.persistence-threshold ({})",
720 num_state_masking_blocks,
721 memory_block_buffer_target,
722 self.persistence_threshold,
723 );
724 ensure!(
725 !self.state_cache_disabled || !self.txpool_prewarming_enabled,
726 "--engine.txpool-prewarming conflicts with --engine.disable-state-cache"
727 );
728 ensure!(
729 self.bal_parallel_execution_disabled || !self.bal_parallel_state_root_disabled,
730 "--engine.disable-bal-parallel-state-root requires --engine.disable-bal-parallel-execution because BAL parallel execution depends on BAL prewarm state-root updates"
731 );
732 Ok(())
733 }
734
735 pub fn tree_config(&self) -> TreeConfig {
737 let config = TreeConfig::default()
738 .with_persistence_threshold(0)
740 .with_persistence_backpressure_threshold(self.persistence_backpressure_threshold())
741 .with_persistence_threshold(self.persistence_threshold)
742 .with_memory_block_buffer_target(self.memory_block_buffer_target())
743 .with_num_state_masking_blocks(self.num_state_masking_blocks())
744 .with_backfill_run_threshold(self.backfill_run_threshold)
745 .with_invalid_header_hit_eviction_threshold(self.invalid_header_hit_eviction_threshold)
746 .without_state_cache(self.state_cache_disabled)
747 .without_prewarming(self.prewarming_disabled)
748 .with_txpool_prewarming(self.txpool_prewarming_enabled)
749 .with_state_provider_metrics(self.state_provider_metrics)
750 .with_cross_block_cache_size(self.cross_block_cache_size * 1024 * 1024)
751 .with_multiproof_chunk_size(self.multiproof_chunk_size)
752 .with_reserved_cpu_cores(self.reserved_cpu_cores)
753 .without_precompile_cache(self.precompile_cache_disabled)
754 .with_state_root_fallback(self.state_root_fallback)
755 .with_always_process_payload_attributes_on_canonical_head(
756 self.always_process_payload_attributes_on_canonical_head,
757 )
758 .with_unwind_canonical_header(self.allow_unwind_canonical_header)
759 .without_cache_metrics(self.cache_metrics_disabled)
760 .with_slow_block_threshold(self.slow_block_threshold)
761 .with_disable_sparse_trie_cache_pruning(self.disable_sparse_trie_cache_pruning)
762 .with_state_root_task_timeout(self.state_root_task_timeout.filter(|d| !d.is_zero()))
763 .with_share_execution_cache_with_payload_builder(
764 self.share_execution_cache_with_payload_builder,
765 )
766 .with_share_sparse_trie_with_payload_builder(
767 self.share_sparse_trie_with_payload_builder,
768 )
769 .with_suppress_persistence_during_build(self.suppress_persistence_during_build)
770 .without_bal_parallel_execution(self.bal_parallel_execution_disabled)
771 .without_bal_parallel_state_root(self.bal_parallel_state_root_disabled)
772 .without_bal_batch_io(self.disable_bal_batch_io)
773 .with_prewarm_reuse(self.prewarm_reuse);
774 #[cfg(feature = "trie-debug")]
775 let config = config.with_proof_jitter(self.proof_jitter);
776 if let Some(cpu_cores) = self.cpu_cores {
777 config.with_has_enough_parallelism(cpu_cores.get() >= 5)
778 } else {
779 config
780 }
781 }
782}
783
784#[cfg(test)]
785mod tests {
786 use super::*;
787 use clap::Parser;
788
789 #[derive(Parser)]
791 struct CommandParser<T: Args> {
792 #[command(flatten)]
793 args: T,
794 }
795
796 #[test]
797 fn cpu_cores_override_controls_parallel_state_root() {
798 for (cores, enabled) in [(1, false), (4, false), (5, true), (8, true)] {
799 let args = CommandParser::<EngineArgs>::parse_from([
800 "reth",
801 "--cpu-cores",
802 &cores.to_string(),
803 ])
804 .args;
805 assert_eq!(args.cpu_cores.unwrap().get(), cores);
806 assert_eq!(args.tree_config().use_state_root_task(), enabled);
807 }
808 }
809
810 #[test]
811 fn cpu_cores_override_preserves_state_root_fallback() {
812 let args = CommandParser::<EngineArgs>::parse_from([
813 "reth",
814 "--cpu-cores",
815 "8",
816 "--engine.state-root-fallback",
817 ])
818 .args;
819 assert!(!args.tree_config().use_state_root_task());
820 }
821
822 #[test]
823 fn test_parse_engine_args() {
824 let default_args = EngineArgs::default();
825 let args = CommandParser::<EngineArgs>::parse_from(["reth"]).args;
826 assert_eq!(args, default_args);
827 assert_eq!(args.persistence_threshold, 50);
828 assert_eq!(args.num_state_masking_blocks, 30);
829 assert_eq!(args.memory_block_buffer_target, None);
830 assert_eq!(args.memory_block_buffer_target(), 5);
831 assert_eq!(args.persistence_backpressure_threshold(), 100);
832 args.validate().unwrap();
833 let config = args.tree_config();
834 assert_eq!(config.persistence_threshold(), 50);
835 assert_eq!(config.num_state_masking_blocks(), 30);
836 assert_eq!(config.persistence_backpressure_threshold(), 100);
837 }
838
839 #[test]
840 fn txpool_prewarming_is_disabled_by_default_and_can_be_enabled() {
841 let args = CommandParser::<EngineArgs>::parse_from(["reth"]).args;
842 assert!(!args.txpool_prewarming_enabled);
843 assert!(!args.tree_config().txpool_prewarming());
844
845 let args =
846 CommandParser::<EngineArgs>::parse_from(["reth", "--engine.txpool-prewarming"]).args;
847 assert!(args.txpool_prewarming_enabled);
848 assert!(args.tree_config().txpool_prewarming());
849 }
850
851 #[test]
852 fn validate_rejects_txpool_prewarming_with_disabled_state_cache() {
853 let args = EngineArgs {
854 state_cache_disabled: true,
855 txpool_prewarming_enabled: true,
856 ..EngineArgs::default()
857 };
858
859 let err = args.validate().unwrap_err().to_string();
860 assert!(err.contains("engine.txpool-prewarming"));
861 assert!(err.contains("engine.disable-state-cache"));
862 }
863
864 #[test]
865 fn default_backpressure_threshold_uses_parsed_persistence_args() {
866 let args = CommandParser::<EngineArgs>::parse_from([
867 "reth",
868 "--engine.persistence-threshold",
869 "100",
870 "--engine.memory-block-buffer-target",
871 "50",
872 ])
873 .args;
874
875 assert_eq!(args.persistence_backpressure_threshold(), 200);
876
877 let tree_config = args.tree_config();
878 assert_eq!(tree_config.persistence_threshold(), 100);
879 assert_eq!(tree_config.memory_block_buffer_target(), 50);
880 assert_eq!(tree_config.persistence_backpressure_threshold(), 200);
881 }
882
883 #[test]
884 fn default_backpressure_threshold_uses_minimum_when_larger() {
885 let args = CommandParser::<EngineArgs>::parse_from([
886 "reth",
887 "--engine.persistence-threshold",
888 "4",
889 ])
890 .args;
891
892 assert_eq!(
893 args.persistence_backpressure_threshold(),
894 DefaultEngineValues::get_global().persistence_backpressure_threshold
895 );
896 assert_eq!(args.persistence_backpressure_threshold(), 16);
897 }
898
899 #[test]
900 fn explicit_backpressure_threshold_overrides_calculated_default() {
901 let args = CommandParser::<EngineArgs>::parse_from([
902 "reth",
903 "--engine.persistence-threshold",
904 "100",
905 "--engine.memory-block-buffer-target",
906 "50",
907 "--engine.persistence-backpressure-threshold",
908 "101",
909 ])
910 .args;
911
912 assert_eq!(args.persistence_backpressure_threshold(), 101);
913 }
914
915 #[test]
916 fn sender_recovery_cache_is_enabled_by_default_and_can_be_disabled() {
917 let args = CommandParser::<EngineArgs>::parse_from(["reth"]).args;
918 assert!(args.sender_recovery_cache_enabled);
919
920 let args =
921 CommandParser::<EngineArgs>::parse_from(["reth", "--engine.sender-recovery-cache"])
922 .args;
923 assert!(args.sender_recovery_cache_enabled);
924
925 let args = CommandParser::<EngineArgs>::parse_from([
926 "reth",
927 "--engine.sender-recovery-cache=true",
928 ])
929 .args;
930 assert!(args.sender_recovery_cache_enabled);
931
932 let args = CommandParser::<EngineArgs>::parse_from([
933 "reth",
934 "--engine.sender-recovery-cache=false",
935 ])
936 .args;
937 assert!(!args.sender_recovery_cache_enabled);
938
939 let args = CommandParser::<EngineArgs>::parse_from([
940 "reth",
941 "--engine.sender-recovery-cache",
942 "false",
943 ])
944 .args;
945 assert!(!args.sender_recovery_cache_enabled);
946 }
947
948 #[test]
949 fn engine_args() {
950 let args = EngineArgs {
951 persistence_threshold: 100,
952 persistence_backpressure_threshold: Some(101),
953 num_state_masking_blocks: DEFAULT_NUM_STATE_MASKING_BLOCKS,
954 memory_block_buffer_target: Some(50),
955 backfill_run_threshold: 100,
956 invalid_header_hit_eviction_threshold: 7,
957 state_cache_disabled: true,
958 prewarming_disabled: true,
959 txpool_prewarming_enabled: false,
961 sender_recovery_cache_enabled: true,
962 state_provider_metrics: true,
963 cross_block_cache_size: 256,
964 accept_execution_requests_hash: true,
965 multiproof_chunk_size: 512,
966 reserved_cpu_cores: 4,
967 cpu_cores: Some(NonZeroUsize::new(8).unwrap()),
968 precompile_cache_disabled: true,
969 state_root_fallback: true,
970 always_process_payload_attributes_on_canonical_head: true,
971 allow_unwind_canonical_header: true,
972 storage_worker_count: Some(16),
973 account_worker_count: Some(8),
974 prewarming_threads: Some(4),
975 cache_metrics_disabled: true,
976 slow_block_threshold: None,
977 disable_sparse_trie_cache_pruning: true,
978 state_root_task_timeout: Some(Duration::from_secs(2)),
979 share_execution_cache_with_payload_builder: false,
980 share_sparse_trie_with_payload_builder: false,
981 suppress_persistence_during_build: false,
982 bal_parallel_execution_disabled: true,
983 bal_parallel_state_root_disabled: true,
984 disable_bal_batch_io: true,
985 prewarm_reuse: true,
986 #[cfg(feature = "trie-debug")]
987 proof_jitter: None,
988 };
989
990 let parsed_args = CommandParser::<EngineArgs>::parse_from([
991 "reth",
992 "--engine.persistence-threshold",
993 "100",
994 "--engine.persistence-backpressure-threshold",
995 "101",
996 "--engine.memory-block-buffer-target",
997 "50",
998 "--engine.backfill-threshold",
999 "100",
1000 "--engine.invalid-header-cache-hit-eviction-threshold",
1001 "7",
1002 "--engine.disable-state-cache",
1003 "--engine.disable-prewarming",
1004 "--engine.sender-recovery-cache",
1005 "--engine.state-provider-metrics",
1006 "--engine.cross-block-cache-size",
1007 "256",
1008 "--engine.accept-execution-requests-hash",
1009 "--engine.multiproof-chunk-size",
1010 "512",
1011 "--engine.reserved-cpu-cores",
1012 "4",
1013 "--cpu-cores",
1014 "8",
1015 "--engine.disable-precompile-cache",
1016 "--engine.state-root-fallback",
1017 "--engine.always-process-payload-attributes-on-canonical-head",
1018 "--engine.allow-unwind-canonical-header",
1019 "--engine.storage-worker-count",
1020 "16",
1021 "--engine.account-worker-count",
1022 "8",
1023 "--engine.prewarming-threads",
1024 "4",
1025 "--engine.disable-cache-metrics",
1026 "--engine.disable-sparse-trie-cache-pruning",
1027 "--engine.state-root-task-timeout",
1028 "2s",
1029 "--engine.disable-bal-parallel-execution",
1030 "--engine.disable-bal-parallel-state-root",
1031 "--engine.disable-bal-batch-io",
1032 "--engine.prewarm-reuse",
1033 ])
1034 .args;
1035
1036 assert_eq!(parsed_args, args);
1037 }
1038
1039 #[test]
1040 fn validate_rejects_invalid_backpressure_threshold() {
1041 let args = EngineArgs {
1042 persistence_threshold: 4,
1043 persistence_backpressure_threshold: Some(4),
1044 ..EngineArgs::default()
1045 };
1046
1047 let err = args.validate().unwrap_err().to_string();
1048 assert!(err.contains("engine.persistence-backpressure-threshold"));
1049 assert!(err.contains("engine.persistence-threshold"));
1050 }
1051
1052 #[test]
1053 fn validate_memory_block_buffer_target() {
1054 let args = EngineArgs {
1055 persistence_threshold: 4,
1056 num_state_masking_blocks: 0,
1057 memory_block_buffer_target: Some(4),
1058 ..EngineArgs::default()
1059 };
1060 args.validate().unwrap();
1061
1062 let args = EngineArgs { memory_block_buffer_target: Some(5), ..args };
1063 let err = args.validate().unwrap_err().to_string();
1064 assert!(err.contains("engine.memory-block-buffer-target"));
1065 assert!(err.contains("engine.persistence-threshold"));
1066 }
1067
1068 #[test]
1069 fn test_parse_backfill_threshold() {
1070 let args = CommandParser::<EngineArgs>::parse_from(["reth"]).args;
1071 let config = args.tree_config();
1072 assert_eq!(config.backfill_run_threshold(), DEFAULT_BACKFILL_RUN_THRESHOLD);
1073
1074 for (threshold, expected) in [("500", 500), ("2048", 1024)] {
1075 let args = CommandParser::<EngineArgs>::parse_from([
1076 "reth",
1077 "--engine.backfill-threshold",
1078 threshold,
1079 ])
1080 .args;
1081 assert_eq!(args.tree_config().backfill_run_threshold(), expected);
1082 }
1083 }
1084
1085 #[test]
1086 fn validate_rejects_state_masking_window_at_or_above_threshold() {
1087 let args = EngineArgs {
1088 persistence_threshold: 4,
1089 num_state_masking_blocks: 2,
1090 memory_block_buffer_target: Some(2),
1091 ..EngineArgs::default()
1092 };
1093
1094 let err = args.validate().unwrap_err().to_string();
1095 assert!(err.contains("engine.num-state-masking-blocks"));
1096 }
1097
1098 #[test]
1099 fn validate_rejects_overflowing_state_masking_window() {
1100 let args = EngineArgs {
1101 persistence_threshold: 7,
1102 num_state_masking_blocks: u64::MAX,
1103 ..EngineArgs::default()
1104 };
1105
1106 let err = args.validate().unwrap_err().to_string();
1107 assert!(err.contains("engine.num-state-masking-blocks"));
1108 }
1109
1110 #[test]
1111 fn default_memory_block_buffer_target_is_bounded_by_persistence_threshold() {
1112 let args = CommandParser::<EngineArgs>::parse_from([
1113 "reth",
1114 "--engine.persistence-threshold",
1115 "4",
1116 "--engine.num-state-masking-blocks",
1117 "0",
1118 ])
1119 .args;
1120
1121 assert_eq!(args.memory_block_buffer_target, None);
1122 assert_eq!(args.memory_block_buffer_target(), 4);
1123 assert_eq!(args.tree_config().memory_block_buffer_target(), 4);
1124 args.validate().unwrap();
1125 }
1126
1127 #[test]
1128 fn explicit_persistence_settings_can_be_lower_than_defaults() {
1129 let args = CommandParser::<EngineArgs>::parse_from([
1130 "reth",
1131 "--engine.persistence-threshold",
1132 "0",
1133 "--engine.persistence-backpressure-threshold",
1134 "1",
1135 ])
1136 .args;
1137
1138 args.validate().unwrap();
1139 let config = args.tree_config();
1140 assert_eq!(config.persistence_threshold(), 0);
1141 assert_eq!(config.num_state_masking_blocks(), 0);
1142 assert_eq!(config.memory_block_buffer_target(), 0);
1143 assert_eq!(config.persistence_backpressure_threshold(), 1);
1144 }
1145
1146 #[test]
1147 fn parse_rejects_zero_multiproof_chunk_size() {
1148 let result = CommandParser::<EngineArgs>::try_parse_from([
1149 "reth",
1150 "--engine.multiproof-chunk-size",
1151 "0",
1152 ]);
1153
1154 assert!(result.is_err());
1155 }
1156
1157 #[test]
1158 fn validate_rejects_bal_parallel_execution_without_bal_parallel_state_root() {
1159 let args = EngineArgs {
1160 bal_parallel_execution_disabled: false,
1161 bal_parallel_state_root_disabled: true,
1162 ..EngineArgs::default()
1163 };
1164
1165 let err = args.validate().unwrap_err().to_string();
1166 assert!(err.contains("engine.disable-bal-parallel-state-root"));
1167 assert!(err.contains("engine.disable-bal-parallel-execution"));
1168 }
1169
1170 #[test]
1171 fn test_parse_slow_block_threshold() {
1172 let args = CommandParser::<EngineArgs>::parse_from(["reth"]).args;
1174 assert_eq!(args.slow_block_threshold, None);
1175
1176 let args =
1178 CommandParser::<EngineArgs>::parse_from(["reth", "--engine.slow-block-threshold", "0"])
1179 .args;
1180 assert_eq!(args.slow_block_threshold, Some(Duration::ZERO));
1181
1182 let args = CommandParser::<EngineArgs>::parse_from([
1184 "reth",
1185 "--engine.slow-block-threshold",
1186 "500",
1187 ])
1188 .args;
1189 assert_eq!(args.slow_block_threshold, Some(Duration::from_secs(500)));
1190
1191 let args = CommandParser::<EngineArgs>::parse_from([
1192 "reth",
1193 "--engine.slow-block-threshold",
1194 "500ms",
1195 ])
1196 .args;
1197 assert_eq!(args.slow_block_threshold, Some(Duration::from_millis(500)));
1198 }
1199
1200 #[test]
1201 fn test_parse_invalid_header_hit_eviction_threshold() {
1202 let args = CommandParser::<EngineArgs>::parse_from(["reth"]).args;
1203 assert_eq!(
1204 args.invalid_header_hit_eviction_threshold,
1205 DEFAULT_INVALID_HEADER_HIT_EVICTION_THRESHOLD
1206 );
1207 assert_eq!(
1208 args.tree_config().invalid_header_hit_eviction_threshold(),
1209 DEFAULT_INVALID_HEADER_HIT_EVICTION_THRESHOLD
1210 );
1211
1212 let args = CommandParser::<EngineArgs>::parse_from([
1213 "reth",
1214 "--engine.invalid-header-cache-hit-eviction-threshold",
1215 "0",
1216 ])
1217 .args;
1218 assert_eq!(args.invalid_header_hit_eviction_threshold, 0);
1219 assert_eq!(args.tree_config().invalid_header_hit_eviction_threshold(), 0);
1220 }
1221
1222 #[test]
1223 fn test_parse_share_sparse_trie_flag() {
1224 let args = CommandParser::<EngineArgs>::parse_from(["reth"]).args;
1225 assert!(!args.share_sparse_trie_with_payload_builder);
1226 assert!(!args.tree_config().share_sparse_trie_with_payload_builder());
1227
1228 let args = CommandParser::<EngineArgs>::parse_from([
1229 "reth",
1230 "--engine.share-sparse-trie-with-payload-builder",
1231 ])
1232 .args;
1233 assert!(args.share_sparse_trie_with_payload_builder);
1234 assert!(args.tree_config().share_sparse_trie_with_payload_builder());
1235 }
1236}