1#![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
54pub type Backend = TFullBackend<Block>;
56
57type Client<RuntimeApi> = FullClient<Block, RuntimeApi>;
58
59pub type DomainOperator<RuntimeApi> =
61 domain_service::DomainOperator<Block, CBlock, subspace_test_client::Client, RuntimeApi>;
62
63pub 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 pub domain_id: DomainId,
82 pub key: Runtime::Keyring,
84 pub task_manager: TaskManager,
86 pub client: Arc<Client<RuntimeApi>>,
88 pub backend: Arc<Backend>,
90 pub code_executor: Arc<RuntimeExecutor>,
92 pub network_service: Arc<dyn NetworkService>,
94 pub sync_service: Arc<SyncingService<Block>>,
96 pub addr: MultiaddrWithPeerId,
99 pub rpc_handlers: RpcHandlers,
101 pub operator: DomainOperator<RuntimeApi>,
103 pub tx_pool_sink: TracingUnboundedSender<ChainMsg>,
105 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 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 pub fn wait_for_blocks(&self, count: usize) -> impl Future<Output = ()> {
306 self.client.wait_for_blocks(count)
307 }
308
309 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 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 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 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 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 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 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 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 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 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 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 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 pub fn ban_peer(&self, addr: MultiaddrWithPeerId) {
451 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 pub fn unban_peer(&self, addr: MultiaddrWithPeerId) {
465 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 pub fn stop(self) -> Result<(), std::io::Error> {
479 let lock_file_path = self.base_path.path().join("paritydb").join("lock");
480 std::mem::drop(self);
482 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 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 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 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
551pub 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 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 pub fn exclusively_connect_to_registered_parachain_nodes(mut self) -> Self {
581 self.domain_nodes_exclusive = true;
582 self
583 }
584
585 pub fn connect_to_domain_node(mut self, addr: MultiaddrWithPeerId) -> Self {
590 self.domain_nodes.push(addr);
591 self
592 }
593
594 pub fn skip_empty_bundle(mut self) -> Self {
596 self.skip_empty_bundle_production = true;
597 self
598 }
599
600 pub fn operator_id(mut self, operator_id: OperatorId) -> Self {
602 self.maybe_operator_id = Some(operator_id);
603 self
604 }
605
606 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 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 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
682pub type EvmDomainNode =
684 DomainNode<evm_domain_test_runtime::Runtime, evm_domain_test_runtime::RuntimeApi>;
685
686pub type EvmDomainClient = Client<evm_domain_test_runtime::RuntimeApi>;
688
689pub type AutoIdDomainNode =
691 DomainNode<auto_id_domain_test_runtime::Runtime, auto_id_domain_test_runtime::RuntimeApi>;
692
693pub type AutoIdDomainClient = Client<auto_id_domain_test_runtime::RuntimeApi>;