referrerpolicy=no-referrer-when-downgrade

cumulus_test_client/
lib.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Cumulus.
3
4// Cumulus 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// Cumulus 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 Cumulus.  If not, see <http://www.gnu.org/licenses/>.
16
17//! A Cumulus test client.
18
19mod block_builder;
20pub use block_builder::*;
21use codec::{Decode, Encode};
22pub use cumulus_test_runtime as runtime;
23use cumulus_test_runtime::AuraId;
24pub use polkadot_parachain_primitives::primitives::{
25	BlockData, HeadData, ValidationParams, ValidationResult,
26};
27use runtime::{
28	Balance, Block, BlockHashCount, Runtime, RuntimeCall, Signature, SignedPayload, TxExtension,
29	UncheckedExtrinsic, VERSION,
30};
31use sc_consensus_aura::{
32	find_pre_digest,
33	standalone::{seal, slot_author},
34};
35pub use sc_executor::error::Result as ExecutorResult;
36use sc_executor::HeapAllocStrategy;
37use sc_executor_common::runtime_blob::RuntimeBlob;
38use sp_api::ProvideRuntimeApi;
39use sp_application_crypto::AppCrypto;
40use sp_blockchain::HeaderBackend;
41use sp_consensus_aura::AuraApi;
42use sp_core::Pair;
43use sp_io::TestExternalities;
44use sp_keystore::testing::MemoryKeystore;
45use sp_runtime::{generic::Era, traits::Header, BuildStorage, MultiAddress, SaturatedConversion};
46use std::sync::Arc;
47pub use substrate_test_client::*;
48
49pub type ParachainBlockData = cumulus_primitives_core::ParachainBlockData<Block>;
50
51/// Test client database backend.
52pub type Backend = substrate_test_client::Backend<Block>;
53
54/// Test client executor.
55pub type Executor = client::LocalCallExecutor<
56	Block,
57	Backend,
58	WasmExecutor<(
59		sp_io::SubstrateHostFunctions,
60		cumulus_primitives_proof_size_hostfunction::storage_proof_size::HostFunctions,
61	)>,
62>;
63
64/// Test client builder for Cumulus
65pub type TestClientBuilder =
66	substrate_test_client::TestClientBuilder<Block, Executor, Backend, GenesisParameters>;
67
68/// LongestChain type for the test runtime/client.
69pub type LongestChain = sc_consensus::LongestChain<Backend, Block>;
70
71/// Test client type with `LocalExecutor` and generic Backend.
72pub type Client = client::Client<Backend, Executor, Block, runtime::RuntimeApi>;
73
74/// Parameters of test-client builder with test-runtime.
75#[derive(Default)]
76pub struct GenesisParameters {
77	pub endowed_accounts: Vec<cumulus_test_runtime::AccountId>,
78	pub wasm: Option<Vec<u8>>,
79}
80
81impl substrate_test_client::GenesisInit for GenesisParameters {
82	fn genesis_storage(&self) -> Storage {
83		cumulus_test_service::chain_spec::get_chain_spec_with_extra_endowed(
84			None,
85			self.endowed_accounts.clone(),
86			self.wasm.as_deref().unwrap_or_else(|| {
87				cumulus_test_runtime::WASM_BINARY.expect("WASM binary not compiled!")
88			}),
89		)
90		.build_storage()
91		.expect("Builds test runtime genesis storage")
92	}
93}
94
95/// A `test-runtime` extensions to [`TestClientBuilder`].
96pub trait TestClientBuilderExt: Sized {
97	/// Build the test client.
98	fn build(self) -> Client {
99		self.build_with_longest_chain().0
100	}
101
102	/// Build the test client and longest chain selector.
103	fn build_with_longest_chain(self) -> (Client, LongestChain);
104}
105
106impl TestClientBuilderExt for TestClientBuilder {
107	fn build_with_longest_chain(self) -> (Client, LongestChain) {
108		self.build_with_native_executor(None)
109	}
110}
111
112/// A `TestClientBuilder` with default backend and executor.
113pub trait DefaultTestClientBuilderExt: Sized {
114	/// Create new `TestClientBuilder`
115	fn new() -> Self;
116}
117
118impl DefaultTestClientBuilderExt for TestClientBuilder {
119	fn new() -> Self {
120		Self::with_default_backend()
121	}
122}
123
124/// Create an unsigned extrinsic from a runtime call.
125pub fn generate_unsigned(function: impl Into<RuntimeCall>) -> UncheckedExtrinsic {
126	UncheckedExtrinsic::new_bare(function.into())
127}
128
129/// Create a signed extrinsic from a runtime call and sign
130/// with the given key pair.
131pub fn generate_extrinsic_with_pair(
132	client: &Client,
133	origin: sp_core::sr25519::Pair,
134	function: impl Into<RuntimeCall>,
135	nonce: Option<u32>,
136) -> UncheckedExtrinsic {
137	let current_block_hash = client.info().best_hash;
138	let current_block = client.info().best_number.saturated_into();
139	let genesis_block = client.hash(0).unwrap().unwrap();
140	let nonce = nonce.unwrap_or_default();
141	let period =
142		BlockHashCount::get().checked_next_power_of_two().map(|c| c / 2).unwrap_or(2) as u64;
143	let tip = 0;
144	let tx_ext: TxExtension = (
145		frame_system::AuthorizeCall::<Runtime>::new(),
146		frame_system::CheckNonZeroSender::<Runtime>::new(),
147		frame_system::CheckSpecVersion::<Runtime>::new(),
148		frame_system::CheckGenesis::<Runtime>::new(),
149		frame_system::CheckEra::<Runtime>::from(Era::mortal(period, current_block)),
150		frame_system::CheckNonce::<Runtime>::from(nonce),
151		frame_system::CheckWeight::<Runtime>::new(),
152		pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::from(tip),
153	)
154		.into();
155
156	let function = function.into();
157
158	let raw_payload = SignedPayload::from_raw(
159		function.clone(),
160		tx_ext.clone(),
161		((), (), VERSION.spec_version, genesis_block, current_block_hash, (), (), ()),
162	);
163	let signature = raw_payload.using_encoded(|e| origin.sign(e));
164
165	UncheckedExtrinsic::new_signed(
166		function,
167		MultiAddress::Id(origin.public().into()),
168		Signature::Sr25519(signature),
169		tx_ext,
170	)
171}
172
173/// Generate an extrinsic from the provided function call, origin and [`Client`].
174pub fn generate_extrinsic(
175	client: &Client,
176	origin: sp_keyring::Sr25519Keyring,
177	function: impl Into<RuntimeCall>,
178) -> UncheckedExtrinsic {
179	generate_extrinsic_with_pair(client, origin.into(), function, None)
180}
181
182/// Transfer some token from one account to another using a provided test [`Client`].
183pub fn transfer(
184	client: &Client,
185	origin: sp_keyring::Sr25519Keyring,
186	dest: sp_keyring::Sr25519Keyring,
187	value: Balance,
188) -> UncheckedExtrinsic {
189	let function = RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death {
190		dest: MultiAddress::Id(dest.public().into()),
191		value,
192	});
193
194	generate_extrinsic(client, origin, function)
195}
196
197/// Call `validate_block` in the given `wasm_blob`.
198pub fn validate_block(
199	validation_params: ValidationParams,
200	wasm_blob: &[u8],
201) -> ExecutorResult<ValidationResult> {
202	let mut ext = TestExternalities::default();
203	let mut ext_ext = ext.ext();
204
205	let heap_pages = HeapAllocStrategy::Static { extra_pages: 1024 };
206	let executor = WasmExecutor::<(
207		sp_io::SubstrateHostFunctions,
208		cumulus_primitives_proof_size_hostfunction::storage_proof_size::HostFunctions,
209	)>::builder()
210	.with_execution_method(WasmExecutionMethod::default())
211	.with_max_runtime_instances(1)
212	.with_runtime_cache_size(2)
213	.with_onchain_heap_alloc_strategy(heap_pages)
214	.with_offchain_heap_alloc_strategy(heap_pages)
215	.build();
216
217	executor
218		.uncached_call(
219			RuntimeBlob::uncompress_if_needed(wasm_blob).expect("RuntimeBlob uncompress & parse"),
220			&mut ext_ext,
221			false,
222			"validate_block",
223			&validation_params.encode(),
224		)
225		.map(|v| ValidationResult::decode(&mut &v[..]).expect("Decode `ValidationResult`."))
226}
227
228fn get_keystore() -> sp_keystore::KeystorePtr {
229	let keystore = MemoryKeystore::new();
230	sp_keyring::Sr25519Keyring::iter().for_each(|key| {
231		keystore
232			.sr25519_generate_new(
233				sp_consensus_aura::sr25519::AuthorityPair::ID,
234				Some(&key.to_seed()),
235			)
236			.expect("Key should be created");
237	});
238	Arc::new(keystore)
239}
240
241/// Seals the given block with an AURA seal.
242///
243/// Assumes that the authorities of the test runtime are present in the keyring.
244pub fn seal_block(mut block: Block, client: &Client) -> Block {
245	let parachain_slot =
246		find_pre_digest::<Block, <AuraId as AppCrypto>::Signature>(&block.header).unwrap();
247	let parent_hash = block.header.parent_hash;
248	let authorities = client.runtime_api().authorities(parent_hash).unwrap();
249	let expected_author = slot_author::<<AuraId as AppCrypto>::Pair>(parachain_slot, &authorities)
250		.expect("Should be able to find author");
251
252	let keystore = get_keystore();
253	let seal_digest = seal::<_, sp_consensus_aura::sr25519::AuthorityPair>(
254		&block.header.hash(),
255		expected_author,
256		&keystore,
257	)
258	.expect("Should be able to create seal");
259	block.header.digest_mut().push(seal_digest);
260
261	block
262}
263
264/// Seals all the blocks in the given [`ParachainBlockData`] with an AURA seal.
265///
266/// Assumes that the authorities of the test runtime are present in the keyring.
267pub fn seal_parachain_block_data(block: ParachainBlockData, client: &Client) -> ParachainBlockData {
268	let (blocks, proof) = block.into_inner();
269
270	ParachainBlockData::new(
271		blocks.into_iter().map(|block| seal_block(block, &client)).collect::<Vec<_>>(),
272		proof,
273	)
274}