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