domain_test_service/
domain.rs

1//! Utilities used for testing with the domain.
2#![warn(missing_docs)]
3
4use crate::chain_spec::create_domain_spec;
5use crate::{
6    construct_extrinsic_generic, node_config, BalanceOf, DomainRuntime, EcdsaKeyring,
7    Sr25519Keyring, UncheckedExtrinsicFor, AUTO_ID_DOMAIN_ID, EVM_DOMAIN_ID,
8};
9use cross_domain_message_gossip::ChainMsg;
10use domain_client_operator::snap_sync::ConsensusChainSyncParams;
11use domain_client_operator::{fetch_domain_bootstrap_info, BootstrapResult, OperatorStreams};
12use domain_runtime_primitives::opaque::Block;
13use domain_runtime_primitives::{Balance, EthereumAccountId};
14use domain_service::providers::DefaultProvider;
15use domain_service::FullClient;
16use domain_test_primitives::{EvmOnchainStateApi, OnchainStateApi};
17use frame_support::dispatch::{DispatchInfo, PostDispatchInfo};
18use frame_system::pallet_prelude::{BlockNumberFor, RuntimeCallFor};
19use pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi;
20use sc_client_api::HeaderBackend;
21use sc_domains::RuntimeExecutor;
22use sc_network::service::traits::NetworkService;
23use sc_network::{NetworkStateInfo, ReputationChange};
24use sc_network_sync::SyncingService;
25use sc_service::config::MultiaddrWithPeerId;
26use sc_service::{BasePath, Role, RpcHandlers, TFullBackend, TaskManager, TransactionPool};
27use sc_transaction_pool_api::OffchainTransactionPoolFactory;
28use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedSender};
29use sp_api::{ApiExt, ConstructRuntimeApi, Metadata, ProvideRuntimeApi};
30use sp_block_builder::BlockBuilder;
31use sp_blockchain::HashAndNumber;
32use sp_consensus_subspace::SubspaceApi;
33use sp_core::{Encode, H256};
34use sp_domains::core_api::DomainCoreApi;
35use sp_domains::{DomainId, OperatorId, PermissionedActionAllowedBy};
36use sp_messenger::messages::{ChainId, ChannelId};
37use sp_messenger::{MessengerApi, RelayerApi};
38use sp_offchain::OffchainWorkerApi;
39use sp_runtime::traits::{AsSystemOriginSigner, Block as BlockT, Dispatchable, NumberFor};
40use sp_runtime::OpaqueExtrinsic;
41use sp_session::SessionKeys;
42use sp_transaction_pool::runtime_api::TaggedTransactionQueue;
43use std::future::Future;
44use std::sync::Arc;
45use subspace_runtime_primitives::opaque::Block as CBlock;
46use subspace_runtime_primitives::Nonce;
47use subspace_test_primitives::DOMAINS_BLOCK_PRUNING_DEPTH;
48use subspace_test_service::MockConsensusNode;
49use substrate_frame_rpc_system::AccountNonceApi;
50use substrate_test_client::{
51    BlockchainEventsExt, RpcHandlersExt, RpcTransactionError, RpcTransactionOutput,
52};
53
54/// The backend type used by the test service.
55pub type Backend = TFullBackend<Block>;
56
57type Client<RuntimeApi> = FullClient<Block, RuntimeApi>;
58
59/// Domain executor for the test service.
60pub type DomainOperator<RuntimeApi> =
61    domain_service::DomainOperator<Block, CBlock, subspace_test_client::Client, RuntimeApi>;
62
63/// A generic domain node instance used for testing.
64pub struct DomainNode<Runtime, RuntimeApi>
65where
66    Runtime: DomainRuntime,
67    RuntimeApi: ConstructRuntimeApi<Block, Client<RuntimeApi>> + Send + Sync + 'static,
68    RuntimeApi::RuntimeApi: ApiExt<Block>
69        + Metadata<Block>
70        + BlockBuilder<Block>
71        + OffchainWorkerApi<Block>
72        + SessionKeys<Block>
73        + DomainCoreApi<Block>
74        + MessengerApi<Block, NumberFor<CBlock>, <CBlock as BlockT>::Hash>
75        + TaggedTransactionQueue<Block>
76        + AccountNonceApi<Block, Runtime::AccountId, Nonce>
77        + TransactionPaymentRuntimeApi<Block, Balance>
78        + RelayerApi<Block, NumberFor<Block>, NumberFor<CBlock>, <CBlock as BlockT>::Hash>,
79{
80    /// The domain id
81    pub domain_id: DomainId,
82    /// The node's account key
83    pub key: Runtime::Keyring,
84    /// TaskManager's instance.
85    pub task_manager: TaskManager,
86    /// Client's instance.
87    pub client: Arc<Client<RuntimeApi>>,
88    /// Client backend.
89    pub backend: Arc<Backend>,
90    /// Code executor.
91    pub code_executor: Arc<RuntimeExecutor>,
92    /// Network service.
93    pub network_service: Arc<dyn NetworkService>,
94    /// Sync service.
95    pub sync_service: Arc<SyncingService<Block>>,
96    /// The `MultiaddrWithPeerId` to this node. This is useful if you want to pass it as "boot node"
97    /// to other nodes.
98    pub addr: MultiaddrWithPeerId,
99    /// RPCHandlers to make RPC queries.
100    pub rpc_handlers: RpcHandlers,
101    /// Domain oeprator.
102    pub operator: DomainOperator<RuntimeApi>,
103    /// Sink to the node's tx pool
104    pub tx_pool_sink: TracingUnboundedSender<ChainMsg>,
105    /// The node base path
106    pub base_path: BasePath,
107}
108
109impl<Runtime, RuntimeApi> DomainNode<Runtime, RuntimeApi>
110where
111    Runtime: frame_system::Config<Hash = H256>
112        + pallet_transaction_payment::Config
113        + DomainRuntime
114        + Send
115        + Sync,
116    Runtime::RuntimeCall:
117        Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + Send + Sync,
118    crate::BalanceOf<Runtime>: Send + Sync + From<u64> + sp_runtime::FixedPointOperand,
119    u64: From<BlockNumberFor<Runtime>>,
120    RuntimeApi: ConstructRuntimeApi<Block, Client<RuntimeApi>> + Send + Sync + 'static,
121    RuntimeApi::RuntimeApi: ApiExt<Block>
122        + Metadata<Block>
123        + BlockBuilder<Block>
124        + OffchainWorkerApi<Block>
125        + SessionKeys<Block>
126        + DomainCoreApi<Block>
127        + TaggedTransactionQueue<Block>
128        + AccountNonceApi<Block, <Runtime as DomainRuntime>::AccountId, Nonce>
129        + TransactionPaymentRuntimeApi<Block, Balance>
130        + MessengerApi<Block, NumberFor<CBlock>, <CBlock as BlockT>::Hash>
131        + RelayerApi<Block, NumberFor<Block>, NumberFor<CBlock>, <CBlock as BlockT>::Hash>
132        + OnchainStateApi<Block, <Runtime as DomainRuntime>::AccountId, Balance>,
133    <RuntimeCallFor<Runtime> as Dispatchable>::RuntimeOrigin:
134        AsSystemOriginSigner<<Runtime as frame_system::Config>::AccountId> + Clone,
135{
136    #[allow(clippy::too_many_arguments)]
137    async fn build(
138        domain_id: DomainId,
139        tokio_handle: tokio::runtime::Handle,
140        key: Runtime::Keyring,
141        base_path: BasePath,
142        domain_nodes: Vec<MultiaddrWithPeerId>,
143        domain_nodes_exclusive: bool,
144        skip_empty_bundle_production: bool,
145        maybe_operator_id: Option<OperatorId>,
146        role: Role,
147        mock_consensus_node: &mut MockConsensusNode,
148    ) -> Self {
149        let mut domain_config = node_config(
150            domain_id,
151            tokio_handle.clone(),
152            Runtime::to_seed(key),
153            domain_nodes,
154            domain_nodes_exclusive,
155            role,
156            base_path.clone(),
157            Box::new(create_domain_spec()) as Box<_>,
158        )
159        .expect("could not generate domain node Configuration");
160
161        let domain_backend = sc_service::new_db_backend::<Block>(domain_config.db_config())
162            .unwrap_or_else(
163                |err| panic!("Failed to create domain backend: {domain_id:?} {role:?} {base_path:?} error: {err:?}")
164            );
165
166        let BootstrapResult {
167            domain_instance_data,
168            domain_created_at,
169            imported_block_notification_stream,
170            ..
171        } = fetch_domain_bootstrap_info::<Block, _, _, _>(
172            &*mock_consensus_node.client,
173            &*domain_backend,
174            domain_id,
175        )
176        .await
177        .expect("Failed to get domain instance data");
178
179        domain_config
180            .chain_spec
181            .set_storage(domain_instance_data.raw_genesis.into_storage());
182
183        let span = sc_tracing::tracing::info_span!(
184            sc_tracing::logging::PREFIX_LOG_SPAN,
185            name = domain_config.network.node_name.as_str()
186        );
187        let _enter = span.enter();
188
189        let multiaddr = domain_config.network.listen_addresses[0].clone();
190
191        let operator_streams = OperatorStreams {
192            // Set `consensus_block_import_throttling_buffer_size` to 0 to ensure the primary chain will not be
193            // ahead of the execution chain by more than one block, thus slot will not be skipped in test.
194            consensus_block_import_throttling_buffer_size: 0,
195            block_importing_notification_stream: mock_consensus_node
196                .block_importing_notification_stream(),
197            imported_block_notification_stream,
198            new_slot_notification_stream: mock_consensus_node.new_slot_notification_stream(),
199            acknowledgement_sender_stream: mock_consensus_node.new_acknowledgement_sender_stream(),
200            _phantom: Default::default(),
201        };
202
203        let (domain_message_sink, domain_message_receiver) =
204            tracing_unbounded("domain_message_channel", 100);
205        let gossip_msg_sink = mock_consensus_node
206            .xdm_gossip_worker_builder()
207            .gossip_msg_sink();
208
209        let maybe_operator_id = role
210            .is_authority()
211            .then_some(maybe_operator_id.unwrap_or(if domain_id == EVM_DOMAIN_ID { 0 } else { 1 }));
212
213        let consensus_best_hash = mock_consensus_node.client.info().best_hash;
214        let chain_constants = mock_consensus_node
215            .client
216            .runtime_api()
217            .chain_constants(consensus_best_hash)
218            .unwrap();
219
220        let domain_params = domain_service::DomainParams {
221            domain_id,
222            domain_config,
223            domain_created_at,
224            consensus_client: mock_consensus_node.client.clone(),
225            consensus_offchain_tx_pool_factory: OffchainTransactionPoolFactory::new(
226                mock_consensus_node.transaction_pool.clone(),
227            ),
228            domain_sync_oracle: mock_consensus_node.sync_service.clone(),
229            consensus_network: mock_consensus_node.network_service.clone(),
230            operator_streams,
231            gossip_message_sink: gossip_msg_sink,
232            domain_message_receiver,
233            provider: DefaultProvider,
234            skip_empty_bundle_production,
235            skip_out_of_order_slot: true,
236            maybe_operator_id,
237            confirmation_depth_k: chain_constants.confirmation_depth_k(),
238            challenge_period: DOMAINS_BLOCK_PRUNING_DEPTH,
239            consensus_chain_sync_params: None::<
240                ConsensusChainSyncParams<_, <Block as BlockT>::Header>,
241            >,
242            domain_backend,
243        };
244
245        let domain_node = domain_service::new_full::<
246            _,
247            _,
248            _,
249            _,
250            _,
251            _,
252            RuntimeApi,
253            <Runtime as DomainRuntime>::AccountId,
254            _,
255        >(domain_params)
256        .await
257        .expect("failed to build domain node");
258
259        let domain_service::NewFull {
260            task_manager,
261            client,
262            backend,
263            code_executor,
264            network_service,
265            sync_service,
266            network_starter,
267            rpc_handlers,
268            operator,
269            ..
270        } = domain_node;
271
272        if role.is_authority() {
273            mock_consensus_node
274                .xdm_gossip_worker_builder()
275                .push_chain_sink(ChainId::Domain(domain_id), domain_message_sink.clone());
276        }
277
278        let addr = MultiaddrWithPeerId {
279            multiaddr,
280            peer_id: network_service.local_peer_id(),
281        };
282
283        network_starter.start_network();
284
285        DomainNode {
286            domain_id,
287            key,
288            task_manager,
289            client,
290            backend,
291            code_executor,
292            network_service,
293            sync_service,
294            addr,
295            rpc_handlers,
296            operator,
297            tx_pool_sink: domain_message_sink,
298            base_path,
299        }
300    }
301
302    /// Wait for `count` blocks to be imported in the node and then exit. This function will not
303    /// return if no blocks are ever created, thus you should restrict the maximum amount of time of
304    /// the test execution.
305    pub fn wait_for_blocks(&self, count: usize) -> impl Future<Output = ()> {
306        self.client.wait_for_blocks(count)
307    }
308
309    /// Get the nonce of the node account
310    pub fn account_nonce(&self) -> u32 {
311        self.client
312            .runtime_api()
313            .account_nonce(
314                self.client.info().best_hash,
315                <Runtime as DomainRuntime>::account_id(self.key),
316            )
317            .expect("Fail to get account nonce")
318    }
319
320    /// Get the nonce of the given account
321    pub fn account_nonce_of(&self, account_id: <Runtime as DomainRuntime>::AccountId) -> u32 {
322        self.client
323            .runtime_api()
324            .account_nonce(self.client.info().best_hash, account_id)
325            .expect("Fail to get account nonce")
326    }
327
328    /// Sends a signed system.remark extrinsic to the pool containing the current account nonce.
329    pub async fn send_system_remark(&self) {
330        let nonce = self.account_nonce();
331        let _ = self
332            .construct_and_send_extrinsic(frame_system::Call::remark {
333                remark: nonce.encode(),
334            })
335            .await
336            .map(|_| ());
337    }
338
339    /// Construct a signed extrinsic with the current nonce of the node account and send it to this node.
340    pub async fn construct_and_send_extrinsic(
341        &self,
342        function: impl Into<<Runtime as frame_system::Config>::RuntimeCall>,
343    ) -> Result<RpcTransactionOutput, RpcTransactionError> {
344        self.construct_and_send_extrinsic_with(self.account_nonce(), 0.into(), function)
345            .await
346    }
347
348    /// Construct a signed extrinsic with the given nonce and tip for the node account and send it to this node.
349    pub async fn construct_and_send_extrinsic_with(
350        &self,
351        nonce: u32,
352        tip: BalanceOf<Runtime>,
353        function: impl Into<<Runtime as frame_system::Config>::RuntimeCall>,
354    ) -> Result<RpcTransactionOutput, RpcTransactionError> {
355        let extrinsic = construct_extrinsic_generic::<Runtime, _>(
356            &self.client,
357            function,
358            self.key,
359            false,
360            nonce,
361            tip,
362        );
363        self.rpc_handlers.send_transaction(extrinsic.into()).await
364    }
365
366    /// Construct a signed extrinsic.
367    pub fn construct_extrinsic(
368        &self,
369        nonce: u32,
370        function: impl Into<<Runtime as frame_system::Config>::RuntimeCall>,
371    ) -> UncheckedExtrinsicFor<Runtime> {
372        construct_extrinsic_generic::<Runtime, _>(
373            &self.client,
374            function,
375            self.key,
376            false,
377            nonce,
378            0.into(),
379        )
380    }
381
382    /// Construct a signed extrinsic with the given transaction tip.
383    pub fn construct_extrinsic_with_tip(
384        &self,
385        nonce: u32,
386        tip: BalanceOf<Runtime>,
387        function: impl Into<<Runtime as frame_system::Config>::RuntimeCall>,
388    ) -> UncheckedExtrinsicFor<Runtime> {
389        construct_extrinsic_generic::<Runtime, _>(
390            &self.client,
391            function,
392            self.key,
393            false,
394            nonce,
395            tip,
396        )
397    }
398
399    /// Send an extrinsic to this node.
400    pub async fn send_extrinsic(
401        &self,
402        extrinsic: impl Into<OpaqueExtrinsic>,
403    ) -> Result<RpcTransactionOutput, RpcTransactionError> {
404        self.rpc_handlers.send_transaction(extrinsic.into()).await
405    }
406
407    /// Get the free balance of the given account
408    pub fn free_balance(&self, account_id: <Runtime as DomainRuntime>::AccountId) -> Balance {
409        self.client
410            .runtime_api()
411            .free_balance(self.client.info().best_hash, account_id)
412            .expect("Fail to get account free balance")
413    }
414
415    /// Returns the open XDM channel for given chain
416    pub fn get_open_channel_for_chain(&self, chain_id: ChainId) -> Option<ChannelId> {
417        self.client
418            .runtime_api()
419            .get_open_channel_for_chain(self.client.info().best_hash, chain_id)
420            .expect("Fail to get open channel for Chain")
421    }
422
423    /// Construct an unsigned extrinsic that can be applied to the test runtime.
424    pub fn construct_unsigned_extrinsic(
425        &self,
426        function: impl Into<<Runtime as frame_system::Config>::RuntimeCall>,
427    ) -> UncheckedExtrinsicFor<Runtime>
428    where
429        Runtime:
430            frame_system::Config<Hash = H256> + pallet_transaction_payment::Config + Send + Sync,
431        RuntimeCallFor<Runtime>:
432            Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + Send + Sync,
433        BalanceOf<Runtime>: Send + Sync + From<u64> + sp_runtime::FixedPointOperand,
434    {
435        let function = function.into();
436        UncheckedExtrinsicFor::<Runtime>::new_bare(function)
437    }
438
439    /// Construct an unsigned extrinsic and send it to this node.
440    pub async fn construct_and_send_unsigned_extrinsic(
441        &self,
442        function: impl Into<<Runtime as frame_system::Config>::RuntimeCall>,
443    ) -> Result<RpcTransactionOutput, RpcTransactionError> {
444        let extrinsic = self.construct_unsigned_extrinsic(function);
445        self.rpc_handlers.send_transaction(extrinsic.into()).await
446    }
447
448    /// Give the peer at `addr` the minimum reputation, which will ban it.
449    // TODO: also ban/unban in the DSN
450    pub fn ban_peer(&self, addr: MultiaddrWithPeerId) {
451        // If unban_peer() has been called on the peer, we need to bump it twice
452        // to give it the minimal reputation.
453        self.network_service.report_peer(
454            addr.peer_id,
455            ReputationChange::new_fatal("Peer banned by test (1)"),
456        );
457        self.network_service.report_peer(
458            addr.peer_id,
459            ReputationChange::new_fatal("Peer banned by test (2)"),
460        );
461    }
462
463    /// Give the peer at `addr` a high reputation, which guarantees it is un-banned it.
464    pub fn unban_peer(&self, addr: MultiaddrWithPeerId) {
465        // If ReputationChange::new_fatal() has been called on the peer, we need to bump it twice
466        // to give it a positive reputation.
467        self.network_service.report_peer(
468            addr.peer_id,
469            ReputationChange::new(i32::MAX, "Peer unbanned by test (1)"),
470        );
471        self.network_service.report_peer(
472            addr.peer_id,
473            ReputationChange::new(i32::MAX, "Peer unbanned by test (2)"),
474        );
475    }
476
477    /// Take and stop the domain node and delete its database lock file
478    pub fn stop(self) -> Result<(), std::io::Error> {
479        let lock_file_path = self.base_path.path().join("paritydb").join("lock");
480        // On Windows, sometimes open files can’t be deleted so `drop` first then delete
481        std::mem::drop(self);
482        // The lock file already being deleted is not a fatal test error, so just log it
483        if let Err(err) = std::fs::remove_file(lock_file_path) {
484            tracing::error!("deleting paritydb lock file failed: {err:?}");
485        }
486        Ok(())
487    }
488
489    /// Remove all tx from the tx pool
490    pub async fn clear_tx_pool(&self) {
491        let tx_hashes: Vec<_> = self
492            .operator
493            .transaction_pool
494            .ready()
495            .map(|t| self.operator.transaction_pool.hash_of(&t.data))
496            .collect();
497
498        let hash_and_number = HashAndNumber {
499            number: self.client.info().best_number,
500            hash: self.client.info().best_hash,
501        };
502        self.operator
503            .transaction_pool
504            .pool()
505            .prune_known(&hash_and_number, &tx_hashes);
506
507        // `ban_time` have set to 0, explicitly wait 1ms here to ensure `clear_stale` will remove
508        // all the bans as the ban time must be passed.
509        tokio::time::sleep(std::time::Duration::from_millis(1)).await;
510        self.operator
511            .transaction_pool
512            .pool()
513            .validated_pool()
514            .clear_stale(&hash_and_number);
515    }
516}
517
518impl<Runtime, RuntimeApi> DomainNode<Runtime, RuntimeApi>
519where
520    Runtime: frame_system::Config<Hash = H256>
521        + pallet_transaction_payment::Config
522        + DomainRuntime
523        + Send
524        + Sync,
525    RuntimeApi: ConstructRuntimeApi<Block, Client<RuntimeApi>> + Send + Sync + 'static,
526    RuntimeApi::RuntimeApi: Metadata<Block>
527        + BlockBuilder<Block>
528        + OffchainWorkerApi<Block>
529        + SessionKeys<Block>
530        + DomainCoreApi<Block>
531        + TaggedTransactionQueue<Block>
532        + AccountNonceApi<Block, <Runtime as DomainRuntime>::AccountId, Nonce>
533        + TransactionPaymentRuntimeApi<Block, Balance>
534        + MessengerApi<Block, NumberFor<CBlock>, <CBlock as BlockT>::Hash>
535        + RelayerApi<Block, NumberFor<Block>, NumberFor<CBlock>, <CBlock as BlockT>::Hash>
536        + EvmOnchainStateApi<Block>,
537{
538    /// Returns the current EVM contract creation allow list.
539    /// Returns `None` if this is not an EVM domain, or if the allow list isn't set (allow all).
540    pub fn evm_contract_creation_allowed_by(
541        &self,
542    ) -> PermissionedActionAllowedBy<EthereumAccountId> {
543        self.client
544            .runtime_api()
545            .evm_contract_creation_allowed_by(self.client.info().best_hash)
546            .expect("Failed to get EVM contact creation allow list")
547            .expect("Should be an EVM domain")
548    }
549}
550
551/// A builder to create a [`DomainNode`].
552pub struct DomainNodeBuilder {
553    tokio_handle: tokio::runtime::Handle,
554    domain_nodes: Vec<MultiaddrWithPeerId>,
555    domain_nodes_exclusive: bool,
556    skip_empty_bundle_production: bool,
557    base_path: BasePath,
558    maybe_operator_id: Option<OperatorId>,
559}
560
561impl DomainNodeBuilder {
562    /// Create a new instance of `Self`.
563    ///
564    /// `tokio_handle` - The tokio handler to use.
565    /// `base_path` - Where databases will be stored.
566    pub fn new(tokio_handle: tokio::runtime::Handle, base_path: BasePath) -> Self {
567        DomainNodeBuilder {
568            tokio_handle,
569            domain_nodes: Vec::new(),
570            domain_nodes_exclusive: false,
571            skip_empty_bundle_production: false,
572            base_path,
573            maybe_operator_id: None,
574        }
575    }
576
577    /// Instruct the node to exclusively connect to registered parachain nodes.
578    ///
579    /// Domain nodes can be registered using [`Self::connect_to_domain_node`].
580    pub fn exclusively_connect_to_registered_parachain_nodes(mut self) -> Self {
581        self.domain_nodes_exclusive = true;
582        self
583    }
584
585    /// Make the node connect to the given domain node.
586    ///
587    /// By default the node will not be connected to any node or will be able to discover any other
588    /// node.
589    pub fn connect_to_domain_node(mut self, addr: MultiaddrWithPeerId) -> Self {
590        self.domain_nodes.push(addr);
591        self
592    }
593
594    /// Skip empty bundle production when there is no non-empty domain block need to confirm
595    pub fn skip_empty_bundle(mut self) -> Self {
596        self.skip_empty_bundle_production = true;
597        self
598    }
599
600    /// Set the operator id
601    pub fn operator_id(mut self, operator_id: OperatorId) -> Self {
602        self.maybe_operator_id = Some(operator_id);
603        self
604    }
605
606    /// Build an EVM domain node
607    pub async fn build_evm_node(
608        self,
609        role: Role,
610        key: EcdsaKeyring,
611        mock_consensus_node: &mut MockConsensusNode,
612    ) -> EvmDomainNode {
613        DomainNode::build(
614            EVM_DOMAIN_ID,
615            self.tokio_handle,
616            key,
617            BasePath::new(
618                self.base_path
619                    .path()
620                    .join(format!("domain-{EVM_DOMAIN_ID}")),
621            ),
622            self.domain_nodes,
623            self.domain_nodes_exclusive,
624            self.skip_empty_bundle_production,
625            self.maybe_operator_id,
626            role,
627            mock_consensus_node,
628        )
629        .await
630    }
631
632    /// Build an Auto ID domain node
633    pub async fn build_auto_id_node(
634        self,
635        role: Role,
636        key: Sr25519Keyring,
637        mock_consensus_node: &mut MockConsensusNode,
638    ) -> AutoIdDomainNode {
639        DomainNode::build(
640            AUTO_ID_DOMAIN_ID,
641            self.tokio_handle,
642            key,
643            BasePath::new(
644                self.base_path
645                    .path()
646                    .join(format!("domain-{AUTO_ID_DOMAIN_ID}")),
647            ),
648            self.domain_nodes,
649            self.domain_nodes_exclusive,
650            self.skip_empty_bundle_production,
651            self.maybe_operator_id,
652            role,
653            mock_consensus_node,
654        )
655        .await
656    }
657
658    /// Build an EVM domain node with the given domain id
659    pub async fn build_evm_node_with(
660        self,
661        role: Role,
662        key: EcdsaKeyring,
663        mock_consensus_node: &mut MockConsensusNode,
664        domain_id: DomainId,
665    ) -> EvmDomainNode {
666        DomainNode::build(
667            domain_id,
668            self.tokio_handle,
669            key,
670            self.base_path,
671            self.domain_nodes,
672            self.domain_nodes_exclusive,
673            self.skip_empty_bundle_production,
674            self.maybe_operator_id,
675            role,
676            mock_consensus_node,
677        )
678        .await
679    }
680}
681
682/// The evm domain node
683pub type EvmDomainNode =
684    DomainNode<evm_domain_test_runtime::Runtime, evm_domain_test_runtime::RuntimeApi>;
685
686/// The evm domain client
687pub type EvmDomainClient = Client<evm_domain_test_runtime::RuntimeApi>;
688
689/// The auto-id domain node
690pub type AutoIdDomainNode =
691    DomainNode<auto_id_domain_test_runtime::Runtime, auto_id_domain_test_runtime::RuntimeApi>;
692
693/// The auto-id domain client
694pub type AutoIdDomainClient = Client<auto_id_domain_test_runtime::RuntimeApi>;