referrerpolicy=no-referrer-when-downgrade

substrate_test_client/
lib.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Client testing utilities.
19
20#![warn(missing_docs)]
21
22pub mod client_ext;
23
24pub use self::client_ext::{BlockOrigin, ClientBlockImportExt, ClientExt};
25pub use sc_client_api::{execution_extensions::ExecutionExtensions, BadBlocks, ForkBlocks};
26pub use sc_client_db::{self, Backend, BlocksPruning};
27pub use sc_executor::{self, WasmExecutionMethod, WasmExecutor};
28pub use sc_service::{client, RpcHandlers};
29pub use sp_consensus;
30pub use sp_keyring::{Ed25519Keyring, Sr25519Keyring};
31pub use sp_keystore::{Keystore, KeystorePtr};
32pub use sp_runtime::{Storage, StorageChild};
33
34use futures::{future::Future, stream::StreamExt};
35use sc_client_api::BlockchainEvents;
36use sc_service::client::{ClientConfig, LocalCallExecutor};
37use serde::Deserialize;
38use sp_core::{storage::ChildInfo, testing::TaskExecutor};
39use sp_runtime::{
40	codec::Encode,
41	traits::{Block as BlockT, Header},
42	OpaqueExtrinsic,
43};
44use std::{
45	collections::{HashMap, HashSet},
46	pin::Pin,
47	sync::Arc,
48};
49
50/// A genesis storage initialization trait.
51pub trait GenesisInit: Default {
52	/// Construct genesis storage.
53	fn genesis_storage(&self) -> Storage;
54}
55
56impl GenesisInit for () {
57	fn genesis_storage(&self) -> Storage {
58		Default::default()
59	}
60}
61
62/// A builder for creating a test client instance.
63pub struct TestClientBuilder<Block: BlockT, ExecutorDispatch, Backend: 'static, G: GenesisInit> {
64	genesis_init: G,
65	/// The key is an unprefixed storage key, this only contains
66	/// default child trie content.
67	child_storage_extension: HashMap<Vec<u8>, StorageChild>,
68	backend: Arc<Backend>,
69	_executor: std::marker::PhantomData<ExecutorDispatch>,
70	fork_blocks: ForkBlocks<Block>,
71	bad_blocks: BadBlocks<Block>,
72	enable_offchain_indexing_api: bool,
73	enable_import_proof_recording: bool,
74	no_genesis: bool,
75}
76
77impl<Block: BlockT, ExecutorDispatch, G: GenesisInit> Default
78	for TestClientBuilder<Block, ExecutorDispatch, Backend<Block>, G>
79{
80	fn default() -> Self {
81		Self::with_default_backend()
82	}
83}
84
85impl<Block: BlockT, ExecutorDispatch, G: GenesisInit>
86	TestClientBuilder<Block, ExecutorDispatch, Backend<Block>, G>
87{
88	/// Create new `TestClientBuilder` with default backend.
89	pub fn with_default_backend() -> Self {
90		let backend = Arc::new(Backend::new_test(std::u32::MAX, std::u64::MAX));
91		Self::with_backend(backend)
92	}
93
94	/// Create new `TestClientBuilder` with default backend and pruning window size
95	pub fn with_pruning_window(blocks_pruning: u32) -> Self {
96		let backend = Arc::new(Backend::new_test(blocks_pruning, 0));
97		Self::with_backend(backend)
98	}
99
100	/// Create new `TestClientBuilder` with default backend and storage chain mode
101	pub fn with_tx_storage(blocks_pruning: u32) -> Self {
102		let backend =
103			Arc::new(Backend::new_test_with_tx_storage(BlocksPruning::Some(blocks_pruning), 0));
104		Self::with_backend(backend)
105	}
106}
107
108impl<Block: BlockT, ExecutorDispatch, Backend, G: GenesisInit>
109	TestClientBuilder<Block, ExecutorDispatch, Backend, G>
110{
111	/// Create a new instance of the test client builder.
112	pub fn with_backend(backend: Arc<Backend>) -> Self {
113		TestClientBuilder {
114			backend,
115			child_storage_extension: Default::default(),
116			genesis_init: Default::default(),
117			_executor: Default::default(),
118			fork_blocks: None,
119			bad_blocks: None,
120			enable_offchain_indexing_api: false,
121			no_genesis: false,
122			enable_import_proof_recording: false,
123		}
124	}
125
126	/// Alter the genesis storage parameters.
127	pub fn genesis_init_mut(&mut self) -> &mut G {
128		&mut self.genesis_init
129	}
130
131	/// Give access to the underlying backend of these clients
132	pub fn backend(&self) -> Arc<Backend> {
133		self.backend.clone()
134	}
135
136	/// Extend child storage
137	pub fn add_child_storage(
138		mut self,
139		child_info: &ChildInfo,
140		key: impl AsRef<[u8]>,
141		value: impl AsRef<[u8]>,
142	) -> Self {
143		let storage_key = child_info.storage_key();
144		let entry = self.child_storage_extension.entry(storage_key.to_vec()).or_insert_with(|| {
145			StorageChild { data: Default::default(), child_info: child_info.clone() }
146		});
147		entry.data.insert(key.as_ref().to_vec(), value.as_ref().to_vec());
148		self
149	}
150
151	/// Sets custom block rules.
152	pub fn set_block_rules(
153		mut self,
154		fork_blocks: ForkBlocks<Block>,
155		bad_blocks: BadBlocks<Block>,
156	) -> Self {
157		self.fork_blocks = fork_blocks;
158		self.bad_blocks = bad_blocks;
159		self
160	}
161
162	/// Enable the offchain indexing api.
163	pub fn enable_offchain_indexing_api(mut self) -> Self {
164		self.enable_offchain_indexing_api = true;
165		self
166	}
167
168	/// Enable proof recording on import.
169	pub fn enable_import_proof_recording(mut self) -> Self {
170		self.enable_import_proof_recording = true;
171		self
172	}
173
174	/// Disable writing genesis.
175	pub fn set_no_genesis(mut self) -> Self {
176		self.no_genesis = true;
177		self
178	}
179
180	/// Build the test client with the given native executor.
181	pub fn build_with_executor<RuntimeApi>(
182		self,
183		executor: ExecutorDispatch,
184	) -> (
185		client::Client<Backend, ExecutorDispatch, Block, RuntimeApi>,
186		sc_consensus::LongestChain<Backend, Block>,
187	)
188	where
189		ExecutorDispatch:
190			sc_client_api::CallExecutor<Block> + sc_executor::RuntimeVersionOf + Clone + 'static,
191		Backend: sc_client_api::backend::Backend<Block>,
192		<Backend as sc_client_api::backend::Backend<Block>>::OffchainStorage: 'static,
193	{
194		let storage = {
195			let mut storage = self.genesis_init.genesis_storage();
196			// Add some child storage keys.
197			for (key, child_content) in self.child_storage_extension {
198				storage.children_default.insert(
199					key,
200					StorageChild {
201						data: child_content.data.into_iter().collect(),
202						child_info: child_content.child_info,
203					},
204				);
205			}
206
207			storage
208		};
209
210		let client_config = ClientConfig {
211			enable_import_proof_recording: self.enable_import_proof_recording,
212			offchain_indexing_api: self.enable_offchain_indexing_api,
213			no_genesis: self.no_genesis,
214			..Default::default()
215		};
216
217		let genesis_block_builder = sc_service::GenesisBlockBuilder::new(
218			&storage,
219			!client_config.no_genesis,
220			self.backend.clone(),
221			executor.clone(),
222		)
223		.expect("Creates genesis block builder");
224
225		let spawn_handle = Box::new(TaskExecutor::new());
226
227		let client = client::Client::new(
228			self.backend.clone(),
229			executor,
230			spawn_handle,
231			genesis_block_builder,
232			self.fork_blocks,
233			self.bad_blocks,
234			None,
235			None,
236			client_config,
237		)
238		.expect("Creates new client");
239
240		let longest_chain = sc_consensus::LongestChain::new(self.backend);
241
242		(client, longest_chain)
243	}
244}
245
246impl<Block: BlockT, H, Backend, G: GenesisInit>
247	TestClientBuilder<Block, client::LocalCallExecutor<Block, Backend, WasmExecutor<H>>, Backend, G>
248{
249	/// Build the test client with the given native executor.
250	pub fn build_with_native_executor<RuntimeApi, I>(
251		self,
252		executor: I,
253	) -> (
254		client::Client<
255			Backend,
256			client::LocalCallExecutor<Block, Backend, WasmExecutor<H>>,
257			Block,
258			RuntimeApi,
259		>,
260		sc_consensus::LongestChain<Backend, Block>,
261	)
262	where
263		I: Into<Option<WasmExecutor<H>>>,
264		Backend: sc_client_api::backend::Backend<Block> + 'static,
265		H: sc_executor::HostFunctions,
266	{
267		let executor = executor.into().unwrap_or_else(|| WasmExecutor::<H>::builder().build());
268		let executor = LocalCallExecutor::new(
269			self.backend.clone(),
270			executor.clone(),
271			Default::default(),
272			ExecutionExtensions::new(None, Arc::new(executor)),
273		)
274		.expect("Creates LocalCallExecutor");
275
276		self.build_with_executor(executor)
277	}
278}
279
280/// The output of an RPC transaction.
281pub struct RpcTransactionOutput {
282	/// The output string of the transaction if any.
283	pub result: String,
284	/// An async receiver if data will be returned via a callback.
285	pub receiver: tokio::sync::mpsc::Receiver<String>,
286}
287
288impl std::fmt::Debug for RpcTransactionOutput {
289	fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
290		write!(f, "RpcTransactionOutput {{ result: {:?}, receiver }}", self.result)
291	}
292}
293
294/// An error for when the RPC call fails.
295#[derive(Deserialize, Debug)]
296pub struct RpcTransactionError {
297	/// A Number that indicates the error type that occurred.
298	pub code: i64,
299	/// A String providing a short description of the error.
300	pub message: String,
301	/// A Primitive or Structured value that contains additional information about the error.
302	pub data: Option<serde_json::Value>,
303}
304
305impl std::fmt::Display for RpcTransactionError {
306	fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
307		std::fmt::Debug::fmt(self, f)
308	}
309}
310
311/// An extension trait for `RpcHandlers`.
312#[async_trait::async_trait]
313pub trait RpcHandlersExt {
314	/// Send a transaction through the RpcHandlers.
315	async fn send_transaction(
316		&self,
317		extrinsic: OpaqueExtrinsic,
318	) -> Result<RpcTransactionOutput, RpcTransactionError>;
319}
320
321#[async_trait::async_trait]
322impl RpcHandlersExt for RpcHandlers {
323	async fn send_transaction(
324		&self,
325		extrinsic: OpaqueExtrinsic,
326	) -> Result<RpcTransactionOutput, RpcTransactionError> {
327		let (result, rx) = self
328			.rpc_query(&format!(
329				r#"{{
330						"jsonrpc": "2.0",
331						"method": "author_submitExtrinsic",
332						"params": ["0x{}"],
333						"id": 0
334					}}"#,
335				array_bytes::bytes2hex("", &extrinsic.encode())
336			))
337			.await
338			.expect("valid JSON-RPC request object; qed");
339		parse_rpc_result(result, rx)
340	}
341}
342
343pub(crate) fn parse_rpc_result(
344	result: String,
345	receiver: tokio::sync::mpsc::Receiver<String>,
346) -> Result<RpcTransactionOutput, RpcTransactionError> {
347	let json: serde_json::Value =
348		serde_json::from_str(&result).expect("the result can only be a JSONRPC string; qed");
349	let error = json.as_object().expect("JSON result is always an object; qed").get("error");
350
351	if let Some(error) = error {
352		return Err(serde_json::from_value(error.clone())
353			.expect("the JSONRPC result's error is always valid; qed"))
354	}
355
356	Ok(RpcTransactionOutput { result, receiver })
357}
358
359/// An extension trait for `BlockchainEvents`.
360pub trait BlockchainEventsExt<C, B>
361where
362	C: BlockchainEvents<B>,
363	B: BlockT,
364{
365	/// Wait for `count` blocks to be imported in the node and then exit. This function will not
366	/// return if no blocks are ever created, thus you should restrict the maximum amount of time of
367	/// the test execution.
368	fn wait_for_blocks(&self, count: usize) -> Pin<Box<dyn Future<Output = ()> + Send>>;
369}
370
371impl<C, B> BlockchainEventsExt<C, B> for C
372where
373	C: BlockchainEvents<B>,
374	B: BlockT,
375{
376	fn wait_for_blocks(&self, count: usize) -> Pin<Box<dyn Future<Output = ()> + Send>> {
377		assert!(count > 0, "'count' argument must be greater than 0");
378
379		let mut import_notification_stream = self.import_notification_stream();
380		let mut blocks = HashSet::new();
381
382		Box::pin(async move {
383			while let Some(notification) = import_notification_stream.next().await {
384				if notification.is_new_best {
385					blocks.insert(*notification.header.number());
386					if blocks.len() == count {
387						break
388					}
389				}
390			}
391		})
392	}
393}
394
395#[cfg(test)]
396mod tests {
397	#[test]
398	fn parses_error_properly() {
399		let (_, rx) = tokio::sync::mpsc::channel(1);
400		assert!(super::parse_rpc_result(
401			r#"{
402				"jsonrpc": "2.0",
403				"result": 19,
404				"id": 1
405			}"#
406			.to_string(),
407			rx
408		)
409		.is_ok());
410
411		let (_, rx) = tokio::sync::mpsc::channel(1);
412		let error = super::parse_rpc_result(
413			r#"{
414				"jsonrpc": "2.0",
415				"error": {
416					"code": -32601,
417					"message": "Method not found"
418				},
419				"id": 1
420			}"#
421			.to_string(),
422			rx,
423		)
424		.unwrap_err();
425		assert_eq!(error.code, -32601);
426		assert_eq!(error.message, "Method not found");
427		assert!(error.data.is_none());
428
429		let (_, rx) = tokio::sync::mpsc::channel(1);
430		let error = super::parse_rpc_result(
431			r#"{
432				"jsonrpc": "2.0",
433				"error": {
434					"code": -32601,
435					"message": "Method not found",
436					"data": 42
437				},
438				"id": 1
439			}"#
440			.to_string(),
441			rx,
442		)
443		.unwrap_err();
444		assert_eq!(error.code, -32601);
445		assert_eq!(error.message, "Method not found");
446		assert!(error.data.is_some());
447	}
448}