Skip to main content

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