1use crate::{
7 eth_payload_attributes,
8 node::{NodeRestart, NodeTestContext, Relaunch},
9 test_chain_spec,
10 wallet::Wallet,
11 Adapter, NodeBuilderHelper, NodeHelperType, TmpDB, TmpNodeAdapter,
12};
13use alloy_primitives::B256;
14use alloy_rpc_types_engine::{ForkchoiceState, PayloadAttributes};
15use alloy_rpc_types_eth::BlockNumberOrTag;
16use eyre::{ensure, eyre, WrapErr};
17use futures_util::future::{BoxFuture, TryJoinAll};
18use reth_chainspec::{ChainSpec, EthChainSpec, EthereumHardfork};
19use reth_db::{init_db, mdbx::DatabaseArguments, test_utils::TempDatabase};
20use reth_node_api::{PayloadAttrTy, TreeConfig};
21use reth_node_builder::{
22 sync::{BackfillClientFor, BackfillSyncBuilder, PipelineBackfill},
23 ComponentsFor, DebugNode, DebugNodeLauncher, EngineNodeLauncher, Node, NodeBuilder,
24 NodeBuilderWithComponents, NodeConfig, NodeHandle, NodeTypesWithDBAdapter,
25};
26use reth_node_core::{
27 args::{DatadirArgs, DiscoveryArgs, NetworkArgs, PruningArgs, RpcServerArgs, StorageArgs},
28 dirs::{ChainPath, DataDirPath, MaybePlatformPath},
29};
30use reth_primitives_traits::AlloyBlockHeader;
31use reth_provider::{
32 providers::BlockchainProvider, BlockReaderIdExt, DatabaseProviderFactory, MetadataProvider,
33};
34use reth_rpc_server_types::RpcModuleSelection;
35use reth_tasks::Runtime;
36use std::{
37 path::{Path, PathBuf},
38 sync::{Arc, Mutex},
39 time::Duration,
40};
41use tracing::{span, Instrument, Level};
42
43pub struct E2ETestSetupBuilder<N: NodeBuilderHelper> {
77 num_nodes: usize,
78 chain_spec: Arc<N::ChainSpec>,
79 runtime: Option<Runtime>,
80 attributes_generator: AttributesGenerator<N>,
81 connect_nodes: bool,
82 tree_config_modifiers: Vec<TreeConfigModifier>,
83 node_config_modifiers: Vec<NodeConfigModifier<N::ChainSpec>>,
84 storage_v2: bool,
85 dev_launcher: Option<NodeLauncher<N>>,
86 backfill_launcher: Option<NodeLauncher<N>>,
87 dev_payload_attributes: Option<PayloadAttributesMapper<N>>,
88 node_factory: NodeFactory<N>,
89 node_builder_modifiers: Vec<NodeBuilderModifier<N>>,
90 restartable: bool,
91}
92
93impl<N: NodeBuilderHelper> E2ETestSetupBuilder<N> {
94 pub fn new(num_nodes: usize, chain_spec: Arc<N::ChainSpec>) -> Self
100 where
101 PayloadAttrTy<N>: From<PayloadAttributes>,
102 {
103 let attributes_chain_spec = chain_spec.clone();
104 Self::new_with_attributes_generator(num_nodes, chain_spec, move |timestamp| {
105 eth_payload_attributes(&attributes_chain_spec, timestamp).into()
106 })
107 }
108
109 pub fn new_with_attributes_generator<G>(
116 num_nodes: usize,
117 chain_spec: Arc<N::ChainSpec>,
118 attributes_generator: G,
119 ) -> Self
120 where
121 G: Fn(u64) -> PayloadAttrTy<N> + Send + Sync + 'static,
122 {
123 Self {
124 num_nodes,
125 chain_spec,
126 runtime: None,
127 attributes_generator: Arc::new(attributes_generator),
128 connect_nodes: true,
129 tree_config_modifiers: Vec::new(),
130 node_config_modifiers: Vec::new(),
131 storage_v2: StorageArgs::default().v2,
132 dev_launcher: None,
133 backfill_launcher: None,
134 dev_payload_attributes: None,
135 node_factory: Arc::new(|_| N::default()),
136 node_builder_modifiers: Vec::new(),
137 restartable: false,
138 }
139 }
140
141 pub const fn with_num_nodes(mut self, num_nodes: usize) -> Self {
143 self.num_nodes = num_nodes;
144 self
145 }
146
147 pub fn with_runtime(mut self, runtime: Runtime) -> Self {
154 self.runtime = Some(runtime);
155 self
156 }
157
158 pub fn with_attributes_generator<G>(mut self, generator: G) -> Self
164 where
165 G: Fn(u64) -> PayloadAttrTy<N> + Send + Sync + 'static,
166 {
167 self.attributes_generator = Arc::new(generator);
168 self
169 }
170
171 pub const fn with_connect_nodes(mut self, connect_nodes: bool) -> Self {
173 self.connect_nodes = connect_nodes;
174 self
175 }
176
177 pub fn with_tree_config_modifier<G>(mut self, modifier: G) -> Self
184 where
185 G: Fn(TreeConfig) -> TreeConfig + Send + Sync + 'static,
186 {
187 self.tree_config_modifiers.push(Box::new(modifier));
188 self
189 }
190
191 pub fn with_node_config_modifier<G>(mut self, modifier: G) -> Self
197 where
198 G: Fn(NodeConfig<N::ChainSpec>) -> NodeConfig<N::ChainSpec> + Send + Sync + 'static,
199 {
200 self.node_config_modifiers.push(Box::new(modifier));
201 self
202 }
203
204 pub fn with_rpc_modifier<G>(self, modifier: G) -> Self
209 where
210 G: Fn(RpcServerArgs) -> RpcServerArgs + Send + Sync + 'static,
211 {
212 self.with_node_config_modifier(move |mut config| {
213 config.rpc = modifier(config.rpc);
214 config
215 })
216 }
217
218 pub fn with_pruning(self, pruning: PruningArgs) -> Self {
220 self.with_node_config_modifier(move |config| config.with_pruning(pruning.clone()))
221 }
222
223 pub const fn with_storage_v2(mut self, storage_v2: bool) -> Self {
229 self.storage_v2 = storage_v2;
230 self
231 }
232
233 pub fn with_dev_mode(self, dev: bool) -> Self {
238 self.with_node_config_modifier(move |config| config.set_dev(dev))
239 }
240
241 pub fn with_dev_mining(mut self, block_time: Option<Duration>) -> Self
251 where
252 N: DebugNode<Adapter<N>>,
253 {
254 self.dev_launcher =
255 Some(Arc::new(|args, database| Box::pin(launch_dev_node::<N>(args, database))));
256 self.with_node_config_modifier(move |mut config| {
257 config.dev.dev = true;
258 config.dev.block_time = block_time;
259 config
260 })
261 }
262
263 pub fn map_dev_payload_attributes<G>(mut self, map: G) -> Self
269 where
270 G: Fn(PayloadAttrTy<N>) -> PayloadAttrTy<N> + Send + Sync + 'static,
271 {
272 self.dev_payload_attributes = Some(match self.dev_payload_attributes.take() {
273 Some(prev) => Arc::new(move |attributes| map(prev(attributes))),
274 None => Arc::new(map),
275 });
276 self
277 }
278
279 pub fn with_backfill<F, B>(mut self, backfill: F) -> Self
289 where
290 F: Fn(&NodeConfig<N::ChainSpec>) -> B + Send + Sync + 'static,
291 B: BackfillSyncBuilder<NodeTypesWithDBAdapter<N, TmpDB>, BackfillClient<N>> + 'static,
292 {
293 self.backfill_launcher = Some(Arc::new(move |args, database| {
294 let backfill = backfill(&args.node_config);
295 Box::pin(launch_test_node(args, database, backfill))
296 }));
297 self
298 }
299
300 pub fn with_node<F>(mut self, node: F) -> Self
306 where
307 F: Fn(usize) -> N + Send + Sync + 'static,
308 {
309 self.node_factory = Arc::new(node);
310 self
311 }
312
313 pub fn with_node_builder_modifier<G>(mut self, modifier: G) -> Self
328 where
329 G: Fn(TestNodeBuilder<N>) -> TestNodeBuilder<N> + Send + Sync + 'static,
330 {
331 self.node_builder_modifiers.push(Arc::new(modifier));
332 self
333 }
334
335 pub const fn with_restartable_nodes(mut self) -> Self {
342 self.restartable = true;
343 self
344 }
345
346 pub async fn build(self) -> eyre::Result<(Vec<NodeHelperType<N>>, Wallet)> {
348 ensure!(
349 self.dev_launcher.is_none() || self.num_nodes == 1,
350 "dev mining requires a single node setup, got {} nodes",
351 self.num_nodes
352 );
353 ensure!(
354 !self.restartable || self.runtime.is_none(),
355 "restartable nodes run on a runtime of their own and can not use the runtime set with \
356 `with_runtime`"
357 );
358 ensure!(
359 self.dev_launcher.is_none() || self.backfill_launcher.is_none(),
360 "dev mining nodes backfill with the staged pipeline and can not use the backfill set \
361 with `with_backfill`"
362 );
363 ensure!(
365 self.backfill_launcher.is_some() || !self.node_and_tree_config().0.network.snap_v2,
366 "nodes with `--snap.v2` need a backfill set with `with_backfill`, e.g. \
367 `EthereumBackfill::new`"
368 );
369 let dev_mining = self.dev_launcher.is_some();
370 let launch = self.dev_launcher.as_ref().or(self.backfill_launcher.as_ref()).cloned();
371 let launch = launch.unwrap_or_else(|| {
372 Arc::new(|args, database| Box::pin(launch_test_node(args, database, PipelineBackfill)))
373 });
374 let runtime =
376 (!self.restartable).then(|| self.runtime.clone().unwrap_or_else(Runtime::test));
377
378 let mut nodes = (0..self.num_nodes)
379 .map(async |idx| {
380 let (node_config, tree_config) = self.node_and_tree_config();
381 let mines = dev_mining && node_config.dev.dev;
384 let args = LaunchArgs {
385 idx,
386 node_factory: self.node_factory.clone(),
387 node_builder_modifiers: self.node_builder_modifiers.clone(),
388 node_config,
389 runtime: runtime.clone(),
390 tree_config,
391 datadir: reth_db::test_utils::tempdir_path(),
392 attributes_generator: self.attributes_generator.clone(),
393 dev_payload_attributes: self.dev_payload_attributes.clone(),
394 };
395 let node = launch_node(launch.clone(), args, self.restartable, mines)
396 .instrument(span!(Level::INFO, "node", idx))
397 .await?;
398
399 if !mines {
400 let genesis = node.block_hash(self.chain_spec.genesis_header().number());
401 node.update_forkchoice(genesis, genesis).await?;
402 }
403
404 eyre::Ok(node)
405 })
406 .collect::<TryJoinAll<_>>()
407 .await?;
408
409 if self.connect_nodes {
410 for idx in 1..self.num_nodes {
411 let (prev, current) = nodes.split_at_mut(idx);
412 prev[idx - 1].connect(&mut current[0]).await;
413 }
414
415 if self.num_nodes > 2 {
417 let (first, rest) = nodes.split_at_mut(1);
418 rest.last_mut().unwrap().connect(&mut first[0]).await;
419 }
420 }
421
422 Ok((nodes, Wallet::default().with_chain_id(self.chain_spec.chain().into())))
423 }
424
425 pub async fn build_single(self) -> eyre::Result<(NodeHelperType<N>, Wallet)> {
429 ensure!(self.num_nodes == 1, "expected a single node setup, got {} nodes", self.num_nodes);
430 let (mut nodes, wallet) = self.build().await?;
431 Ok((nodes.pop().expect("one node was launched"), wallet))
432 }
433
434 fn node_and_tree_config(&self) -> (NodeConfig<N::ChainSpec>, TreeConfig) {
439 let node_config = self.node_config_modifiers.iter().fold(
440 test_node_config(self.chain_spec.clone())
441 .with_storage(StorageArgs { v2: self.storage_v2 }),
442 |config, modifier| modifier(config),
443 );
444 let tree_config = self
445 .tree_config_modifiers
446 .iter()
447 .fold(node_config.tree_config(), |config, modifier| modifier(config));
448 (node_config, tree_config)
449 }
450}
451
452impl<N: NodeBuilderHelper> std::fmt::Debug for E2ETestSetupBuilder<N> {
453 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
454 f.debug_struct("E2ETestSetupBuilder")
455 .field("num_nodes", &self.num_nodes)
456 .field("runtime", &self.runtime)
457 .field("connect_nodes", &self.connect_nodes)
458 .field("tree_config_modifiers", &self.tree_config_modifiers.len())
459 .field("node_config_modifiers", &self.node_config_modifiers.len())
460 .field("storage_v2", &self.storage_v2)
461 .field("dev_mining", &self.dev_launcher.is_some())
462 .field("custom_backfill", &self.backfill_launcher.is_some())
463 .field("node_builder_modifiers", &self.node_builder_modifiers.len())
464 .field("restartable", &self.restartable)
465 .finish_non_exhaustive()
466 }
467}
468
469pub trait E2ETestSetupExt: NodeBuilderHelper {
471 fn test_setup(num_nodes: usize, chain_spec: Arc<Self::ChainSpec>) -> E2ETestSetupBuilder<Self>
474 where
475 PayloadAttrTy<Self>: From<PayloadAttributes>,
476 {
477 E2ETestSetupBuilder::new(num_nodes, chain_spec)
478 }
479
480 fn test_setup_for(fork: EthereumHardfork) -> E2ETestSetupBuilder<Self>
486 where
487 Self::ChainSpec: From<ChainSpec>,
488 PayloadAttrTy<Self>: From<PayloadAttributes>,
489 {
490 let chain_spec = Arc::unwrap_or_clone(test_chain_spec(fork));
491 E2ETestSetupBuilder::new(1, Arc::new(chain_spec.into()))
492 }
493}
494
495impl<N: NodeBuilderHelper> E2ETestSetupExt for N {}
496
497type TreeConfigModifier = Box<dyn Fn(TreeConfig) -> TreeConfig + Send + Sync>;
499
500type NodeConfigModifier<C> = Box<dyn Fn(NodeConfig<C>) -> NodeConfig<C> + Send + Sync>;
502
503pub(crate) type AttributesGenerator<N> = Arc<dyn Fn(u64) -> PayloadAttrTy<N> + Send + Sync>;
505
506type PayloadAttributesMapper<N> = Arc<dyn Fn(PayloadAttrTy<N>) -> PayloadAttrTy<N> + Send + Sync>;
508
509pub type TestNodeBuilder<N> = NodeBuilderWithComponents<
514 TmpNodeAdapter<N>,
515 <N as Node<TmpNodeAdapter<N>>>::ComponentsBuilder,
516 <N as Node<TmpNodeAdapter<N>>>::AddOns,
517>;
518
519type NodeFactory<N> = Arc<dyn Fn(usize) -> N + Send + Sync>;
521
522type NodeBuilderModifier<N> = Arc<dyn Fn(TestNodeBuilder<N>) -> TestNodeBuilder<N> + Send + Sync>;
524
525type NodeLauncher<N> = Arc<
527 dyn Fn(LaunchArgs<N>, TmpDB) -> BoxFuture<'static, eyre::Result<NodeHelperType<N>>>
528 + Send
529 + Sync,
530>;
531
532type BackfillClient<N> = BackfillClientFor<TmpNodeAdapter<N>, ComponentsFor<TmpNodeAdapter<N>>>;
534
535pub(crate) struct LaunchArgs<N: NodeBuilderHelper> {
537 pub(crate) idx: usize,
539 pub(crate) node_factory: NodeFactory<N>,
541 pub(crate) node_builder_modifiers: Vec<NodeBuilderModifier<N>>,
543 pub(crate) node_config: NodeConfig<N::ChainSpec>,
545 pub(crate) runtime: Option<Runtime>,
548 pub(crate) tree_config: TreeConfig,
550 pub(crate) datadir: PathBuf,
552 pub(crate) attributes_generator: AttributesGenerator<N>,
554 pub(crate) dev_payload_attributes: Option<PayloadAttributesMapper<N>>,
556}
557
558impl<N: NodeBuilderHelper> Clone for LaunchArgs<N> {
560 fn clone(&self) -> Self {
561 Self {
562 idx: self.idx,
563 node_factory: self.node_factory.clone(),
564 node_builder_modifiers: self.node_builder_modifiers.clone(),
565 node_config: self.node_config.clone(),
566 runtime: self.runtime.clone(),
567 tree_config: self.tree_config.clone(),
568 datadir: self.datadir.clone(),
569 attributes_generator: self.attributes_generator.clone(),
570 dev_payload_attributes: self.dev_payload_attributes.clone(),
571 }
572 }
573}
574
575pub(crate) fn test_node_config<C>(chain_spec: Arc<C>) -> NodeConfig<C> {
581 let mut config = NodeConfig::new(chain_spec)
582 .with_network(NetworkArgs {
583 discovery: DiscoveryArgs { disable_discovery: true, ..DiscoveryArgs::default() },
584 ..NetworkArgs::default()
585 })
586 .with_unused_ports()
587 .with_rpc(
588 RpcServerArgs::default()
589 .with_unused_ports()
590 .with_http()
591 .with_http_api(RpcModuleSelection::All),
592 );
593 config.debug.startup_sync_state_idle = true;
596 config.network.no_persist_peers = true;
599 config.engine.cross_block_cache_size = 1;
602 config.debug.lazy_kzg_settings = true;
604 config
605}
606
607pub(crate) async fn launch_test_node<N, B>(
609 args: LaunchArgs<N>,
610 database: TmpDB,
611 backfill: B,
612) -> eyre::Result<NodeHelperType<N>>
613where
614 N: NodeBuilderHelper,
615 B: BackfillSyncBuilder<NodeTypesWithDBAdapter<N, TmpDB>, BackfillClient<N>> + 'static,
616{
617 let LaunchArgs {
618 idx,
619 node_factory,
620 node_builder_modifiers,
621 node_config,
622 runtime,
623 tree_config,
624 datadir,
625 attributes_generator,
626 ..
627 } = args;
628 let (builder, datadir) = test_node_builder(
629 node_factory(idx),
630 node_config,
631 datadir,
632 database,
633 &node_builder_modifiers,
634 );
635 let runtime = runtime.unwrap_or_else(Runtime::test);
636 let NodeHandle { node, node_exit_future } = builder
637 .launch_with(EngineNodeLauncher::new(runtime, datadir, tree_config).with_backfill(backfill))
638 .await?;
639
640 let mut node =
641 NodeTestContext::new(node, move |timestamp| attributes_generator(timestamp)).await?;
642 node.exit_future = Mutex::new(Some(node_exit_future));
643 Ok(node)
644}
645
646async fn launch_dev_node<N>(args: LaunchArgs<N>, database: TmpDB) -> eyre::Result<NodeHelperType<N>>
648where
649 N: NodeBuilderHelper + DebugNode<Adapter<N>>,
650{
651 let LaunchArgs {
652 idx,
653 node_factory,
654 node_builder_modifiers,
655 node_config,
656 runtime,
657 tree_config,
658 datadir,
659 attributes_generator,
660 dev_payload_attributes,
661 } = args;
662 let (builder, datadir) = test_node_builder(
663 node_factory(idx),
664 node_config,
665 datadir,
666 database,
667 &node_builder_modifiers,
668 );
669 let runtime = runtime.unwrap_or_else(Runtime::test);
670 let launch = builder.launch_with(DebugNodeLauncher::new(EngineNodeLauncher::new(
671 runtime,
672 datadir,
673 tree_config,
674 )));
675 let launch = match dev_payload_attributes {
676 Some(map) => launch.map_debug_payload_attributes(move |attributes| map(attributes)),
677 None => launch,
678 };
679 let NodeHandle { node, node_exit_future } = launch.await?;
680
681 let mut node =
682 NodeTestContext::new(node, move |timestamp| attributes_generator(timestamp)).await?;
683 node.exit_future = Mutex::new(Some(node_exit_future));
684 Ok(node)
685}
686
687fn test_node_builder<N: NodeBuilderHelper>(
690 node: N,
691 node_config: NodeConfig<N::ChainSpec>,
692 datadir: PathBuf,
693 database: TmpDB,
694 node_builder_modifiers: &[NodeBuilderModifier<N>],
695) -> (TestNodeBuilder<N>, ChainPath<DataDirPath>) {
696 let datadir_args =
697 DatadirArgs { datadir: MaybePlatformPath::from(datadir), ..node_config.datadir.clone() };
698 let node_config = node_config.with_datadir_args(datadir_args);
699 let datadir = node_config.datadir();
700 let builder = NodeBuilder::new(node_config)
701 .with_database(database)
702 .with_types_and_provider::<N, BlockchainProvider<_>>()
703 .with_components(node.components_builder())
704 .with_add_ons(node.add_ons());
705 let builder =
706 node_builder_modifiers.iter().fold(builder, |builder, modifier| modifier(builder));
707 (builder, datadir)
708}
709
710fn launch_node<N: NodeBuilderHelper>(
717 launch: NodeLauncher<N>,
718 args: LaunchArgs<N>,
719 restartable: bool,
720 mines: bool,
721) -> BoxFuture<'static, eyre::Result<NodeHelperType<N>>> {
722 Box::pin(async move {
723 let database = open_test_database(&args.datadir)?;
724 let relaunch_args = restartable.then(|| args.clone());
725 let mut node = launch(args, database.clone()).await?;
726 if let Some(args) = relaunch_args {
727 let relaunch: Relaunch<_> =
728 Arc::new(move || relaunch_node(launch.clone(), args.clone(), mines));
729 node.restart = Some(NodeRestart { database, relaunch });
730 }
731 Ok(node)
732 })
733}
734
735fn relaunch_node<N: NodeBuilderHelper>(
737 launch: NodeLauncher<N>,
738 args: LaunchArgs<N>,
739 mines: bool,
740) -> BoxFuture<'static, eyre::Result<NodeHelperType<N>>> {
741 let span = span!(Level::INFO, "node", idx = args.idx);
742 Box::pin(
743 async move {
744 let node = launch_node(launch, args, true, mines).await?;
745 if !mines {
746 restate_forkchoice(&node).await?;
747 }
748 Ok(node)
749 }
750 .instrument(span),
751 )
752}
753
754async fn restate_forkchoice<N: NodeBuilderHelper>(node: &NodeHelperType<N>) -> eyre::Result<()> {
760 let provider = &node.inner.provider;
761 if provider
764 .database_provider_ro()?
765 .snap_attempt()?
766 .is_some_and(|attempt| !attempt.is_verified())
767 {
768 return Ok(())
769 }
770 let hash = |tag| -> eyre::Result<Option<B256>> {
771 Ok(provider.sealed_header_by_number_or_tag(tag)?.map(|header| header.hash()))
772 };
773 let head = hash(BlockNumberOrTag::Latest)?
774 .ok_or_else(|| eyre!("the restarted node has no latest block"))?;
775 let state = ForkchoiceState {
777 head_block_hash: head,
778 safe_block_hash: hash(BlockNumberOrTag::Safe)?.unwrap_or_default(),
779 finalized_block_hash: hash(BlockNumberOrTag::Finalized)?.unwrap_or_default(),
780 };
781 let updated =
782 node.inner.add_ons_handle.beacon_engine_handle.fork_choice_updated(state, None).await?;
783 ensure!(
784 updated.is_valid(),
785 "forkchoice update to the head {head} the restarted node loaded from disk is not valid: {}",
786 updated.payload_status.status
787 );
788 Ok(())
789}
790
791pub(crate) fn open_test_database(datadir: &Path) -> eyre::Result<TmpDB> {
798 let path = datadir.join("db");
799 let database = init_db(&path, DatabaseArguments::test())
800 .wrap_err_with(|| format!("failed to open the database at {}", path.display()))?;
801 Ok(Arc::new(TempDatabase::new(database, datadir.to_path_buf())))
802}
803
804#[cfg(test)]
805mod tests {
806 use super::*;
807 use reth_node_ethereum::EthereumNode;
808
809 #[test]
810 fn tree_config_uses_small_cross_block_cache() {
811 let (_, tree_config) =
812 EthereumNode::test_setup_for(EthereumHardfork::Cancun).node_and_tree_config();
813 assert_eq!(tree_config.cross_block_cache_size(), 1024 * 1024);
814 }
815
816 #[test]
817 fn tree_config_follows_node_config() {
818 let (_, tree_config) = EthereumNode::test_setup_for(EthereumHardfork::Cancun)
819 .with_node_config_modifier(|mut config| {
820 config.engine.cross_block_cache_size = 2;
821 config.engine.persistence_threshold = 0;
822 config.debug.skip_state_root = true;
823 config
824 })
825 .node_and_tree_config();
826 assert_eq!(tree_config.cross_block_cache_size(), 2 * 1024 * 1024);
827 assert_eq!(tree_config.persistence_threshold(), 0);
828 assert!(tree_config.skip_state_root());
829 }
830
831 #[test]
832 fn tree_config_modifiers_override_node_config() {
833 let (_, tree_config) = EthereumNode::test_setup_for(EthereumHardfork::Cancun)
835 .with_tree_config_modifier(|config| config.with_state_root_fallback(false))
836 .with_node_config_modifier(|mut config| {
837 config.engine.state_root_fallback = true;
838 config
839 })
840 .node_and_tree_config();
841 assert!(!tree_config.state_root_fallback());
842 }
843
844 #[expect(dead_code)]
847 async fn build_with_attributes_generator<N: NodeBuilderHelper>(
848 chain_spec: Arc<N::ChainSpec>,
849 attributes_generator: fn(u64) -> PayloadAttrTy<N>,
850 ) -> eyre::Result<(Vec<NodeHelperType<N>>, Wallet)> {
851 E2ETestSetupBuilder::new_with_attributes_generator(1, chain_spec, attributes_generator)
852 .build()
853 .await
854 }
855
856 #[expect(dead_code)]
858 fn node_builder_modifier_can_use_builder_hooks<N: NodeBuilderHelper>(
859 setup: E2ETestSetupBuilder<N>,
860 ) -> E2ETestSetupBuilder<N> {
861 setup.with_node_builder_modifier(|builder| {
862 builder
863 .on_component_initialized(|_| Ok(()))
864 .on_node_started(|_| Ok(()))
865 .on_rpc_started(|_, _| Ok(()))
866 .extend_rpc_modules(|_| Ok(()))
867 .map_add_ons(|add_ons| add_ons)
868 .install_exex("exex", |_| async { Ok(async { Ok(()) }) })
869 })
870 }
871}