1#![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
56pub type Backend = TFullBackend<Block>;
58
59type Client<RuntimeApi> = FullClient<Block, RuntimeApi>;
60
61pub type DomainOperator<RuntimeApi> =
63 domain_service::DomainOperator<Block, CBlock, subspace_test_client::Client, RuntimeApi>;
64
65pub 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 pub domain_id: DomainId,
84 pub key: Runtime::Keyring,
86 pub task_manager: TaskManager,
88 pub client: Arc<Client<RuntimeApi>>,
90 pub backend: Arc<Backend>,
92 pub code_executor: Arc<RuntimeExecutor>,
94 pub network_service: Arc<dyn NetworkService>,
96 pub sync_service: Arc<SyncingService<Block>>,
98 pub addr: MultiaddrWithPeerId,
101 pub rpc_handlers: RpcHandlers,
103 pub operator: DomainOperator<RuntimeApi>,
105 pub tx_pool_sink: TracingUnboundedSender<ChainMsg>,
107 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 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 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 pub fn wait_for_blocks(&self, count: usize) -> impl Future<Output = ()> {
314 self.client.wait_for_blocks(count)
315 }
316
317 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 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 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 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 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 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 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 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 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 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 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 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 pub fn ban_peer(&self, addr: MultiaddrWithPeerId) {
459 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 pub fn unban_peer(&self, addr: MultiaddrWithPeerId) {
473 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 pub async fn stop(self) -> Result<(), std::io::Error> {
491 let lock_file_path = self.base_path.path().join("paritydb").join("lock");
492 std::mem::drop(self);
494
495 sleep(Duration::from_secs(2)).await;
498
499 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 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 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 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
568pub 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 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 pub fn exclusively_connect_to_registered_parachain_nodes(mut self) -> Self {
598 self.domain_nodes_exclusive = true;
599 self
600 }
601
602 pub fn connect_to_domain_node(mut self, addr: MultiaddrWithPeerId) -> Self {
607 self.domain_nodes.push(addr);
608 self
609 }
610
611 pub fn skip_empty_bundle(mut self) -> Self {
613 self.skip_empty_bundle_production = true;
614 self
615 }
616
617 pub fn operator_id(mut self, operator_id: OperatorId) -> Self {
619 self.maybe_operator_id = Some(operator_id);
620 self
621 }
622
623 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 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 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
699pub type EvmDomainNode =
701 DomainNode<evm_domain_test_runtime::Runtime, evm_domain_test_runtime::RuntimeApi>;
702
703pub type EvmDomainClient = Client<evm_domain_test_runtime::RuntimeApi>;
705
706pub type AutoIdDomainNode =
708 DomainNode<auto_id_domain_test_runtime::Runtime, auto_id_domain_test_runtime::RuntimeApi>;
709
710pub type AutoIdDomainClient = Client<auto_id_domain_test_runtime::RuntimeApi>;