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 = 50;
8
9/// Number of persisted blocks whose state/trie writes are masked by an in-memory suffix.
10pub const DEFAULT_NUM_STATE_MASKING_BLOCKS: u64 = 30;
11
12/// The engine stalls API processing when this many blocks are awaiting persistence beyond the
13/// in-memory buffer target.
14pub const MIN_PERSISTENCE_BACKPRESSURE_THRESHOLD: u64 = 16;
15
16/// How close to the canonical head we persist blocks.
17pub const DEFAULT_MEMORY_BLOCK_BUFFER_TARGET: u64 = 5;
18
19/// The largest gap for which the tree will be used to sync individual blocks by downloading them.
20///
21/// This is the default threshold, and represents the distance (gap) from the local head to a
22/// new (canonical) block, e.g. the forkchoice head block. If the block distance from the local head
23/// exceeds this threshold, the pipeline will be used to backfill the gap more efficiently.
24///
25/// E.g.: Local head `block.number` is 100 and the forkchoice head `block.number` is 133 (more than
26/// an epoch has slots), then this exceeds the threshold at which the pipeline should be used to
27/// backfill this gap.
28pub const DEFAULT_BACKFILL_RUN_THRESHOLD: u64 = EPOCH_SLOTS;
29
30/// Largest supported live-sync gap, matching the peer header response limit of 1024.
31///
32/// The range downloader requires a complete response and cannot yet split larger requests.
33pub const MAX_BACKFILL_RUN_THRESHOLD: u64 = 1024;
34
35/// The size of proof targets chunk to spawn in one multiproof calculation.
36pub const DEFAULT_MULTIPROOF_TASK_CHUNK_SIZE: usize = 5;
37
38/// Default number of cache hits before an invalid header entry is evicted and reprocessed.
39pub const DEFAULT_INVALID_HEADER_HIT_EVICTION_THRESHOLD: u8 = 128;
40
41/// Gas threshold below which the small block chunk size is used.
42pub const SMALL_BLOCK_GAS_THRESHOLD: u64 = 20_000_000;
43
44/// Default number of reserved CPU cores for non-reth processes.
45///
46/// This will be deducted from the thread count of main reth global threadpool.
47pub const DEFAULT_RESERVED_CPU_CORES: usize = 1;
48
49/// Default depth for sparse trie pruning.
50///
51/// Nodes at this depth and below are converted to hash stubs to reduce memory.
52/// Depth 4 means we keep roughly 16^4 = 65536 potential branch paths at most.
53pub const DEFAULT_SPARSE_TRIE_PRUNE_DEPTH: usize = 4;
54
55/// Default timeout for the state root task before spawning a sequential fallback.
56pub const DEFAULT_STATE_ROOT_TASK_TIMEOUT: Duration = Duration::from_secs(1);
57
58const DEFAULT_BLOCK_BUFFER_LIMIT: u32 = EPOCH_SLOTS as u32 * 2;
59const DEFAULT_MAX_INVALID_HEADER_CACHE_LENGTH: u32 = 256;
60const DEFAULT_MAX_EXECUTE_BLOCK_BATCH_SIZE: usize = 4;
61const DEFAULT_CROSS_BLOCK_CACHE_SIZE: usize = default_cross_block_cache_size();
62
63const fn assert_backpressure_threshold_invariant(
64    persistence_threshold: u64,
65    persistence_backpressure_threshold: u64,
66) {
67    debug_assert!(
68        persistence_backpressure_threshold > persistence_threshold,
69        "persistence_backpressure_threshold must be greater than persistence_threshold",
70    );
71}
72
73const fn assert_state_masking_invariant(
74    persistence_threshold: u64,
75    num_state_masking_blocks: u64,
76    memory_block_buffer_target: u64,
77) {
78    let valid_window = match num_state_masking_blocks.checked_add(memory_block_buffer_target) {
79        Some(window) => window < persistence_threshold,
80        None => false,
81    };
82    debug_assert!(
83        num_state_masking_blocks == 0 || valid_window,
84        "num_state_masking_blocks + memory_block_buffer_target must be less than persistence_threshold",
85    );
86}
87
88const fn default_cross_block_cache_size() -> usize {
89    if cfg!(test) {
90        1024 * 1024 // 1 MB in tests
91    } else if cfg!(target_pointer_width = "32") {
92        usize::MAX // max possible on wasm32 / 32-bit
93    } else {
94        4 * 1024 * 1024 * 1024 // 4 GB on 64-bit
95    }
96}
97
98/// Determines if the host has enough parallelism to run the payload processor.
99///
100/// It requires at least 5 parallel threads:
101/// - Engine in main thread that spawns the state root task.
102/// - Multiproof task in payload processor
103/// - Sparse Trie task in payload processor
104/// - Multiproof computation spawned in payload processor
105/// - Storage root computation spawned in trie parallel proof
106pub fn has_enough_parallelism() -> bool {
107    #[cfg(feature = "std")]
108    {
109        std::thread::available_parallelism().is_ok_and(|num| num.get() >= 5)
110    }
111    #[cfg(not(feature = "std"))]
112    false
113}
114
115/// The configuration of the engine tree.
116#[derive(Debug, Clone)]
117pub struct TreeConfig {
118    /// Maximum number of blocks to be kept only in memory without triggering
119    /// persistence.
120    persistence_threshold: u64,
121    /// Number of persisted blocks whose state/trie writes are masked instead of being durably
122    /// written in the current cycle.
123    num_state_masking_blocks: u64,
124    /// How close to the canonical head we persist blocks. Represents the ideal
125    /// number of most recent blocks to keep in memory for quick access and reorgs.
126    ///
127    /// Note: this should be less than or equal to `persistence_threshold`.
128    memory_block_buffer_target: u64,
129    /// Maximum number of blocks beyond the in-memory buffer target awaiting persistence before
130    /// engine API processing is stalled.
131    persistence_backpressure_threshold: u64,
132    /// Number of pending blocks that cannot be executed due to missing parent and
133    /// are kept in cache.
134    block_buffer_limit: u32,
135    /// Largest gap between the local head and a new canonical block that is closed by downloading
136    /// the missing blocks. Larger gaps trigger a pipeline (backfill) run instead.
137    backfill_run_threshold: u64,
138    /// Number of invalid headers to keep in cache.
139    max_invalid_header_cache_length: u32,
140    /// Number of cache hits before an invalid header entry is evicted and reprocessed.
141    ///
142    /// Setting this to `0` effectively disables the cache because entries are evicted on the
143    /// first lookup.
144    invalid_header_hit_eviction_threshold: u8,
145    /// Maximum number of blocks to execute sequentially in a batch.
146    ///
147    /// This is used as a cutoff to prevent long-running sequential block execution when we receive
148    /// a batch of downloaded blocks.
149    max_execute_block_batch_size: usize,
150    /// Whether to always compare trie updates from the state root task to the trie updates from
151    /// the regular state root calculation.
152    always_compare_trie_updates: bool,
153    /// Whether to disable state cache.
154    disable_state_cache: bool,
155    /// Whether to disable parallel prewarming.
156    disable_prewarming: bool,
157    /// Whether txpool-driven prewarming between payloads is enabled.
158    txpool_prewarming: bool,
159    /// Whether to enable state provider metrics.
160    state_provider_metrics: bool,
161    /// Cross-block cache size in bytes.
162    cross_block_cache_size: usize,
163    /// Whether the host has enough parallelism to run the state root task, see
164    /// [`has_enough_parallelism`].
165    ///
166    /// The state root task pipeline occupies at least 5 threads that block on each other (engine
167    /// main thread, multiproof task, sparse trie task, multiproof computation, storage root
168    /// computation). On hosts with fewer parallel threads these components can starve each other
169    /// and stall payload validation entirely, so state-root strategy selection
170    /// ([`Self::use_state_root_task`]) must keep falling back to synchronous state root
171    /// computation when this is `false`.
172    has_enough_parallelism: bool,
173    /// Multiproof task chunk size for proof targets.
174    multiproof_chunk_size: usize,
175    /// Number of reserved CPU cores for non-reth processes
176    reserved_cpu_cores: usize,
177    /// Whether to disable the precompile cache
178    precompile_cache_disabled: bool,
179    /// Whether to use state root fallback for testing
180    state_root_fallback: bool,
181    /// Whether to always process payload attributes and begin a payload build process
182    /// even if `forkchoiceState.headBlockHash` is already the canonical head or an ancestor.
183    ///
184    /// The Engine API specification generally states that client software "MUST NOT begin a
185    /// payload build process if `forkchoiceState.headBlockHash` references a `VALID`
186    /// ancestor of the head of canonical chain".
187    /// See: <https://github.com/ethereum/execution-apis/blob/main/src/engine/paris.md#engine_forkchoiceupdatedv1> (Rule 2)
188    ///
189    /// This flag allows overriding that behavior.
190    /// This is useful for specific chain configurations (e.g., OP Stack where proposers
191    /// can reorg their own chain), various custom chains, or for development/testing purposes
192    /// where immediate payload regeneration is desired despite the head not changing or moving to
193    /// an ancestor.
194    always_process_payload_attributes_on_canonical_head: bool,
195    /// Whether to unwind canonical header to ancestor during forkchoice updates.
196    allow_unwind_canonical_header: bool,
197    /// Whether to disable cache metrics recording (can be expensive with large cached state).
198    disable_cache_metrics: bool,
199    /// Depth for sparse trie pruning after state root computation.
200    sparse_trie_prune_depth: usize,
201    /// When set, blocks whose total processing time (execution + state reads + state root +
202    /// DB commit) exceeds this duration trigger a structured `warn!` log with detailed timing,
203    /// state-operation counts, and cache hit-rate metrics. `Duration::ZERO` logs every block.
204    slow_block_threshold: Option<Duration>,
205    /// Whether to fully disable sparse trie cache pruning between blocks.
206    disable_sparse_trie_cache_pruning: bool,
207    /// Timeout for the state root task before spawning a sequential fallback computation.
208    /// If `Some`, after waiting this duration for the state root task, a sequential state root
209    /// computation is spawned in parallel and whichever finishes first is used.
210    /// If `None`, the timeout fallback is disabled.
211    state_root_task_timeout: Option<Duration>,
212    /// Whether to share execution cache with the payload builder.
213    share_execution_cache_with_payload_builder: bool,
214    /// Whether to share sparse trie with the payload builder.
215    share_sparse_trie_with_payload_builder: bool,
216    /// Whether to suppress persistence cycles while building a payload.
217    ///
218    /// When enabled, persistence is deferred while a payload job is active. This avoids
219    /// persistence I/O competing with block building on latency-sensitive chains.
220    suppress_persistence_during_build: bool,
221    /// Whether to disable BAL (Block Access List, EIP-7928) based parallel execution.
222    /// When disabled, uses the sequential execution path even when a BAL is available.
223    disable_bal_parallel_execution: bool,
224    /// Whether to disable BAL-driven parallel state root computation.
225    /// Only valid when BAL parallel execution is also disabled.
226    disable_bal_parallel_state_root: bool,
227    /// Whether to disable BAL (Block Access List) storage prefetch IO during prewarming.
228    /// When set, BAL storage slots are not read into the execution cache. BAL hashed-state
229    /// streaming for parallel state-root computation is controlled separately.
230    disable_bal_batch_io: bool,
231    /// Whether to skip trie state-root computation during engine validation.
232    ///
233    /// This trusts the block header's state root. It is intended for experiments that measure
234    /// execution without trie state-root work.
235    skip_state_root: bool,
236    /// Maximum random jitter applied before each proof computation (trie-debug only).
237    /// When set, each proof worker sleeps for a random duration up to this value
238    /// before starting a proof calculation.
239    #[cfg(feature = "trie-debug")]
240    proof_jitter: Option<Duration>,
241}
242
243impl Default for TreeConfig {
244    fn default() -> Self {
245        assert_backpressure_threshold_invariant(
246            DEFAULT_PERSISTENCE_THRESHOLD,
247            DEFAULT_PERSISTENCE_THRESHOLD * 2,
248        );
249        assert_state_masking_invariant(
250            DEFAULT_PERSISTENCE_THRESHOLD,
251            DEFAULT_NUM_STATE_MASKING_BLOCKS,
252            DEFAULT_MEMORY_BLOCK_BUFFER_TARGET,
253        );
254        Self {
255            persistence_threshold: DEFAULT_PERSISTENCE_THRESHOLD,
256            num_state_masking_blocks: DEFAULT_NUM_STATE_MASKING_BLOCKS,
257            memory_block_buffer_target: DEFAULT_MEMORY_BLOCK_BUFFER_TARGET,
258            persistence_backpressure_threshold: DEFAULT_PERSISTENCE_THRESHOLD * 2,
259            block_buffer_limit: DEFAULT_BLOCK_BUFFER_LIMIT,
260            backfill_run_threshold: DEFAULT_BACKFILL_RUN_THRESHOLD,
261            max_invalid_header_cache_length: DEFAULT_MAX_INVALID_HEADER_CACHE_LENGTH,
262            invalid_header_hit_eviction_threshold: DEFAULT_INVALID_HEADER_HIT_EVICTION_THRESHOLD,
263            max_execute_block_batch_size: DEFAULT_MAX_EXECUTE_BLOCK_BATCH_SIZE,
264            always_compare_trie_updates: false,
265            disable_state_cache: false,
266            disable_prewarming: false,
267            txpool_prewarming: false,
268            state_provider_metrics: false,
269            cross_block_cache_size: DEFAULT_CROSS_BLOCK_CACHE_SIZE,
270            has_enough_parallelism: has_enough_parallelism(),
271            multiproof_chunk_size: DEFAULT_MULTIPROOF_TASK_CHUNK_SIZE,
272            reserved_cpu_cores: DEFAULT_RESERVED_CPU_CORES,
273            precompile_cache_disabled: false,
274            state_root_fallback: false,
275            always_process_payload_attributes_on_canonical_head: false,
276            allow_unwind_canonical_header: false,
277            disable_cache_metrics: false,
278            sparse_trie_prune_depth: DEFAULT_SPARSE_TRIE_PRUNE_DEPTH,
279            slow_block_threshold: None,
280            disable_sparse_trie_cache_pruning: false,
281            state_root_task_timeout: Some(DEFAULT_STATE_ROOT_TASK_TIMEOUT),
282            share_execution_cache_with_payload_builder: false,
283            share_sparse_trie_with_payload_builder: false,
284            suppress_persistence_during_build: false,
285            disable_bal_parallel_execution: false,
286            disable_bal_parallel_state_root: false,
287            disable_bal_batch_io: false,
288            skip_state_root: false,
289            #[cfg(feature = "trie-debug")]
290            proof_jitter: None,
291        }
292    }
293}
294
295impl TreeConfig {
296    /// Create engine tree configuration.
297    ///
298    /// The backfill run threshold is clamped to [`MAX_BACKFILL_RUN_THRESHOLD`].
299    #[expect(clippy::too_many_arguments)]
300    pub const fn new(
301        persistence_threshold: u64,
302        num_state_masking_blocks: u64,
303        memory_block_buffer_target: u64,
304        persistence_backpressure_threshold: u64,
305        block_buffer_limit: u32,
306        backfill_run_threshold: u64,
307        max_invalid_header_cache_length: u32,
308        invalid_header_hit_eviction_threshold: u8,
309        max_execute_block_batch_size: usize,
310        always_compare_trie_updates: bool,
311        disable_state_cache: bool,
312        disable_prewarming: bool,
313        state_provider_metrics: bool,
314        cross_block_cache_size: usize,
315        has_enough_parallelism: bool,
316        multiproof_chunk_size: usize,
317        reserved_cpu_cores: usize,
318        precompile_cache_disabled: bool,
319        state_root_fallback: bool,
320        always_process_payload_attributes_on_canonical_head: bool,
321        allow_unwind_canonical_header: bool,
322        disable_cache_metrics: bool,
323        sparse_trie_prune_depth: usize,
324        slow_block_threshold: Option<Duration>,
325        state_root_task_timeout: Option<Duration>,
326        share_execution_cache_with_payload_builder: bool,
327        share_sparse_trie_with_payload_builder: bool,
328    ) -> Self {
329        let num_state_masking_blocks =
330            if persistence_threshold == 0 { 0 } else { num_state_masking_blocks };
331        assert_backpressure_threshold_invariant(
332            persistence_threshold,
333            persistence_backpressure_threshold,
334        );
335        assert_state_masking_invariant(
336            persistence_threshold,
337            num_state_masking_blocks,
338            memory_block_buffer_target,
339        );
340        Self {
341            persistence_threshold,
342            num_state_masking_blocks,
343            memory_block_buffer_target,
344            persistence_backpressure_threshold,
345            block_buffer_limit,
346            backfill_run_threshold: clamp_backfill_run_threshold(backfill_run_threshold),
347            max_invalid_header_cache_length,
348            invalid_header_hit_eviction_threshold,
349            max_execute_block_batch_size,
350            always_compare_trie_updates,
351            disable_state_cache,
352            disable_prewarming,
353            txpool_prewarming: false,
354            state_provider_metrics,
355            cross_block_cache_size,
356            has_enough_parallelism,
357            multiproof_chunk_size,
358            reserved_cpu_cores,
359            precompile_cache_disabled,
360            state_root_fallback,
361            always_process_payload_attributes_on_canonical_head,
362            allow_unwind_canonical_header,
363            disable_cache_metrics,
364            sparse_trie_prune_depth,
365            slow_block_threshold,
366            disable_sparse_trie_cache_pruning: false,
367            state_root_task_timeout,
368            share_execution_cache_with_payload_builder,
369            share_sparse_trie_with_payload_builder,
370            suppress_persistence_during_build: false,
371            disable_bal_parallel_execution: false,
372            disable_bal_parallel_state_root: false,
373            disable_bal_batch_io: false,
374            skip_state_root: false,
375            #[cfg(feature = "trie-debug")]
376            proof_jitter: None,
377        }
378    }
379
380    /// Return the persistence threshold.
381    pub const fn persistence_threshold(&self) -> u64 {
382        self.persistence_threshold
383    }
384
385    /// Return the number of persisted blocks whose state/trie writes are masked.
386    pub const fn num_state_masking_blocks(&self) -> u64 {
387        self.num_state_masking_blocks
388    }
389
390    /// Return the memory block buffer target.
391    pub const fn memory_block_buffer_target(&self) -> u64 {
392        self.memory_block_buffer_target
393    }
394
395    /// Return the persistence backpressure threshold.
396    pub const fn persistence_backpressure_threshold(&self) -> u64 {
397        self.persistence_backpressure_threshold
398    }
399
400    /// Return the block buffer limit.
401    pub const fn block_buffer_limit(&self) -> u32 {
402        self.block_buffer_limit
403    }
404
405    /// Return the backfill run threshold.
406    pub const fn backfill_run_threshold(&self) -> u64 {
407        self.backfill_run_threshold
408    }
409
410    /// Return the maximum invalid cache header length.
411    pub const fn max_invalid_header_cache_length(&self) -> u32 {
412        self.max_invalid_header_cache_length
413    }
414
415    /// Return the invalid header cache hit eviction threshold.
416    ///
417    /// Setting this to `0` effectively disables the cache because entries are evicted on the
418    /// first lookup.
419    pub const fn invalid_header_hit_eviction_threshold(&self) -> u8 {
420        self.invalid_header_hit_eviction_threshold
421    }
422
423    /// Return the maximum execute block batch size.
424    pub const fn max_execute_block_batch_size(&self) -> usize {
425        self.max_execute_block_batch_size
426    }
427
428    /// Return the multiproof task chunk size.
429    pub const fn multiproof_chunk_size(&self) -> usize {
430        self.multiproof_chunk_size
431    }
432
433    /// Return the effective multiproof task chunk size.
434    pub const fn effective_multiproof_chunk_size(&self) -> usize {
435        self.multiproof_chunk_size
436    }
437
438    /// Return the number of reserved CPU cores for non-reth processes
439    pub const fn reserved_cpu_cores(&self) -> usize {
440        self.reserved_cpu_cores
441    }
442
443    /// Returns whether or not state provider metrics are enabled.
444    pub const fn state_provider_metrics(&self) -> bool {
445        self.state_provider_metrics
446    }
447
448    /// Returns whether or not state cache is disabled.
449    pub const fn disable_state_cache(&self) -> bool {
450        self.disable_state_cache
451    }
452
453    /// Returns whether or not parallel prewarming is disabled.
454    pub const fn disable_prewarming(&self) -> bool {
455        self.disable_prewarming
456    }
457
458    /// Returns whether txpool prewarming is enabled.
459    pub const fn txpool_prewarming(&self) -> bool {
460        self.txpool_prewarming
461    }
462
463    /// Returns whether to always compare trie updates from the state root task to the trie updates
464    /// from the regular state root calculation.
465    pub const fn always_compare_trie_updates(&self) -> bool {
466        self.always_compare_trie_updates
467    }
468
469    /// Returns the cross-block cache size.
470    pub const fn cross_block_cache_size(&self) -> usize {
471        self.cross_block_cache_size
472    }
473
474    /// Returns whether precompile cache is disabled.
475    pub const fn precompile_cache_disabled(&self) -> bool {
476        self.precompile_cache_disabled
477    }
478
479    /// Returns whether to use state root fallback.
480    pub const fn state_root_fallback(&self) -> bool {
481        self.state_root_fallback
482    }
483
484    /// Sets whether to always process payload attributes when the FCU head is already canonical.
485    pub const fn with_always_process_payload_attributes_on_canonical_head(
486        mut self,
487        always_process_payload_attributes_on_canonical_head: bool,
488    ) -> Self {
489        self.always_process_payload_attributes_on_canonical_head =
490            always_process_payload_attributes_on_canonical_head;
491        self
492    }
493
494    /// Returns true if payload attributes should always be processed even when the FCU head is
495    /// canonical.
496    pub const fn always_process_payload_attributes_on_canonical_head(&self) -> bool {
497        self.always_process_payload_attributes_on_canonical_head
498    }
499
500    /// Returns true if canonical header should be unwound to ancestor during forkchoice updates.
501    pub const fn unwind_canonical_header(&self) -> bool {
502        self.allow_unwind_canonical_header
503    }
504
505    /// Setter for persistence threshold. Setting this to zero disables state masking.
506    pub const fn with_persistence_threshold(mut self, persistence_threshold: u64) -> Self {
507        self.persistence_threshold = persistence_threshold;
508        if persistence_threshold == 0 {
509            self.num_state_masking_blocks = 0;
510        }
511        assert_backpressure_threshold_invariant(
512            self.persistence_threshold,
513            self.persistence_backpressure_threshold,
514        );
515        assert_state_masking_invariant(
516            self.persistence_threshold,
517            self.num_state_masking_blocks,
518            self.memory_block_buffer_target,
519        );
520        self
521    }
522
523    /// Setter for the number of persisted blocks whose state/trie writes are masked.
524    /// State masking is disabled when the persistence threshold is zero.
525    pub const fn with_num_state_masking_blocks(mut self, num_state_masking_blocks: u64) -> Self {
526        self.num_state_masking_blocks =
527            if self.persistence_threshold == 0 { 0 } else { num_state_masking_blocks };
528        assert_state_masking_invariant(
529            self.persistence_threshold,
530            self.num_state_masking_blocks,
531            self.memory_block_buffer_target,
532        );
533        self
534    }
535
536    /// Setter for memory block buffer target.
537    pub const fn with_memory_block_buffer_target(
538        mut self,
539        memory_block_buffer_target: u64,
540    ) -> Self {
541        self.memory_block_buffer_target = memory_block_buffer_target;
542        assert_state_masking_invariant(
543            self.persistence_threshold,
544            self.num_state_masking_blocks,
545            self.memory_block_buffer_target,
546        );
547        self
548    }
549
550    /// Setter for persistence backpressure threshold.
551    pub const fn with_persistence_backpressure_threshold(
552        mut self,
553        persistence_backpressure_threshold: u64,
554    ) -> Self {
555        self.persistence_backpressure_threshold = persistence_backpressure_threshold;
556        assert_backpressure_threshold_invariant(
557            self.persistence_threshold,
558            self.persistence_backpressure_threshold,
559        );
560        self
561    }
562
563    /// Setter for block buffer limit.
564    pub const fn with_block_buffer_limit(mut self, block_buffer_limit: u32) -> Self {
565        self.block_buffer_limit = block_buffer_limit;
566        self
567    }
568
569    /// Setter for backfill run threshold.
570    ///
571    /// Clamps the threshold to [`MAX_BACKFILL_RUN_THRESHOLD`] and grows the block buffer to twice
572    /// the clamped threshold so it can retain disconnected blocks while downloading the gap,
573    /// with room for incoming payloads.
574    pub const fn with_backfill_run_threshold(mut self, backfill_run_threshold: u64) -> Self {
575        self.backfill_run_threshold = clamp_backfill_run_threshold(backfill_run_threshold);
576        let buffer_limit = self.backfill_run_threshold as u32 * 2;
577        if buffer_limit > self.block_buffer_limit {
578            self.block_buffer_limit = buffer_limit;
579        }
580        self
581    }
582
583    /// Setter for maximum invalid header cache length.
584    pub const fn with_max_invalid_header_cache_length(
585        mut self,
586        max_invalid_header_cache_length: u32,
587    ) -> Self {
588        self.max_invalid_header_cache_length = max_invalid_header_cache_length;
589        self
590    }
591
592    /// Setter for the invalid header cache hit eviction threshold.
593    pub const fn with_invalid_header_hit_eviction_threshold(
594        mut self,
595        invalid_header_hit_eviction_threshold: u8,
596    ) -> Self {
597        self.invalid_header_hit_eviction_threshold = invalid_header_hit_eviction_threshold;
598        self
599    }
600
601    /// Setter for maximum execute block batch size.
602    pub const fn with_max_execute_block_batch_size(
603        mut self,
604        max_execute_block_batch_size: usize,
605    ) -> Self {
606        self.max_execute_block_batch_size = max_execute_block_batch_size;
607        self
608    }
609
610    /// Setter for whether to disable state cache.
611    pub const fn without_state_cache(mut self, disable_state_cache: bool) -> Self {
612        self.disable_state_cache = disable_state_cache;
613        self
614    }
615
616    /// Setter for whether to disable parallel prewarming.
617    pub const fn without_prewarming(mut self, disable_prewarming: bool) -> Self {
618        self.disable_prewarming = disable_prewarming;
619        self
620    }
621
622    /// Enables or disables txpool transaction prewarming.
623    pub const fn with_txpool_prewarming(mut self, enabled: bool) -> Self {
624        self.txpool_prewarming = enabled;
625        self
626    }
627
628    /// Setter for whether to always compare trie updates from the state root task to the trie
629    /// updates from the regular state root calculation.
630    pub const fn with_always_compare_trie_updates(
631        mut self,
632        always_compare_trie_updates: bool,
633    ) -> Self {
634        self.always_compare_trie_updates = always_compare_trie_updates;
635        self
636    }
637
638    /// Setter for cross block cache size.
639    pub const fn with_cross_block_cache_size(mut self, cross_block_cache_size: usize) -> Self {
640        self.cross_block_cache_size = cross_block_cache_size;
641        self
642    }
643
644    /// Setter for has enough parallelism.
645    pub const fn with_has_enough_parallelism(mut self, has_enough_parallelism: bool) -> Self {
646        self.has_enough_parallelism = has_enough_parallelism;
647        self
648    }
649
650    /// Returns whether the host has enough parallelism to run the state root task.
651    pub const fn has_enough_parallelism(&self) -> bool {
652        self.has_enough_parallelism
653    }
654
655    /// Returns whether engine validation should use the state root task.
656    ///
657    /// The state root task requires at least 5 parallel threads, see
658    /// [`has_enough_parallelism`].
659    pub const fn use_state_root_task(&self) -> bool {
660        !self.skip_state_root && !self.state_root_fallback && self.has_enough_parallelism
661    }
662
663    /// Setter for state provider metrics.
664    pub const fn with_state_provider_metrics(mut self, state_provider_metrics: bool) -> Self {
665        self.state_provider_metrics = state_provider_metrics;
666        self
667    }
668
669    /// Setter for multiproof task chunk size for proof targets.
670    pub const fn with_multiproof_chunk_size(mut self, multiproof_chunk_size: usize) -> Self {
671        self.multiproof_chunk_size = multiproof_chunk_size;
672        self
673    }
674
675    /// Setter for the number of reserved CPU cores for any non-reth processes
676    pub const fn with_reserved_cpu_cores(mut self, reserved_cpu_cores: usize) -> Self {
677        self.reserved_cpu_cores = reserved_cpu_cores;
678        self
679    }
680
681    /// Setter for whether to disable the precompile cache.
682    pub const fn without_precompile_cache(mut self, precompile_cache_disabled: bool) -> Self {
683        self.precompile_cache_disabled = precompile_cache_disabled;
684        self
685    }
686
687    /// Setter for whether to use state root fallback, useful for testing.
688    pub const fn with_state_root_fallback(mut self, state_root_fallback: bool) -> Self {
689        self.state_root_fallback = state_root_fallback;
690        self
691    }
692
693    /// Setter for whether to unwind canonical header to ancestor during forkchoice updates.
694    pub const fn with_unwind_canonical_header(mut self, unwind_canonical_header: bool) -> Self {
695        self.allow_unwind_canonical_header = unwind_canonical_header;
696        self
697    }
698
699    /// Returns whether cache metrics recording is disabled.
700    pub const fn disable_cache_metrics(&self) -> bool {
701        self.disable_cache_metrics
702    }
703
704    /// Setter for whether to disable cache metrics recording.
705    pub const fn without_cache_metrics(mut self, disable_cache_metrics: bool) -> Self {
706        self.disable_cache_metrics = disable_cache_metrics;
707        self
708    }
709
710    /// Returns the sparse trie prune depth.
711    pub const fn sparse_trie_prune_depth(&self) -> usize {
712        self.sparse_trie_prune_depth
713    }
714
715    /// Setter for sparse trie prune depth.
716    pub const fn with_sparse_trie_prune_depth(mut self, depth: usize) -> Self {
717        self.sparse_trie_prune_depth = depth;
718        self
719    }
720
721    /// Returns the slow block threshold, if configured.
722    ///
723    /// When `Some`, blocks whose total processing time exceeds this duration emit a structured
724    /// warning with timing, state-operation, and cache-hit-rate details. `Duration::ZERO` logs
725    /// every block.
726    pub const fn slow_block_threshold(&self) -> Option<Duration> {
727        self.slow_block_threshold
728    }
729
730    /// Setter for slow block threshold.
731    pub const fn with_slow_block_threshold(
732        mut self,
733        slow_block_threshold: Option<Duration>,
734    ) -> Self {
735        self.slow_block_threshold = slow_block_threshold;
736        self
737    }
738
739    /// Returns whether sparse trie cache pruning is disabled.
740    pub const fn disable_sparse_trie_cache_pruning(&self) -> bool {
741        self.disable_sparse_trie_cache_pruning
742    }
743
744    /// Setter for whether to disable sparse trie cache pruning.
745    pub const fn with_disable_sparse_trie_cache_pruning(mut self, value: bool) -> Self {
746        self.disable_sparse_trie_cache_pruning = value;
747        self
748    }
749
750    /// Returns the state root task timeout.
751    pub const fn state_root_task_timeout(&self) -> Option<Duration> {
752        self.state_root_task_timeout
753    }
754
755    /// Setter for state root task timeout.
756    pub const fn with_state_root_task_timeout(mut self, timeout: Option<Duration>) -> Self {
757        self.state_root_task_timeout = timeout;
758        self
759    }
760
761    /// Returns whether to share execution cache with the payload builder.
762    pub const fn share_execution_cache_with_payload_builder(&self) -> bool {
763        self.share_execution_cache_with_payload_builder
764    }
765
766    /// Returns whether to share sparse trie with the payload builder.
767    pub const fn share_sparse_trie_with_payload_builder(&self) -> bool {
768        self.share_sparse_trie_with_payload_builder
769    }
770
771    /// Setter for whether to share execution cache with the payload builder.
772    pub const fn with_share_execution_cache_with_payload_builder(
773        mut self,
774        share_execution_cache_with_payload_builder: bool,
775    ) -> Self {
776        self.share_execution_cache_with_payload_builder =
777            share_execution_cache_with_payload_builder;
778        self
779    }
780
781    /// Setter for whether to share sparse trie with the payload builder.
782    pub const fn with_share_sparse_trie_with_payload_builder(
783        mut self,
784        share_sparse_trie_with_payload_builder: bool,
785    ) -> Self {
786        self.share_sparse_trie_with_payload_builder = share_sparse_trie_with_payload_builder;
787        self
788    }
789
790    /// Returns whether persistence is suppressed during payload building.
791    pub const fn suppress_persistence_during_build(&self) -> bool {
792        self.suppress_persistence_during_build
793    }
794
795    /// Setter for whether to suppress persistence during payload building.
796    pub const fn with_suppress_persistence_during_build(mut self, value: bool) -> Self {
797        self.suppress_persistence_during_build = value;
798        self
799    }
800
801    /// Returns whether BAL-based parallel execution is disabled.
802    pub const fn disable_bal_parallel_execution(&self) -> bool {
803        self.disable_bal_parallel_execution
804    }
805
806    /// Setter for whether to disable BAL-based parallel execution.
807    pub const fn without_bal_parallel_execution(
808        mut self,
809        disable_bal_parallel_execution: bool,
810    ) -> Self {
811        self.disable_bal_parallel_execution = disable_bal_parallel_execution;
812        self
813    }
814
815    /// Returns whether BAL-driven parallel state root computation is disabled.
816    pub const fn disable_bal_parallel_state_root(&self) -> bool {
817        self.disable_bal_parallel_state_root
818    }
819
820    /// Setter for whether to disable BAL-driven parallel state root computation.
821    pub const fn without_bal_parallel_state_root(
822        mut self,
823        disable_bal_parallel_state_root: bool,
824    ) -> Self {
825        self.disable_bal_parallel_state_root = disable_bal_parallel_state_root;
826        self
827    }
828
829    /// Returns whether BAL state prefetching during prewarm is disabled.
830    pub const fn disable_bal_batch_io(&self) -> bool {
831        self.disable_bal_batch_io
832    }
833
834    /// Setter for whether to disable BAL state prefetching during prewarm.
835    pub const fn without_bal_batch_io(mut self, disable_bal_batch_io: bool) -> Self {
836        self.disable_bal_batch_io = disable_bal_batch_io;
837        self
838    }
839
840    /// Returns whether trie state-root computation is skipped during engine validation.
841    pub const fn skip_state_root(&self) -> bool {
842        self.skip_state_root
843    }
844
845    /// Setter for whether to skip trie state-root computation during engine validation.
846    pub const fn with_skip_state_root(mut self, skip_state_root: bool) -> Self {
847        self.skip_state_root = skip_state_root;
848        self
849    }
850
851    /// Returns the proof jitter duration, if configured (trie-debug only).
852    #[cfg(feature = "trie-debug")]
853    pub const fn proof_jitter(&self) -> Option<Duration> {
854        self.proof_jitter
855    }
856
857    /// Setter for proof jitter (trie-debug only).
858    #[cfg(feature = "trie-debug")]
859    pub const fn with_proof_jitter(mut self, proof_jitter: Option<Duration>) -> Self {
860        self.proof_jitter = proof_jitter;
861        self
862    }
863}
864
865/// Keep live-sync requests within a single peer header response until chunking is supported.
866const fn clamp_backfill_run_threshold(threshold: u64) -> u64 {
867    if threshold > MAX_BACKFILL_RUN_THRESHOLD {
868        MAX_BACKFILL_RUN_THRESHOLD
869    } else {
870        threshold
871    }
872}
873
874#[cfg(test)]
875mod tests {
876    use super::*;
877
878    #[test]
879    fn backfill_threshold_grows_block_buffer() {
880        let default = TreeConfig::default();
881        for (threshold, expected, buffer_limit) in
882            [(0, 0, 64), (500, 500, 1000), (1024, 1024, 2048), (u64::MAX, 1024, 2048)]
883        {
884            let config = default.clone().with_backfill_run_threshold(threshold);
885            assert_eq!(config.backfill_run_threshold(), expected);
886            assert_eq!(config.block_buffer_limit(), buffer_limit);
887        }
888        let config = default.with_block_buffer_limit(2000).with_backfill_run_threshold(500);
889        assert_eq!(config.block_buffer_limit(), 2000);
890    }
891
892    #[test]
893    fn txpool_prewarming_is_disabled_by_default_and_can_be_enabled() {
894        assert!(!TreeConfig::default().txpool_prewarming());
895        assert!(TreeConfig::default().with_txpool_prewarming(true).txpool_prewarming());
896    }
897
898    #[test]
899    fn state_root_task_requires_parallelism_without_overrides() {
900        assert!(TreeConfig::default().with_has_enough_parallelism(true).use_state_root_task());
901        assert!(!TreeConfig::default().with_has_enough_parallelism(false).use_state_root_task());
902        assert!(!TreeConfig::default()
903            .with_has_enough_parallelism(true)
904            .with_state_root_fallback(true)
905            .use_state_root_task());
906        assert!(!TreeConfig::default()
907            .with_has_enough_parallelism(true)
908            .with_skip_state_root(true)
909            .use_state_root_task());
910    }
911
912    #[test]
913    #[should_panic(
914        expected = "persistence_backpressure_threshold must be greater than persistence_threshold"
915    )]
916    fn rejects_backpressure_threshold_at_or_below_persistence_threshold() {
917        let _ = TreeConfig::default()
918            .with_num_state_masking_blocks(0)
919            .with_persistence_threshold(4)
920            .with_persistence_backpressure_threshold(4);
921    }
922
923    #[test]
924    fn default_persistence_settings() {
925        let config = TreeConfig::default();
926        assert_eq!(config.persistence_threshold(), 50);
927        assert_eq!(config.num_state_masking_blocks(), 30);
928        assert_eq!(config.persistence_backpressure_threshold(), 100);
929    }
930
931    #[test]
932    fn zero_persistence_threshold_disables_state_masking() {
933        let config = TreeConfig::default().with_persistence_threshold(0);
934        assert_eq!(config.num_state_masking_blocks(), 0);
935        let config = config.with_num_state_masking_blocks(u64::MAX);
936        assert_eq!(config.num_state_masking_blocks(), 0);
937        let config = config.with_memory_block_buffer_target(0);
938        assert_eq!(config.num_state_masking_blocks(), 0);
939    }
940
941    #[test]
942    #[should_panic(
943        expected = "num_state_masking_blocks + memory_block_buffer_target must be less than persistence_threshold"
944    )]
945    fn rejects_state_masking_window_at_or_above_persistence_threshold() {
946        let _ = TreeConfig::default()
947            .with_num_state_masking_blocks(0)
948            .with_persistence_threshold(4)
949            .with_memory_block_buffer_target(2)
950            .with_num_state_masking_blocks(2);
951    }
952
953    #[test]
954    #[should_panic(
955        expected = "num_state_masking_blocks + memory_block_buffer_target must be less than persistence_threshold"
956    )]
957    fn rejects_overflowing_state_masking_window() {
958        let _ = TreeConfig::default().with_num_state_masking_blocks(u64::MAX);
959    }
960}