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