Skip to main content

reth_engine_primitives/
config.rs

1//! Engine tree configuration.
2
3use alloy_eips::merge::EPOCH_SLOTS;
4use core::time::Duration;
5
6/// Triggers persistence when the number of canonical blocks in memory exceeds this threshold.
7pub const DEFAULT_PERSISTENCE_THRESHOLD: u64 = 2;
8
9/// How close to the canonical head we persist blocks.
10pub const DEFAULT_MEMORY_BLOCK_BUFFER_TARGET: u64 = 0;
11
12/// The size of proof targets chunk to spawn in one multiproof calculation.
13pub const DEFAULT_MULTIPROOF_TASK_CHUNK_SIZE: usize = 5;
14
15/// Gas threshold below which the small block chunk size is used.
16pub const SMALL_BLOCK_GAS_THRESHOLD: u64 = 20_000_000;
17
18/// Default number of reserved CPU cores for non-reth processes.
19///
20/// This will be deducted from the thread count of main reth global threadpool.
21pub const DEFAULT_RESERVED_CPU_CORES: usize = 1;
22
23/// Default depth for sparse trie pruning.
24///
25/// Nodes at this depth and below are converted to hash stubs to reduce memory.
26/// Depth 4 means we keep roughly 16^4 = 65536 potential branch paths at most.
27pub const DEFAULT_SPARSE_TRIE_PRUNE_DEPTH: usize = 4;
28
29/// Default LFU hot-slot capacity for sparse trie pruning.
30///
31/// Limits the number of `(address, slot)` pairs retained across prune cycles.
32pub const DEFAULT_SPARSE_TRIE_MAX_HOT_SLOTS: usize = 1500;
33
34/// Default LFU hot-account capacity for sparse trie pruning.
35///
36/// Limits the number of account addresses retained across prune cycles.
37pub const DEFAULT_SPARSE_TRIE_MAX_HOT_ACCOUNTS: usize = 1000;
38
39/// Default timeout for the state root task before spawning a sequential fallback.
40pub const DEFAULT_STATE_ROOT_TASK_TIMEOUT: Duration = Duration::from_secs(1);
41
42const DEFAULT_BLOCK_BUFFER_LIMIT: u32 = EPOCH_SLOTS as u32 * 2;
43const DEFAULT_MAX_INVALID_HEADER_CACHE_LENGTH: u32 = 256;
44const DEFAULT_MAX_EXECUTE_BLOCK_BATCH_SIZE: usize = 4;
45const DEFAULT_CROSS_BLOCK_CACHE_SIZE: usize = default_cross_block_cache_size();
46
47const fn default_cross_block_cache_size() -> usize {
48    if cfg!(test) {
49        1024 * 1024 // 1 MB in tests
50    } else if cfg!(target_pointer_width = "32") {
51        usize::MAX // max possible on wasm32 / 32-bit
52    } else {
53        4 * 1024 * 1024 * 1024 // 4 GB on 64-bit
54    }
55}
56
57/// Determines if the host has enough parallelism to run the payload processor.
58///
59/// It requires at least 5 parallel threads:
60/// - Engine in main thread that spawns the state root task.
61/// - Multiproof task in payload processor
62/// - Sparse Trie task in payload processor
63/// - Multiproof computation spawned in payload processor
64/// - Storage root computation spawned in trie parallel proof
65pub fn has_enough_parallelism() -> bool {
66    #[cfg(feature = "std")]
67    {
68        std::thread::available_parallelism().is_ok_and(|num| num.get() >= 5)
69    }
70    #[cfg(not(feature = "std"))]
71    false
72}
73
74/// The configuration of the engine tree.
75#[derive(Debug, Clone)]
76pub struct TreeConfig {
77    /// Maximum number of blocks to be kept only in memory without triggering
78    /// persistence.
79    persistence_threshold: u64,
80    /// How close to the canonical head we persist blocks. Represents the ideal
81    /// number of most recent blocks to keep in memory for quick access and reorgs.
82    ///
83    /// Note: this should be less than or equal to `persistence_threshold`.
84    memory_block_buffer_target: u64,
85    /// Number of pending blocks that cannot be executed due to missing parent and
86    /// are kept in cache.
87    block_buffer_limit: u32,
88    /// Number of invalid headers to keep in cache.
89    max_invalid_header_cache_length: u32,
90    /// Maximum number of blocks to execute sequentially in a batch.
91    ///
92    /// This is used as a cutoff to prevent long-running sequential block execution when we receive
93    /// a batch of downloaded blocks.
94    max_execute_block_batch_size: usize,
95    /// Whether to use the legacy state root calculation method instead of the
96    /// new state root task.
97    legacy_state_root: bool,
98    /// Whether to always compare trie updates from the state root task to the trie updates from
99    /// the regular state root calculation.
100    always_compare_trie_updates: bool,
101    /// Whether to disable state cache.
102    disable_state_cache: bool,
103    /// Whether to disable parallel prewarming.
104    disable_prewarming: bool,
105    /// Whether to enable state provider metrics.
106    state_provider_metrics: bool,
107    /// Cross-block cache size in bytes.
108    cross_block_cache_size: usize,
109    /// Whether the host has enough parallelism to run state root task.
110    has_enough_parallelism: bool,
111    /// Multiproof task chunk size for proof targets.
112    multiproof_chunk_size: usize,
113    /// Number of reserved CPU cores for non-reth processes
114    reserved_cpu_cores: usize,
115    /// Whether to disable the precompile cache
116    precompile_cache_disabled: bool,
117    /// Whether to use state root fallback for testing
118    state_root_fallback: bool,
119    /// Whether to always process payload attributes and begin a payload build process
120    /// even if `forkchoiceState.headBlockHash` is already the canonical head or an ancestor.
121    ///
122    /// The Engine API specification generally states that client software "MUST NOT begin a
123    /// payload build process if `forkchoiceState.headBlockHash` references a `VALID`
124    /// ancestor of the head of canonical chain".
125    /// See: <https://github.com/ethereum/execution-apis/blob/main/src/engine/paris.md#engine_forkchoiceupdatedv1> (Rule 2)
126    ///
127    /// This flag allows overriding that behavior.
128    /// This is useful for specific chain configurations (e.g., OP Stack where proposers
129    /// can reorg their own chain), various custom chains, or for development/testing purposes
130    /// where immediate payload regeneration is desired despite the head not changing or moving to
131    /// an ancestor.
132    always_process_payload_attributes_on_canonical_head: bool,
133    /// Whether to unwind canonical header to ancestor during forkchoice updates.
134    allow_unwind_canonical_header: bool,
135    /// Whether to disable cache metrics recording (can be expensive with large cached state).
136    disable_cache_metrics: bool,
137    /// Depth for sparse trie pruning after state root computation.
138    sparse_trie_prune_depth: usize,
139    /// LFU hot-slot capacity: max `(address, slot)` pairs retained across prune cycles.
140    sparse_trie_max_hot_slots: usize,
141    /// LFU hot-account capacity: max account addresses retained across prune cycles.
142    sparse_trie_max_hot_accounts: usize,
143    /// When set, blocks whose total processing time (execution + state reads + state root +
144    /// DB commit) exceeds this duration trigger a structured `warn!` log with detailed timing,
145    /// state-operation counts, and cache hit-rate metrics. `Duration::ZERO` logs every block.
146    slow_block_threshold: Option<Duration>,
147    /// Whether to fully disable sparse trie cache pruning between blocks.
148    disable_sparse_trie_cache_pruning: bool,
149    /// Timeout for the state root task before spawning a sequential fallback computation.
150    /// If `Some`, after waiting this duration for the state root task, a sequential state root
151    /// computation is spawned in parallel and whichever finishes first is used.
152    /// If `None`, the timeout fallback is disabled.
153    state_root_task_timeout: Option<Duration>,
154    /// Maximum random jitter applied before each proof computation (trie-debug only).
155    /// When set, each proof worker sleeps for a random duration up to this value
156    /// before starting a proof calculation.
157    #[cfg(feature = "trie-debug")]
158    proof_jitter: Option<Duration>,
159}
160
161impl Default for TreeConfig {
162    fn default() -> Self {
163        Self {
164            persistence_threshold: DEFAULT_PERSISTENCE_THRESHOLD,
165            memory_block_buffer_target: DEFAULT_MEMORY_BLOCK_BUFFER_TARGET,
166            block_buffer_limit: DEFAULT_BLOCK_BUFFER_LIMIT,
167            max_invalid_header_cache_length: DEFAULT_MAX_INVALID_HEADER_CACHE_LENGTH,
168            max_execute_block_batch_size: DEFAULT_MAX_EXECUTE_BLOCK_BATCH_SIZE,
169            legacy_state_root: false,
170            always_compare_trie_updates: false,
171            disable_state_cache: false,
172            disable_prewarming: false,
173            state_provider_metrics: false,
174            cross_block_cache_size: DEFAULT_CROSS_BLOCK_CACHE_SIZE,
175            has_enough_parallelism: has_enough_parallelism(),
176            multiproof_chunk_size: DEFAULT_MULTIPROOF_TASK_CHUNK_SIZE,
177            reserved_cpu_cores: DEFAULT_RESERVED_CPU_CORES,
178            precompile_cache_disabled: false,
179            state_root_fallback: false,
180            always_process_payload_attributes_on_canonical_head: false,
181            allow_unwind_canonical_header: false,
182            disable_cache_metrics: false,
183            sparse_trie_prune_depth: DEFAULT_SPARSE_TRIE_PRUNE_DEPTH,
184            sparse_trie_max_hot_slots: DEFAULT_SPARSE_TRIE_MAX_HOT_SLOTS,
185            sparse_trie_max_hot_accounts: DEFAULT_SPARSE_TRIE_MAX_HOT_ACCOUNTS,
186            slow_block_threshold: None,
187            disable_sparse_trie_cache_pruning: false,
188            state_root_task_timeout: Some(DEFAULT_STATE_ROOT_TASK_TIMEOUT),
189            #[cfg(feature = "trie-debug")]
190            proof_jitter: None,
191        }
192    }
193}
194
195impl TreeConfig {
196    /// Create engine tree configuration.
197    #[expect(clippy::too_many_arguments)]
198    pub const fn new(
199        persistence_threshold: u64,
200        memory_block_buffer_target: u64,
201        block_buffer_limit: u32,
202        max_invalid_header_cache_length: u32,
203        max_execute_block_batch_size: usize,
204        legacy_state_root: bool,
205        always_compare_trie_updates: bool,
206        disable_state_cache: bool,
207        disable_prewarming: bool,
208        state_provider_metrics: bool,
209        cross_block_cache_size: usize,
210        has_enough_parallelism: bool,
211        multiproof_chunk_size: usize,
212        reserved_cpu_cores: usize,
213        precompile_cache_disabled: bool,
214        state_root_fallback: bool,
215        always_process_payload_attributes_on_canonical_head: bool,
216        allow_unwind_canonical_header: bool,
217        disable_cache_metrics: bool,
218        sparse_trie_prune_depth: usize,
219        sparse_trie_max_hot_slots: usize,
220        sparse_trie_max_hot_accounts: usize,
221        slow_block_threshold: Option<Duration>,
222        state_root_task_timeout: Option<Duration>,
223    ) -> Self {
224        Self {
225            persistence_threshold,
226            memory_block_buffer_target,
227            block_buffer_limit,
228            max_invalid_header_cache_length,
229            max_execute_block_batch_size,
230            legacy_state_root,
231            always_compare_trie_updates,
232            disable_state_cache,
233            disable_prewarming,
234            state_provider_metrics,
235            cross_block_cache_size,
236            has_enough_parallelism,
237            multiproof_chunk_size,
238            reserved_cpu_cores,
239            precompile_cache_disabled,
240            state_root_fallback,
241            always_process_payload_attributes_on_canonical_head,
242            allow_unwind_canonical_header,
243            disable_cache_metrics,
244            sparse_trie_prune_depth,
245            sparse_trie_max_hot_slots,
246            sparse_trie_max_hot_accounts,
247            slow_block_threshold,
248            disable_sparse_trie_cache_pruning: false,
249            state_root_task_timeout,
250            #[cfg(feature = "trie-debug")]
251            proof_jitter: None,
252        }
253    }
254
255    /// Return the persistence threshold.
256    pub const fn persistence_threshold(&self) -> u64 {
257        self.persistence_threshold
258    }
259
260    /// Return the memory block buffer target.
261    pub const fn memory_block_buffer_target(&self) -> u64 {
262        self.memory_block_buffer_target
263    }
264
265    /// Return the block buffer limit.
266    pub const fn block_buffer_limit(&self) -> u32 {
267        self.block_buffer_limit
268    }
269
270    /// Return the maximum invalid cache header length.
271    pub const fn max_invalid_header_cache_length(&self) -> u32 {
272        self.max_invalid_header_cache_length
273    }
274
275    /// Return the maximum execute block batch size.
276    pub const fn max_execute_block_batch_size(&self) -> usize {
277        self.max_execute_block_batch_size
278    }
279
280    /// Return the multiproof task chunk size.
281    pub const fn multiproof_chunk_size(&self) -> usize {
282        self.multiproof_chunk_size
283    }
284
285    /// Return the effective multiproof task chunk size.
286    pub const fn effective_multiproof_chunk_size(&self) -> usize {
287        self.multiproof_chunk_size
288    }
289
290    /// Return the number of reserved CPU cores for non-reth processes
291    pub const fn reserved_cpu_cores(&self) -> usize {
292        self.reserved_cpu_cores
293    }
294
295    /// Returns whether to use the legacy state root calculation method instead
296    /// of the new state root task
297    pub const fn legacy_state_root(&self) -> bool {
298        self.legacy_state_root
299    }
300
301    /// Returns whether or not state provider metrics are enabled.
302    pub const fn state_provider_metrics(&self) -> bool {
303        self.state_provider_metrics
304    }
305
306    /// Returns whether or not state cache is disabled.
307    pub const fn disable_state_cache(&self) -> bool {
308        self.disable_state_cache
309    }
310
311    /// Returns whether or not parallel prewarming is disabled.
312    pub const fn disable_prewarming(&self) -> bool {
313        self.disable_prewarming
314    }
315
316    /// Returns whether to always compare trie updates from the state root task to the trie updates
317    /// from the regular state root calculation.
318    pub const fn always_compare_trie_updates(&self) -> bool {
319        self.always_compare_trie_updates
320    }
321
322    /// Returns the cross-block cache size.
323    pub const fn cross_block_cache_size(&self) -> usize {
324        self.cross_block_cache_size
325    }
326
327    /// Returns whether precompile cache is disabled.
328    pub const fn precompile_cache_disabled(&self) -> bool {
329        self.precompile_cache_disabled
330    }
331
332    /// Returns whether to use state root fallback.
333    pub const fn state_root_fallback(&self) -> bool {
334        self.state_root_fallback
335    }
336
337    /// Sets whether to always process payload attributes when the FCU head is already canonical.
338    pub const fn with_always_process_payload_attributes_on_canonical_head(
339        mut self,
340        always_process_payload_attributes_on_canonical_head: bool,
341    ) -> Self {
342        self.always_process_payload_attributes_on_canonical_head =
343            always_process_payload_attributes_on_canonical_head;
344        self
345    }
346
347    /// Returns true if payload attributes should always be processed even when the FCU head is
348    /// canonical.
349    pub const fn always_process_payload_attributes_on_canonical_head(&self) -> bool {
350        self.always_process_payload_attributes_on_canonical_head
351    }
352
353    /// Returns true if canonical header should be unwound to ancestor during forkchoice updates.
354    pub const fn unwind_canonical_header(&self) -> bool {
355        self.allow_unwind_canonical_header
356    }
357
358    /// Setter for persistence threshold.
359    pub const fn with_persistence_threshold(mut self, persistence_threshold: u64) -> Self {
360        self.persistence_threshold = persistence_threshold;
361        self
362    }
363
364    /// Setter for memory block buffer target.
365    pub const fn with_memory_block_buffer_target(
366        mut self,
367        memory_block_buffer_target: u64,
368    ) -> Self {
369        self.memory_block_buffer_target = memory_block_buffer_target;
370        self
371    }
372
373    /// Setter for block buffer limit.
374    pub const fn with_block_buffer_limit(mut self, block_buffer_limit: u32) -> Self {
375        self.block_buffer_limit = block_buffer_limit;
376        self
377    }
378
379    /// Setter for maximum invalid header cache length.
380    pub const fn with_max_invalid_header_cache_length(
381        mut self,
382        max_invalid_header_cache_length: u32,
383    ) -> Self {
384        self.max_invalid_header_cache_length = max_invalid_header_cache_length;
385        self
386    }
387
388    /// Setter for maximum execute block batch size.
389    pub const fn with_max_execute_block_batch_size(
390        mut self,
391        max_execute_block_batch_size: usize,
392    ) -> Self {
393        self.max_execute_block_batch_size = max_execute_block_batch_size;
394        self
395    }
396
397    /// Setter for whether to use the legacy state root calculation method.
398    pub const fn with_legacy_state_root(mut self, legacy_state_root: bool) -> Self {
399        self.legacy_state_root = legacy_state_root;
400        self
401    }
402
403    /// Setter for whether to disable state cache.
404    pub const fn without_state_cache(mut self, disable_state_cache: bool) -> Self {
405        self.disable_state_cache = disable_state_cache;
406        self
407    }
408
409    /// Setter for whether to disable parallel prewarming.
410    pub const fn without_prewarming(mut self, disable_prewarming: bool) -> Self {
411        self.disable_prewarming = disable_prewarming;
412        self
413    }
414
415    /// Setter for whether to always compare trie updates from the state root task to the trie
416    /// updates from the regular state root calculation.
417    pub const fn with_always_compare_trie_updates(
418        mut self,
419        always_compare_trie_updates: bool,
420    ) -> Self {
421        self.always_compare_trie_updates = always_compare_trie_updates;
422        self
423    }
424
425    /// Setter for cross block cache size.
426    pub const fn with_cross_block_cache_size(mut self, cross_block_cache_size: usize) -> Self {
427        self.cross_block_cache_size = cross_block_cache_size;
428        self
429    }
430
431    /// Setter for has enough parallelism.
432    pub const fn with_has_enough_parallelism(mut self, has_enough_parallelism: bool) -> Self {
433        self.has_enough_parallelism = has_enough_parallelism;
434        self
435    }
436
437    /// Setter for state provider metrics.
438    pub const fn with_state_provider_metrics(mut self, state_provider_metrics: bool) -> Self {
439        self.state_provider_metrics = state_provider_metrics;
440        self
441    }
442
443    /// Setter for multiproof task chunk size for proof targets.
444    pub const fn with_multiproof_chunk_size(mut self, multiproof_chunk_size: usize) -> Self {
445        self.multiproof_chunk_size = multiproof_chunk_size;
446        self
447    }
448
449    /// Setter for the number of reserved CPU cores for any non-reth processes
450    pub const fn with_reserved_cpu_cores(mut self, reserved_cpu_cores: usize) -> Self {
451        self.reserved_cpu_cores = reserved_cpu_cores;
452        self
453    }
454
455    /// Setter for whether to disable the precompile cache.
456    pub const fn without_precompile_cache(mut self, precompile_cache_disabled: bool) -> Self {
457        self.precompile_cache_disabled = precompile_cache_disabled;
458        self
459    }
460
461    /// Setter for whether to use state root fallback, useful for testing.
462    pub const fn with_state_root_fallback(mut self, state_root_fallback: bool) -> Self {
463        self.state_root_fallback = state_root_fallback;
464        self
465    }
466
467    /// Setter for whether to unwind canonical header to ancestor during forkchoice updates.
468    pub const fn with_unwind_canonical_header(mut self, unwind_canonical_header: bool) -> Self {
469        self.allow_unwind_canonical_header = unwind_canonical_header;
470        self
471    }
472
473    /// Whether or not to use state root task
474    pub const fn use_state_root_task(&self) -> bool {
475        self.has_enough_parallelism && !self.legacy_state_root
476    }
477
478    /// Returns whether cache metrics recording is disabled.
479    pub const fn disable_cache_metrics(&self) -> bool {
480        self.disable_cache_metrics
481    }
482
483    /// Setter for whether to disable cache metrics recording.
484    pub const fn without_cache_metrics(mut self, disable_cache_metrics: bool) -> Self {
485        self.disable_cache_metrics = disable_cache_metrics;
486        self
487    }
488
489    /// Returns the sparse trie prune depth.
490    pub const fn sparse_trie_prune_depth(&self) -> usize {
491        self.sparse_trie_prune_depth
492    }
493
494    /// Setter for sparse trie prune depth.
495    pub const fn with_sparse_trie_prune_depth(mut self, depth: usize) -> Self {
496        self.sparse_trie_prune_depth = depth;
497        self
498    }
499
500    /// Returns the LFU hot-slot capacity for sparse trie pruning.
501    pub const fn sparse_trie_max_hot_slots(&self) -> usize {
502        self.sparse_trie_max_hot_slots
503    }
504
505    /// Setter for LFU hot-slot capacity.
506    pub const fn with_sparse_trie_max_hot_slots(mut self, max_hot_slots: usize) -> Self {
507        self.sparse_trie_max_hot_slots = max_hot_slots;
508        self
509    }
510
511    /// Returns the LFU hot-account capacity for sparse trie pruning.
512    pub const fn sparse_trie_max_hot_accounts(&self) -> usize {
513        self.sparse_trie_max_hot_accounts
514    }
515
516    /// Setter for LFU hot-account capacity.
517    pub const fn with_sparse_trie_max_hot_accounts(mut self, max_hot_accounts: usize) -> Self {
518        self.sparse_trie_max_hot_accounts = max_hot_accounts;
519        self
520    }
521
522    /// Returns the slow block threshold, if configured.
523    ///
524    /// When `Some`, blocks whose total processing time exceeds this duration emit a structured
525    /// warning with timing, state-operation, and cache-hit-rate details. `Duration::ZERO` logs
526    /// every block.
527    pub const fn slow_block_threshold(&self) -> Option<Duration> {
528        self.slow_block_threshold
529    }
530
531    /// Setter for slow block threshold.
532    pub const fn with_slow_block_threshold(
533        mut self,
534        slow_block_threshold: Option<Duration>,
535    ) -> Self {
536        self.slow_block_threshold = slow_block_threshold;
537        self
538    }
539
540    /// Returns whether sparse trie cache pruning is disabled.
541    pub const fn disable_sparse_trie_cache_pruning(&self) -> bool {
542        self.disable_sparse_trie_cache_pruning
543    }
544
545    /// Setter for whether to disable sparse trie cache pruning.
546    pub const fn with_disable_sparse_trie_cache_pruning(mut self, value: bool) -> Self {
547        self.disable_sparse_trie_cache_pruning = value;
548        self
549    }
550
551    /// Returns the state root task timeout.
552    pub const fn state_root_task_timeout(&self) -> Option<Duration> {
553        self.state_root_task_timeout
554    }
555
556    /// Setter for state root task timeout.
557    pub const fn with_state_root_task_timeout(mut self, timeout: Option<Duration>) -> Self {
558        self.state_root_task_timeout = timeout;
559        self
560    }
561
562    /// Returns the proof jitter duration, if configured (trie-debug only).
563    #[cfg(feature = "trie-debug")]
564    pub const fn proof_jitter(&self) -> Option<Duration> {
565        self.proof_jitter
566    }
567
568    /// Setter for proof jitter (trie-debug only).
569    #[cfg(feature = "trie-debug")]
570    pub const fn with_proof_jitter(mut self, proof_jitter: Option<Duration>) -> Self {
571        self.proof_jitter = proof_jitter;
572        self
573    }
574}