referrerpolicy=no-referrer-when-downgrade

polkadot_test_service/
lib.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Polkadot is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17//! Polkadot test service only.
18
19#![warn(missing_docs)]
20
21pub mod chain_spec;
22
23pub use chain_spec::*;
24use futures::{future::Future, stream::StreamExt};
25use polkadot_node_primitives::{CollationGenerationConfig, CollatorFn};
26use polkadot_node_subsystem::messages::{CollationGenerationMessage, CollatorProtocolMessage};
27use polkadot_overseer::Handle;
28use polkadot_primitives::{Balance, CollatorPair, HeadData, Id as ParaId, ValidationCode};
29use polkadot_runtime_common::BlockHashCount;
30use polkadot_runtime_parachains::paras::{ParaGenesisArgs, ParaKind};
31use polkadot_service::{
32	Error, FullClient, IsParachainNode, NewFull, OverseerGen, PrometheusConfig,
33};
34use polkadot_test_runtime::{
35	ParasCall, ParasSudoWrapperCall, Runtime, SignedPayload, SudoCall, TxExtension,
36	UncheckedExtrinsic, VERSION,
37};
38
39use sc_chain_spec::ChainSpec;
40use sc_client_api::BlockchainEvents;
41use sc_network::{
42	config::{NetworkConfiguration, TransportConfig},
43	multiaddr,
44	service::traits::NetworkService,
45	NetworkStateInfo,
46};
47use sc_service::{
48	config::{
49		DatabaseSource, KeystoreConfig, MultiaddrWithPeerId, RpcBatchRequestConfig,
50		WasmExecutionMethod, WasmtimeInstantiationStrategy,
51	},
52	BasePath, BlocksPruning, Configuration, Role, RpcHandlers, TaskManager,
53};
54use sp_arithmetic::traits::SaturatedConversion;
55use sp_blockchain::HeaderBackend;
56use sp_keyring::Sr25519Keyring;
57use sp_runtime::{codec::Encode, generic, traits::IdentifyAccount, MultiSigner};
58use sp_state_machine::BasicExternalities;
59use std::{
60	collections::HashSet,
61	net::{Ipv4Addr, SocketAddr},
62	path::PathBuf,
63	sync::Arc,
64};
65use substrate_test_client::{
66	BlockchainEventsExt, RpcHandlersExt, RpcTransactionError, RpcTransactionOutput,
67};
68
69/// The client type being used by the test service.
70pub type Client = FullClient;
71
72pub use polkadot_service::{FullBackend, GetLastTimestamp};
73use sc_service::config::{ExecutorConfiguration, RpcConfiguration};
74
75/// Create a new full node.
76#[sc_tracing::logging::prefix_logs_with(custom_log_prefix.unwrap_or(config.network.node_name.as_str()))]
77pub fn new_full<OverseerGenerator: OverseerGen>(
78	config: Configuration,
79	is_parachain_node: IsParachainNode,
80	workers_path: Option<PathBuf>,
81	overseer_gen: OverseerGenerator,
82	custom_log_prefix: Option<&'static str>,
83) -> Result<NewFull, Error> {
84	let workers_path = Some(workers_path.unwrap_or_else(get_relative_workers_path_for_test));
85
86	let params = polkadot_service::NewFullParams {
87		is_parachain_node,
88		enable_beefy: true,
89		force_authoring_backoff: false,
90		telemetry_worker_handle: None,
91		node_version: None,
92		secure_validator_mode: false,
93		workers_path,
94		workers_names: None,
95		overseer_gen,
96		overseer_message_channel_capacity_override: None,
97		malus_finality_delay: None,
98		hwbench: None,
99		execute_workers_max_num: None,
100		prepare_workers_hard_max_num: None,
101		prepare_workers_soft_max_num: None,
102		keep_finalized_for: None,
103	};
104
105	match config.network.network_backend {
106		sc_network::config::NetworkBackendType::Libp2p =>
107			polkadot_service::new_full::<_, sc_network::NetworkWorker<_, _>>(config, params),
108		sc_network::config::NetworkBackendType::Litep2p =>
109			polkadot_service::new_full::<_, sc_network::Litep2pNetworkBackend>(config, params),
110	}
111}
112
113fn get_relative_workers_path_for_test() -> PathBuf {
114	// If no explicit worker path is passed in, we need to specify it ourselves as test binaries
115	// are in the "deps/" directory, one level below where the worker binaries are generated.
116	let mut exe_path = std::env::current_exe()
117		.expect("for test purposes it's reasonable to expect that this will not fail");
118	let _ = exe_path.pop();
119	let _ = exe_path.pop();
120	exe_path
121}
122
123/// Returns a prometheus config usable for testing.
124pub fn test_prometheus_config(port: u16) -> PrometheusConfig {
125	PrometheusConfig::new_with_default_registry(
126		SocketAddr::new(Ipv4Addr::LOCALHOST.into(), port),
127		"test-chain".to_string(),
128	)
129}
130
131/// Create a Polkadot `Configuration`.
132///
133/// By default a TCP socket will be used, therefore you need to provide boot
134/// nodes if you want the future node to be connected to other nodes.
135///
136/// The `storage_update_func` function will be executed in an externalities provided environment
137/// and can be used to make adjustments to the runtime genesis storage.
138pub fn node_config(
139	storage_update_func: impl Fn(),
140	tokio_handle: tokio::runtime::Handle,
141	key: Sr25519Keyring,
142	boot_nodes: Vec<MultiaddrWithPeerId>,
143	is_validator: bool,
144) -> Configuration {
145	let base_path = BasePath::new_temp_dir().expect("could not create temporary directory");
146	let root = base_path.path().join(key.to_string());
147	let role = if is_validator { Role::Authority } else { Role::Full };
148	let key_seed = key.to_seed();
149	let mut spec = polkadot_local_testnet_config();
150	let mut storage = spec.as_storage_builder().build_storage().expect("could not build storage");
151
152	BasicExternalities::execute_with_storage(&mut storage, storage_update_func);
153	spec.set_storage(storage);
154
155	let mut network_config = NetworkConfiguration::new(
156		key_seed.to_string(),
157		"network/test/0.1",
158		Default::default(),
159		None,
160	);
161
162	network_config.boot_nodes = boot_nodes;
163
164	network_config.allow_non_globals_in_dht = true;
165
166	// Libp2p needs to know the local address on which it should listen for incoming connections,
167	// while Litep2p will use `/ip4/127.0.0.1/tcp/0` by default.
168	let addr: multiaddr::Multiaddr = "/ip4/127.0.0.1/tcp/0".parse().expect("valid address; qed");
169	network_config.listen_addresses.push(addr.clone());
170	network_config.public_addresses.push(addr);
171	network_config.transport =
172		TransportConfig::Normal { enable_mdns: false, allow_private_ip: true };
173
174	Configuration {
175		impl_name: "polkadot-test-node".to_string(),
176		impl_version: "0.1".to_string(),
177		role,
178		tokio_handle,
179		transaction_pool: Default::default(),
180		network: network_config,
181		keystore: KeystoreConfig::InMemory,
182		database: DatabaseSource::RocksDb { path: root.join("db"), cache_size: 128 },
183		trie_cache_maximum_size: Some(64 * 1024 * 1024),
184		warm_up_trie_cache: None,
185		state_pruning: Default::default(),
186		blocks_pruning: BlocksPruning::KeepFinalized,
187		chain_spec: Box::new(spec),
188		executor: ExecutorConfiguration {
189			wasm_method: WasmExecutionMethod::Compiled {
190				instantiation_strategy: WasmtimeInstantiationStrategy::PoolingCopyOnWrite,
191			},
192			..ExecutorConfiguration::default()
193		},
194		wasm_runtime_overrides: Default::default(),
195		rpc: RpcConfiguration {
196			addr: Default::default(),
197			max_request_size: Default::default(),
198			max_response_size: Default::default(),
199			max_connections: Default::default(),
200			cors: None,
201			methods: Default::default(),
202			id_provider: None,
203			max_subs_per_conn: Default::default(),
204			port: 9944,
205			message_buffer_capacity: Default::default(),
206			batch_config: RpcBatchRequestConfig::Unlimited,
207			rate_limit: None,
208			rate_limit_whitelisted_ips: Default::default(),
209			rate_limit_trust_proxy_headers: Default::default(),
210		},
211		prometheus_config: None,
212		telemetry_endpoints: None,
213		offchain_worker: Default::default(),
214		force_authoring: false,
215		disable_grandpa: false,
216		dev_key_seed: Some(key_seed),
217		tracing_targets: None,
218		tracing_receiver: Default::default(),
219		announce_block: true,
220		data_path: root,
221		base_path,
222	}
223}
224
225/// Get the listen multiaddr from the network service.
226///
227/// The address is used to connect to the node.
228pub async fn get_listen_address(network: Arc<dyn NetworkService>) -> sc_network::Multiaddr {
229	loop {
230		// Litep2p provides instantly the listen address of the TCP protocol and
231		// ditched the `/0` port used by the `node_config` function.
232		//
233		// Libp2p backend needs to be polled in a separate tokio task a few times
234		// before the listen address is available. The address is made available
235		// through the `SwarmEvent::NewListenAddr` event.
236		let listen_addresses = network.listen_addresses();
237
238		// The network backend must produce a valid TCP port.
239		match listen_addresses.into_iter().find(|addr| {
240			addr.iter().any(|protocol| match protocol {
241				multiaddr::Protocol::Tcp(port) => port > 0,
242				_ => false,
243			})
244		}) {
245			Some(multiaddr) => return multiaddr,
246			None => {
247				tokio::time::sleep(std::time::Duration::from_millis(500)).await;
248				continue;
249			},
250		}
251	}
252}
253
254/// Run a test validator node that uses the test runtime and specified `config`.
255pub async fn run_validator_node(
256	config: Configuration,
257	worker_program_path: Option<PathBuf>,
258) -> PolkadotTestNode {
259	let NewFull { task_manager, client, network, rpc_handlers, overseer_handle, .. } = new_full(
260		config,
261		IsParachainNode::No,
262		worker_program_path,
263		polkadot_service::ValidatorOverseerGen,
264		None,
265	)
266	.expect("could not create Polkadot test service");
267
268	let overseer_handle = overseer_handle.expect("test node must have an overseer handle");
269	let peer_id = network.local_peer_id();
270	let multiaddr = get_listen_address(network).await;
271
272	let addr = MultiaddrWithPeerId { multiaddr, peer_id };
273
274	PolkadotTestNode { task_manager, client, overseer_handle, addr, rpc_handlers }
275}
276
277/// Run a test collator node that uses the test runtime.
278///
279/// The node will be using an in-memory socket, therefore you need to provide boot nodes if you
280/// want it to be connected to other nodes.
281///
282/// The `storage_update_func` function will be executed in an externalities provided environment
283/// and can be used to make adjustments to the runtime genesis storage.
284///
285/// # Note
286///
287/// The collator functionality still needs to be registered at the node! This can be done using
288/// [`PolkadotTestNode::register_collator`].
289pub async fn run_collator_node(
290	tokio_handle: tokio::runtime::Handle,
291	key: Sr25519Keyring,
292	storage_update_func: impl Fn(),
293	boot_nodes: Vec<MultiaddrWithPeerId>,
294	collator_pair: CollatorPair,
295) -> PolkadotTestNode {
296	let config = node_config(storage_update_func, tokio_handle, key, boot_nodes, false);
297	let NewFull { task_manager, client, network, rpc_handlers, overseer_handle, .. } = new_full(
298		config,
299		IsParachainNode::Collator(collator_pair),
300		None,
301		polkadot_service::CollatorOverseerGen,
302		None,
303	)
304	.expect("could not create Polkadot test service");
305
306	let overseer_handle = overseer_handle.expect("test node must have an overseer handle");
307	let peer_id = network.local_peer_id();
308
309	let multiaddr = get_listen_address(network).await;
310	let addr = MultiaddrWithPeerId { multiaddr, peer_id };
311
312	PolkadotTestNode { task_manager, client, overseer_handle, addr, rpc_handlers }
313}
314
315/// A Polkadot test node instance used for testing.
316pub struct PolkadotTestNode {
317	/// `TaskManager`'s instance.
318	pub task_manager: TaskManager,
319	/// Client's instance.
320	pub client: Arc<Client>,
321	/// A handle to Overseer.
322	pub overseer_handle: Handle,
323	/// The `MultiaddrWithPeerId` to this node. This is useful if you want to pass it as "boot
324	/// node" to other nodes.
325	pub addr: MultiaddrWithPeerId,
326	/// `RPCHandlers` to make RPC queries.
327	pub rpc_handlers: RpcHandlers,
328}
329
330impl PolkadotTestNode {
331	/// Send a sudo call to this node.
332	async fn send_sudo(
333		&self,
334		call: impl Into<polkadot_test_runtime::RuntimeCall>,
335		caller: Sr25519Keyring,
336		nonce: u32,
337	) -> Result<(), RpcTransactionError> {
338		let sudo = SudoCall::sudo { call: Box::new(call.into()) };
339
340		let extrinsic = construct_extrinsic(&self.client, sudo, caller, nonce);
341		self.rpc_handlers.send_transaction(extrinsic.into()).await.map(drop)
342	}
343
344	/// Send an extrinsic to this node.
345	pub async fn send_extrinsic(
346		&self,
347		function: impl Into<polkadot_test_runtime::RuntimeCall>,
348		caller: Sr25519Keyring,
349	) -> Result<RpcTransactionOutput, RpcTransactionError> {
350		let extrinsic = construct_extrinsic(&self.client, function, caller, 0);
351
352		self.rpc_handlers.send_transaction(extrinsic.into()).await
353	}
354
355	/// Register a parachain at this relay chain.
356	pub async fn register_parachain(
357		&self,
358		id: ParaId,
359		validation_code: impl Into<ValidationCode>,
360		genesis_head: impl Into<HeadData>,
361	) -> Result<(), RpcTransactionError> {
362		let validation_code: ValidationCode = validation_code.into();
363		let call = ParasSudoWrapperCall::sudo_schedule_para_initialize {
364			id,
365			genesis: ParaGenesisArgs {
366				genesis_head: genesis_head.into(),
367				validation_code: validation_code.clone(),
368				para_kind: ParaKind::Parachain,
369			},
370		};
371
372		self.send_sudo(call, Sr25519Keyring::Alice, 0).await?;
373
374		// Bypass pvf-checking.
375		let call = ParasCall::add_trusted_validation_code { validation_code };
376		self.send_sudo(call, Sr25519Keyring::Alice, 1).await
377	}
378
379	/// Wait for `count` blocks to be imported in the node and then exit. This function will not
380	/// return if no blocks are ever created, thus you should restrict the maximum amount of time of
381	/// the test execution.
382	pub fn wait_for_blocks(&self, count: usize) -> impl Future<Output = ()> {
383		self.client.wait_for_blocks(count)
384	}
385
386	/// Wait for `count` blocks to be finalized and then exit. Similarly with `wait_for_blocks` this
387	/// function will not return if no block are ever finalized.
388	pub async fn wait_for_finalized_blocks(&self, count: usize) {
389		let mut import_notification_stream = self.client.finality_notification_stream();
390		let mut blocks = HashSet::new();
391
392		while let Some(notification) = import_notification_stream.next().await {
393			blocks.insert(notification.hash);
394			if blocks.len() == count {
395				break
396			}
397		}
398	}
399
400	/// Register the collator functionality in the overseer of this node.
401	pub async fn register_collator(
402		&mut self,
403		collator_key: CollatorPair,
404		para_id: ParaId,
405		collator: CollatorFn,
406	) {
407		let config =
408			CollationGenerationConfig { key: collator_key, collator: Some(collator), para_id };
409
410		self.overseer_handle
411			.send_msg(CollationGenerationMessage::Initialize(config), "Collator")
412			.await;
413
414		self.overseer_handle
415			.send_msg(CollatorProtocolMessage::CollateOn(para_id), "Collator")
416			.await;
417	}
418}
419
420/// Construct an extrinsic that can be applied to the test runtime.
421pub fn construct_extrinsic(
422	client: &Client,
423	function: impl Into<polkadot_test_runtime::RuntimeCall>,
424	caller: Sr25519Keyring,
425	nonce: u32,
426) -> UncheckedExtrinsic {
427	let function = function.into();
428	let current_block_hash = client.info().best_hash;
429	let current_block = client.info().best_number.saturated_into();
430	let genesis_block = client.hash(0).unwrap().unwrap();
431	let period =
432		BlockHashCount::get().checked_next_power_of_two().map(|c| c / 2).unwrap_or(2) as u64;
433	let tip = 0;
434	let tx_ext: TxExtension = (
435		frame_system::AuthorizeCall::<Runtime>::new(),
436		frame_system::CheckNonZeroSender::<Runtime>::new(),
437		frame_system::CheckSpecVersion::<Runtime>::new(),
438		frame_system::CheckTxVersion::<Runtime>::new(),
439		frame_system::CheckGenesis::<Runtime>::new(),
440		frame_system::CheckEra::<Runtime>::from(generic::Era::mortal(period, current_block)),
441		frame_system::CheckNonce::<Runtime>::from(nonce),
442		frame_system::CheckWeight::<Runtime>::new(),
443		pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::from(tip),
444		frame_system::WeightReclaim::<Runtime>::new(),
445	)
446		.into();
447	let raw_payload = SignedPayload::from_raw(
448		function.clone(),
449		tx_ext.clone(),
450		(
451			(),
452			(),
453			VERSION.spec_version,
454			VERSION.transaction_version,
455			genesis_block,
456			current_block_hash,
457			(),
458			(),
459			(),
460			(),
461		),
462	);
463	let signature = raw_payload.using_encoded(|e| caller.sign(e));
464	UncheckedExtrinsic::new_signed(
465		function.clone(),
466		polkadot_test_runtime::Address::Id(caller.public().into()),
467		polkadot_primitives::Signature::Sr25519(signature),
468		tx_ext.clone(),
469	)
470}
471
472/// Construct a transfer extrinsic.
473pub fn construct_transfer_extrinsic(
474	client: &Client,
475	origin: sp_keyring::Sr25519Keyring,
476	dest: sp_keyring::Sr25519Keyring,
477	value: Balance,
478) -> UncheckedExtrinsic {
479	let function =
480		polkadot_test_runtime::RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death {
481			dest: MultiSigner::from(dest.public()).into_account().into(),
482			value,
483		});
484
485	construct_extrinsic(client, function, origin, 0)
486}